mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-19 07:29:14 +00:00
Compare commits
48 Commits
card-ssr-s
...
preprocss
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e8a5427e0 | ||
|
|
52a4e5ca54 | ||
|
|
d8673a7d71 | ||
|
|
4fc6224464 | ||
|
|
4921d1c32e | ||
|
|
54d71d2319 | ||
|
|
c1ecca0169 | ||
|
|
d6a91919e0 | ||
|
|
4621094ea1 | ||
|
|
726dc73e2a | ||
|
|
4bfebf3249 | ||
|
|
99ad0abdd3 | ||
|
|
902e2b6367 | ||
|
|
7f3210b12d | ||
|
|
eee63bdecd | ||
|
|
a91fd07ed7 | ||
|
|
34aa8917a6 | ||
|
|
715c2c0def | ||
|
|
13b5385633 | ||
|
|
d25f3748c4 | ||
|
|
b6620ddf7e | ||
|
|
d70d4a91b1 | ||
|
|
bb1f7b2b7a | ||
|
|
9921c17d63 | ||
|
|
7f964f9b56 | ||
|
|
31eeea1630 | ||
|
|
ffda52a7cf | ||
|
|
9be7919c60 | ||
|
|
c30f3c4b09 | ||
|
|
7e8f13b5cb | ||
|
|
97ab986425 | ||
|
|
f1fa7f713e | ||
|
|
3394a95057 | ||
|
|
eed9ddb9b3 | ||
|
|
daf5e7734d | ||
|
|
63133d0e33 | ||
|
|
b2d74145b8 | ||
|
|
17eeccbc5d | ||
|
|
68f645e776 | ||
|
|
b3e4c59197 | ||
|
|
6080a85035 | ||
|
|
f7e088c8df | ||
|
|
80e1c05b37 | ||
|
|
99f0273783 | ||
|
|
8e13683a30 | ||
|
|
fd73542d2c | ||
|
|
bde0ed7403 | ||
|
|
8dc49f7119 |
@@ -13,3 +13,4 @@ package-lock.json
|
||||
tsconfig.json
|
||||
cdn
|
||||
_site
|
||||
docs/assets/scripts/prism-downloaded.js
|
||||
|
||||
@@ -94,7 +94,7 @@ export default function (eleventyConfig) {
|
||||
eleventyConfig.addPlugin(highlightCodePlugin());
|
||||
|
||||
// Add copy code buttons to code blocks
|
||||
eleventyConfig.addPlugin(copyCodePlugin());
|
||||
eleventyConfig.addPlugin(copyCodePlugin);
|
||||
|
||||
// Various text replacements
|
||||
eleventyConfig.addPlugin(
|
||||
|
||||
1
docs/_data/hueRanges.js
Normal file
1
docs/_data/hueRanges.js
Normal file
@@ -0,0 +1 @@
|
||||
export { hueRanges as default } from '../assets/scripts/tweak/data.js';
|
||||
@@ -1 +1 @@
|
||||
["red", "yellow", "green", "teal", "blue", "indigo", "violet", "gray"]
|
||||
["red", "yellow", "green", "cyan", "blue", "indigo", "purple", "pink", "gray"]
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default as default } from '../../src/styles/color/palettes.js';
|
||||
export { default as default } from '../../src/styles/color/scripts/palettes-analyzed.js';
|
||||
|
||||
@@ -5,16 +5,18 @@
|
||||
<meta name="theme-color" content="#f36944">
|
||||
|
||||
<script type="module" src="/assets/scripts/code-examples.js"></script>
|
||||
<script type="module" src="/assets/scripts/copy-code.js"></script>
|
||||
|
||||
<script type="module" src="/assets/scripts/scroll.js"></script>
|
||||
<script type="module" src="/assets/scripts/turbo.js"></script>
|
||||
<script type="module" src="/assets/scripts/search.js"></script>
|
||||
<script type="module" src="/assets/scripts/outline.js"></script>
|
||||
{% if hasSidebar %}<script type="module" src="/assets/scripts/sidebar-tweaks.js"></script>{% endif %}
|
||||
<script defer data-domain="backers.webawesome.com" src="https://plausible.io/js/script.js"></script>
|
||||
|
||||
{# Docs styles #}
|
||||
<link rel="stylesheet" href="/assets/styles/docs.css" />
|
||||
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body class="layout-{{ layout | stripExtension }}{{ ' page-wide' if wide }}">
|
||||
<!-- use view="desktop" as default to reduce layout jank on desktop site. -->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<table class="colors wa-palette-{{ paletteId }}">
|
||||
<table class="colors wa-palette-{{ paletteId }} contrast-table" data-min-contrast="{{ minContrast }}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
@@ -12,19 +12,31 @@
|
||||
</tr>
|
||||
</thead>
|
||||
{% for hue in hues -%}
|
||||
<tr>
|
||||
<tr data-hue="{{ hue }}">
|
||||
<th>{{ hue | capitalize }}</th>
|
||||
{% for tint_bg in tints -%}
|
||||
{% set color_bg = palettes[paletteId][hue][tint_bg] %}
|
||||
{% for tint_fg in tints | reverse -%}
|
||||
{% set color_fg = palettes[paletteId][hue][tint_fg] %}
|
||||
{% if (tint_fg - tint_bg) | abs == difference %}
|
||||
<td>
|
||||
<div class="color swatch" style="background-color: var(--wa-color-{{ hue }}-{{ tint_bg }}); color: var(--wa-color-{{ hue }}-{{ tint_fg }})">
|
||||
{% set contrast_wcag = '' %}
|
||||
{% if color_fg and color_bg %}
|
||||
{% set contrast_wcag = color_bg.contrast(color_fg, 'WCAG21') %}
|
||||
{% endif %}
|
||||
{% set contrast_wcag = '' %}
|
||||
{% if color_fg and color_bg -%}
|
||||
{% set contrast_wcag = color_bg.contrast(color_fg, 'WCAG21') %}
|
||||
{%- endif %}
|
||||
<td v-for="contrast of [contrasts.{{ hue }}['{{ tint_bg }}']['{{ tint_fg }}']]"
|
||||
data-tint-bg="{{ tint_bg }}" data-tint-fg="{{ tint_fg }}" data-original-contrast="{{ contrast_wcag }}">
|
||||
<div v-content:number="contrast.value"
|
||||
class="color swatch" :class="{
|
||||
'value-up': contrast.value - contrast.original > 0.0001,
|
||||
'value-down': contrast.original - contrast.value > 0.0001,
|
||||
'contrast-fail': contrast.value < {{ minContrast }}
|
||||
}"
|
||||
style="--color: var(--wa-color-{{ hue }}-{{ tint_bg }}); color: var(--wa-color-{{ hue }}-{{ tint_fg }})"
|
||||
:style="{
|
||||
'--color': contrast.bgColor,
|
||||
color: contrast.fgColor,
|
||||
}"
|
||||
>
|
||||
{% if contrast_wcag %}
|
||||
{{ contrast_wcag | number({maximumSignificantDigits: 2}) }}
|
||||
{% else %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<wa-tab-group>
|
||||
<wa-tab-group class="import-stylesheet-code">
|
||||
<wa-tab panel="html">In HTML</wa-tab>
|
||||
<wa-tab panel="css">In CSS</wa-tab>
|
||||
<wa-tab-panel name="html">
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
{% include "svgs/" + (page.data.icon or "thumbnail-placeholder") + ".njk" %}
|
||||
</div>
|
||||
<span class="page-name">{{ page.data.title }}</span>
|
||||
{% if pageSubtitle -%}
|
||||
<div class="wa-caption-s">{{ pageSubtitle }}</div>
|
||||
{%- endif %}
|
||||
</wa-card>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
{% set paletteId = page.fileSlug %}
|
||||
{% set tints = [80, 60, 40, 20] %}
|
||||
{% set paletteId = palette.fileSlug or page.fileSlug %}
|
||||
{% set suffixes = ['-80', '', '-20'] %}
|
||||
{% set width = 20 %}
|
||||
{% set height = 13 %}
|
||||
{% set gap_x = 3 %}
|
||||
{% set gap_y = 3 %}
|
||||
{% set height = 12 %}
|
||||
{% set height_core = 20 %}
|
||||
{% set gap_x = 4 %}
|
||||
{% set gap_y = 4 %}
|
||||
|
||||
<svg viewBox="0 0 {{ (width + gap_x) * hues|length }} {{ (height + gap_y) * tints|length }}" fill="none" xmlns="http://www.w3.org/2000/svg" class="wa-palette-{{ paletteId }} palette-icon">
|
||||
{% set total_width = (width + gap_x) * hues|length %}
|
||||
{% set total_height = (height + gap_y) * suffixes|length + (height_core - height) %}
|
||||
<svg viewBox="0 0 {{ total_width }} {{ total_height }}" fill="none" xmlns="http://www.w3.org/2000/svg" class="wa-palette-{{ paletteId }} palette-icon">
|
||||
<style>
|
||||
@import url('/dist/styles/color/{{ paletteId }}.css') layer(palette.{{ paletteId }});
|
||||
.palette-icon {
|
||||
@@ -15,10 +18,14 @@
|
||||
|
||||
{% for hue in hues -%}
|
||||
{% set hueIndex = loop.index0 %}
|
||||
{% for tint in tints -%}
|
||||
<rect x="{{ hueIndex * (width + gap_x) }}" y="{{ loop.index0 * (height + gap_y) }}"
|
||||
width="{{ width }}" height="{{ height }}"
|
||||
fill="var(--wa-color-{{ hue }}-{{ tint }})" rx="4" />
|
||||
{% set y = 0 %}
|
||||
{% for suffix in suffixes -%}
|
||||
{% set swatch_height = height if suffix else height_core %}
|
||||
|
||||
<rect x="{{ hueIndex * (width + gap_x) }}" y="{{ y }}"
|
||||
width="{{ width }}" height="{{ swatch_height }}"
|
||||
fill="var(--wa-color-{{ hue }}{{ suffix }})" rx="2" />
|
||||
{% set y = y + swatch_height + gap_y %}
|
||||
{%- endfor %}
|
||||
{% endfor %}
|
||||
</svg>
|
||||
|
||||
24
docs/_includes/svgs/theme-color.njk
Normal file
24
docs/_includes/svgs/theme-color.njk
Normal file
@@ -0,0 +1,24 @@
|
||||
{% set themeId = theme.fileSlug %}
|
||||
|
||||
<div>
|
||||
<template shadowrootmode="open">
|
||||
<link rel="stylesheet" href="/dist/styles/utilities.css">
|
||||
<link rel="stylesheet" href="/dist/styles/themes/{{ page.fileSlug or 'default' }}.css">
|
||||
<link rel="stylesheet" href="/dist/styles/themes/{{ themeId }}/color.css">
|
||||
<link rel="stylesheet" href="/assets/styles/theme-icons.css">
|
||||
|
||||
<div class="theme-color-icon wa-theme-{{ themeId }}">
|
||||
<div class="wa-brand wa-accent">A</div>
|
||||
<div class="wa-brand wa-outlined">A</div>
|
||||
<div class="wa-brand wa-filled">A</div>
|
||||
<div class="wa-brand wa-plain">A</div>
|
||||
{# <div class="wa-danger wa-outlined wa-filled"><wa-icon slot="icon" name="circle-exclamation" variant="regular"></wa-icon></div> #}
|
||||
|
||||
<div class="wa-neutral wa-accent">A</div>
|
||||
<div class="wa-neutral wa-outlined">A</div>
|
||||
<div class="wa-neutral wa-filled">A</div>
|
||||
<div class="wa-neutral wa-plain">A</div>
|
||||
{# <div class="wa-warning wa-outlined wa-filled"><wa-icon slot="icon" name="triangle-exclamation" variant="regular"></wa-icon></div> #}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
16
docs/_includes/svgs/theme-typography.njk
Normal file
16
docs/_includes/svgs/theme-typography.njk
Normal file
@@ -0,0 +1,16 @@
|
||||
{% set themeId = theme.fileSlug %}
|
||||
|
||||
<div>
|
||||
<template shadowrootmode="open">
|
||||
<link rel="stylesheet" href="/dist/styles/native/content.css">
|
||||
<link rel="stylesheet" href="/dist/styles/native/blockquote.css">
|
||||
<link rel="stylesheet" href="/dist/styles/themes/{{ page.fileSlug or 'default' }}.css">
|
||||
<link rel="stylesheet" href="/dist/styles/themes/{{ themeId }}/typography.css">
|
||||
<link rel="stylesheet" href="/assets/styles/theme-icons.css">
|
||||
|
||||
<div class="theme-typography-icon wa-theme-{{ themeId }}" data-no-outline data-no-anchor role="presentation">
|
||||
<h3>Title</h3>
|
||||
<p>Body text</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -2,7 +2,7 @@
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.8665 59.1993L33.7329 33H64C63.8352 38.2705 62.3694 43.4244 59.7264 48C57.0833 52.5756 53.3507 56.4213 48.8665 59.1993Z" fill="var(--wa-color-green-70)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M32 64C26.714 64 21.5155 62.6919 16.8665 60.1993L32 34L47.1336 60.1993C42.4845 62.6919 37.286 64 32 64Z" fill="var(--wa-color-blue-60)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.27365 48C1.63063 43.4244 0.164839 38.2706 0 33L30.2671 33L15.1336 59.1993C10.6493 56.4213 6.91666 52.5756 4.27365 48Z" fill="var(--wa-color-indigo-60)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.27365 16C6.91667 11.4244 10.6493 7.57868 15.1336 4.80072L30.2671 31L0 31C0.16484 25.7295 1.63063 20.5756 4.27365 16Z" fill="var(--wa-color-violet-60)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.27365 16C6.91667 11.4244 10.6493 7.57868 15.1336 4.80072L30.2671 31L0 31C0.16484 25.7295 1.63063 20.5756 4.27365 16Z" fill="var(--wa-color-purple-60)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M32 30L16.8665 3.80072C21.5155 1.30813 26.714 0 32 0C37.286 0 42.4845 1.30813 47.1336 3.80072L32 30Z" fill="var(--wa-color-red-60)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.7329 31L48.8665 4.80072C53.3507 7.57868 57.0833 11.4244 59.7264 16C62.3694 20.5756 63.8352 25.7294 64 31L33.7329 31Z" fill="var(--wa-color-yellow-80)"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
<div class="wa-flank">
|
||||
<wa-avatar shape="rounded" style="--size: 3em; --background-color: var(--wa-color-teal-60); --text-color: var(--wa-color-teal-95);">
|
||||
<wa-avatar shape="rounded" style="--size: 3em; --background-color: var(--wa-color-cyan-60); --text-color: var(--wa-color-cyan-95);">
|
||||
<wa-icon slot="icon" name="robot-astromech" family="duotone" style="font-size: 1.5em;"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="test-failure"><code>outlined</code></th>
|
||||
<th><code>outlined</code></th>
|
||||
<td>
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<wa-badge variant="brand" appearance="outlined">Brand</wa-badge>
|
||||
@@ -495,7 +495,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="test-failure"><code>outlined</code></th>
|
||||
<th><code>outlined</code></th>
|
||||
<td>
|
||||
<div class="wa-grid wa-gap-2xs">
|
||||
<wa-callout variant="brand" appearance="outlined">
|
||||
@@ -696,7 +696,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="test-failure"><code>outlined</code></th>
|
||||
<th><code>outlined</code></th>
|
||||
<td>
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<wa-tag variant="brand" appearance="outlined">Brand</wa-tag>
|
||||
|
||||
@@ -1,30 +1,134 @@
|
||||
{% set hasSidebar = true %}
|
||||
{% set hasOutline = true %}
|
||||
{# {% set forceTheme = page.fileSlug %} #}
|
||||
|
||||
{% extends '../_layouts/block.njk' %}
|
||||
|
||||
{% set paletteId = page.fileSlug %}
|
||||
|
||||
{% block afterContent %}
|
||||
<style>@import url('/dist/styles/color/{{ paletteId }}.css') layer(palette.{{ paletteId }});</style>
|
||||
|
||||
{% set tints = ["95", "90", "80", "70", "60", "50", "40", "30", "20", "10", "05"] %}
|
||||
|
||||
<table class="colors wa-palette-{{ paletteId }}">
|
||||
{% extends '../_includes/base.njk' %}
|
||||
|
||||
{% block head %}
|
||||
<style>@import url('/dist/styles/color/{{ paletteId }}.css') layer(palette.{{ paletteId }});</style>
|
||||
<link href="{{ page.url }}../tweak.css" rel="stylesheet">
|
||||
<script type="module" src="{{ page.url }}../tweak.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<div id="palette-app" data-palette-id="{{ paletteId }}">
|
||||
<div
|
||||
:class="{
|
||||
tweaking: tweaking.chroma,
|
||||
'tweaking-chroma': tweaking.chroma,
|
||||
'tweaking-hue': tweaking.chroma,
|
||||
'tweaked-chroma': tweaked.chroma,
|
||||
'tweaked-hue': tweaked.hue,
|
||||
'tweaked-any': tweaked.chroma || tweaked.hue
|
||||
}"
|
||||
:style="{ '--chroma-scale': chromaScale }">
|
||||
|
||||
{% include 'breadcrumbs.njk' %}
|
||||
|
||||
<h1 v-if="saved" class="title">
|
||||
{% raw %}{{ saved.title }}{% endraw %}
|
||||
<wa-icon-button name="pencil" label="Rename palette" @click="rename"></wa-icon-button>
|
||||
<wa-icon-button class="delete" name="trash" label="Delete palette" @click="deleteSaved"></wa-icon-button>
|
||||
</h1>
|
||||
<h1 v-if="!saved" class="title">{{ title }}</h1>
|
||||
|
||||
<div class="block-info">
|
||||
<code class="class">.wa-palette-{{ paletteId }}</code>
|
||||
{% include '../_includes/status.njk' %}
|
||||
</div>
|
||||
{% if description %}
|
||||
<p class="summary">
|
||||
{{ description | inlineMarkdown | safe }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block afterContent %}
|
||||
|
||||
{% set maxChroma = 0 %}
|
||||
|
||||
<wa-callout size="small" class="tweaked-callout" variant="brand">
|
||||
<wa-icon name="sliders-simple" slot="icon" variant="regular"></wa-icon>
|
||||
This palette has been tweaked.
|
||||
<wa-tag v-for="tweakHumanReadable, param in tweaksHumanReadable" removable @wa-remove="removeTweak(param)">{% raw %}{{ tweakHumanReadable }}{% endraw %}</wa-tag>
|
||||
|
||||
<wa-button @click="reset" appearance="outlined">
|
||||
<span slot="prefix" class="icon-modifier">
|
||||
<wa-icon name="circle-xmark" variant="regular"></wa-icon>
|
||||
</span>
|
||||
Reset
|
||||
</wa-button>
|
||||
<wa-button v-if="!saved" @click="save">
|
||||
<span slot="prefix" class="icon-modifier">
|
||||
<wa-icon name="sidebar" variant="regular"></wa-icon>
|
||||
<wa-icon name="circle-plus" class="modifier" style="color: light-dark(var(--wa-color-green-70), var(--wa-color-green-60));"></wa-icon>
|
||||
</span>
|
||||
Save
|
||||
</wa-button>
|
||||
</wa-callout>
|
||||
|
||||
<table class="colors main wa-palette-{{ paletteId }}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="core-column">Core tint</th>
|
||||
{% for tint in tints -%}
|
||||
<th>{{ tint }}</th>
|
||||
{%- endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
{# Initialize to last hue before gray #}
|
||||
{%- set hueBefore = hues[hues|length - 2] -%}
|
||||
{% for hue in hues -%}
|
||||
<tr>
|
||||
<th>{{ hue | capitalize }}</th>
|
||||
{%- set coreColor = palettes[paletteId][hue][palettes[paletteId][hue].maxChromaTint] -%}
|
||||
{%- set maxChroma = coreColor.c if coreColor.c > maxChroma else maxChroma -%}
|
||||
<tr data-hue="{{ hue }}" class="color-scale" :class="{tweaking: tweaking.{{ hue }}, tweaked: hueShifts.{{ hue }} }"
|
||||
:style="{ '--hue-shift': hueShifts.{{ hue }} || '' }">
|
||||
<th>
|
||||
{{ hue | capitalize }}
|
||||
</th>
|
||||
<td class="core-column" style="--color: var(--wa-color-{{ hue }})">
|
||||
{% if hue !== 'gray' %}
|
||||
{%- set hueAfter = hues[loop.index0 + 1] -%}
|
||||
{%- set hueAfter = hues[0] if hueAfter == 'gray' else hueAfter -%}
|
||||
{%- set minShift = hueRanges[hue].min - coreColor.h | round -%}
|
||||
{%- set maxShift = hueRanges[hue].max - coreColor.h | round -%}
|
||||
<wa-dropdown>
|
||||
<div slot="trigger" id="core-{{ hue }}-swatch" data-tint="core" class="color swatch" style="background-color: var(--wa-color-{{ hue }}); color: var(--wa-color-{{ hue }}-{{ '05' if palettes[paletteId][hue].maxChromaTint > 60 else '95' }});">
|
||||
{{ palettes[paletteId][hue].maxChromaTint }}
|
||||
<wa-icon name="sliders-simple" class="tweak-icon"></wa-icon>
|
||||
</div>
|
||||
<div class="popup">
|
||||
<div class="decorated-slider hue-shift-slider" style="--min: {{ minShift }}; --max: {{ maxShift }};">
|
||||
<wa-slider name="{{ hue }}-shift" v-model="hueShifts.{{ hue }}" value="0"
|
||||
min="{{ minShift }}" max="{{ maxShift }}" step="1"
|
||||
@input="tweaking.hue = tweaking.{{hue}} = true"
|
||||
@change="tweaking.hue = tweaking.{{ hue }} = false">
|
||||
<div slot="label">
|
||||
Tweak {{ hue }} hue
|
||||
<wa-icon-button @click="hueShifts.{{ hue }} = 0" class="clear-button" name="circle-xmark" library="system" variant="regular" label="Reset"></wa-icon-button>
|
||||
</div>
|
||||
</wa-slider>
|
||||
<div class="label-min">More {{hueBefore}}</div>
|
||||
<div class="label-max">More {{hueAfter}}</div>
|
||||
</div>
|
||||
<div class="wa-gap-s">
|
||||
<code>--wa-color-{{ hue }}</code>
|
||||
<wa-copy-button value="--wa-color-{{ hue }}" copy-label="--wa-color-{{ hue }}"></wa-copy-button>
|
||||
</div>
|
||||
</div>`
|
||||
</wa-dropdown>
|
||||
{%- set hueBefore = hue -%}
|
||||
{% else %}
|
||||
<div id="core-{{ hue }}-swatch" class="color swatch" data-tint="core" style="background-color: var(--wa-color-{{ hue }}); color: var(--wa-color-{{ hue }}-{{ '05' if palettes[paletteId][hue].maxChromaTint > 60 else '95' }});">
|
||||
{{ palettes[paletteId][hue].maxChromaTint }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% for tint in tints -%}
|
||||
<td>
|
||||
{%- set color = palettes[paletteId][hue][tint] -%}
|
||||
<td data-tint="{{ tint }}" style="--color: var(--wa-color-{{ hue }}-{{ tint }})">
|
||||
<div class="color swatch" style="background-color: var(--wa-color-{{ hue }}-{{ tint }})">
|
||||
<wa-copy-button value="--wa-color-{{ hue }}-{{ tint }}" copy-label="--wa-color-{{ hue }}-{{ tint }}"></wa-copy-button>
|
||||
</div>
|
||||
@@ -34,6 +138,25 @@
|
||||
{%- endfor %}
|
||||
</table>
|
||||
|
||||
{% set chromaScaleBounds = [
|
||||
(0.08 / maxChroma) | number({maximumFractionDigits: 2}),
|
||||
(0.3 / maxChroma]) | number({maximumFractionDigits: 2}) -%}
|
||||
<div class="decorated-slider chroma-scale-slider wa-palette-{{ paletteId }}"
|
||||
:class="{ tweaked: chromaScale !== 1 }"
|
||||
style="--min: {{ chromaScaleBounds[0] }}; --max: {{ chromaScaleBounds[1] }};">
|
||||
<wa-slider name="chroma-scale" v-model="chromaScale" value="1" step="0.01"
|
||||
min="{{ chromaScaleBounds[0] }}" max="{{ chromaScaleBounds[1] }}"
|
||||
@input="tweaking.chroma = true"
|
||||
@change="tweaking.chroma = false">
|
||||
<div slot="label">
|
||||
Overall colorfulness
|
||||
<wa-icon-button @click="chromaScale = 1" class="clear-button" name="circle-xmark" library="system" variant="regular" label="Reset"></wa-icon-button>
|
||||
</div>
|
||||
</wa-slider>
|
||||
<div class="label-min">More muted</div>
|
||||
<div class="label-max">More vibrant</div>
|
||||
</div>
|
||||
|
||||
<h2>Used By</h2>
|
||||
|
||||
<section class="index-grid">
|
||||
@@ -58,6 +181,7 @@ A difference of `40` ensures a minimum **3:1** contrast ratio, suitable for larg
|
||||
{% endmarkdown %}
|
||||
|
||||
{% set difference = 40 %}
|
||||
{% set minContrast = 3 %}
|
||||
{% include "contrast-table.njk" %}
|
||||
|
||||
{% markdown %}
|
||||
@@ -77,6 +201,7 @@ A difference of `50` ensures a minimum **4.5:1** contrast ratio, suitable for no
|
||||
{% endmarkdown %}
|
||||
|
||||
{% set difference = 50 %}
|
||||
{% set minContrast = 4.5 %}
|
||||
{% include "contrast-table.njk" %}
|
||||
|
||||
{% markdown %}
|
||||
@@ -95,6 +220,7 @@ A difference of `60` ensures a minimum **7:1** contrast ratio, suitable for all
|
||||
{% endmarkdown %}
|
||||
|
||||
{% set difference = 60 %}
|
||||
{% set minContrast = 7 %}
|
||||
{% include "contrast-table.njk" %}
|
||||
|
||||
{% markdown %}
|
||||
@@ -107,13 +233,34 @@ This also goes for a difference of `65`:
|
||||
{% include "contrast-table.njk" %}
|
||||
|
||||
{% markdown %}
|
||||
## How to use this palette
|
||||
## How to use this palette { #usage }
|
||||
|
||||
If you are using a Web Awesome theme that uses this palette, it will already be included.
|
||||
To use a different palette than a theme default, or to use it in a custom theme, you can import this palette directly from the Web Awesome CDN.
|
||||
|
||||
{% set stylesheet = 'styles/color/' + page.fileSlug + '.css' %}
|
||||
{% include 'import-stylesheet-code.md.njk' %}
|
||||
<wa-tab-group class="import-stylesheet-code">
|
||||
<wa-tab panel="html">In HTML</wa-tab>
|
||||
<wa-tab panel="css">In CSS</wa-tab>
|
||||
<wa-tab-panel name="html">
|
||||
|
||||
Add the following code to the `<head>` of your page:
|
||||
```html { v-content:html="code.html.highlighted" }
|
||||
<link rel="stylesheet" href="{% cdnUrl stylesheet %}" />
|
||||
```
|
||||
</wa-tab-panel>
|
||||
<wa-tab-panel name="css">
|
||||
|
||||
Add the following code at the top of your CSS file:
|
||||
```css { v-content:html="code.css.highlighted" }
|
||||
@import url('{% cdnUrl stylesheet %}');
|
||||
```
|
||||
</wa-tab-panel>
|
||||
</wa-tab-group>
|
||||
|
||||
|
||||
{% endmarkdown %}
|
||||
</div></div> {# end palette app #}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
@@ -4,100 +4,143 @@
|
||||
|
||||
{% extends '../_includes/base.njk' %}
|
||||
|
||||
{% block head %}
|
||||
<script>
|
||||
globalThis.wa_data ??= {};
|
||||
wa_data.baseTheme = "{{ page.fileSlug }}";
|
||||
wa_data.themes = {
|
||||
{% for theme in collections.theme -%}
|
||||
"{{ theme.fileSlug }}": {
|
||||
"title": "{{ theme.data.title }}",
|
||||
"palette": "{{ theme.data.palette }}",
|
||||
"brand": "{{ theme.data.brand }}"
|
||||
},
|
||||
{% endfor %}
|
||||
};
|
||||
wa_data.palettes = {
|
||||
{% for palette in collections.palette -%}
|
||||
"{{ palette.fileSlug }}": {
|
||||
"title": "{{ palette.data.title }}",
|
||||
},
|
||||
{% endfor %}
|
||||
};
|
||||
</script>
|
||||
<link href="{{ page.url }}../remix.css" rel="stylesheet">
|
||||
<script src="{{ page.url }}../remix.js" type="module"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
|
||||
<iframe src='{{ page.url }}demo.html' id="demo"></iframe>
|
||||
|
||||
<p id="mix_and_match" class="wa-gap-m">
|
||||
<strong>
|
||||
<wa-icon name="merge" slot="prefix"></wa-icon>
|
||||
Remix
|
||||
<wa-icon-button href="#remixing" name="circle-question" slot="suffix" variant="regular" label="How to use?"></wa-icon-button>
|
||||
</strong>
|
||||
<wa-select name="colors" label="Colors from…" size="small">
|
||||
<wa-details id="mix_and_match" class="wa-gap-m" >
|
||||
<h4 slot="summary" data-no-anchor data-no-outline id="remix">
|
||||
<wa-icon name="arrows-rotate"></wa-icon>
|
||||
Remix this theme
|
||||
<wa-icon id="what-is-remixing" href="#remixing" name="circle-question" slot="suffix" variant="regular"></wa-icon>
|
||||
<wa-tooltip for="what-is-remixing">Customize this theme by changing its colors and/or remixing it with design elements from other themes!</wa-tooltip>
|
||||
</h4>
|
||||
|
||||
<wa-select name="colors" label="Colors from…" value="" clearable>
|
||||
<wa-icon name="palette" slot="prefix" variant="regular"></wa-icon>
|
||||
<wa-option value="">(Theme default)</wa-option>
|
||||
<wa-divider></wa-divider>
|
||||
{% for theme in collections.theme | sort %}
|
||||
{% if theme.fileSlug !== page.fileSlug %}
|
||||
<wa-option value="{{ theme.fileSlug }}">{{ theme.data.title }}</wa-option>
|
||||
{% endif %}
|
||||
{% set currentTheme = theme.fileSlug == page.fileSlug %}
|
||||
<wa-option label="{{ theme.data.title }}" value="{{ theme.fileSlug if not currentTheme }}" {{ (theme.fileSlug if currentTheme) | attr('data-id') }}>
|
||||
<wa-card with-header>
|
||||
<div slot="header">
|
||||
{% include "svgs/theme-color.njk" %}
|
||||
</div>
|
||||
<span class="page-name">
|
||||
{{ theme.data.title }}
|
||||
{% if theme.data.isPro %}<wa-badge class="pro">PRO</wa-badge>{% endif %}
|
||||
{% if currentTheme %}<wa-badge variant="neutral" appearance="outlined">This theme</wa-badge>{% endif %}
|
||||
</span>
|
||||
</wa-card>
|
||||
</wa-option>
|
||||
{% endfor %}
|
||||
</wa-select>
|
||||
|
||||
<wa-select name="palette" label="Palette" size="small">
|
||||
<wa-select name="palette" label="Palette" clearable>
|
||||
<wa-icon name="swatchbook" slot="prefix" variant="regular"></wa-icon>
|
||||
<wa-option value="">(Theme default)</wa-option>
|
||||
<wa-divider></wa-divider>
|
||||
{% for p in collections.palette | sort %}
|
||||
{% if p.fileSlug !== palette %}
|
||||
<wa-option value="{{ p.fileSlug }}">{{ p.data.title }}</wa-option>
|
||||
{% endif %}
|
||||
{% set defaultPalette = palette %}
|
||||
{% for palette in collections.palette | sort %}
|
||||
{% set currentPalette = palette.fileSlug == defaultPalette %}
|
||||
<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" %}
|
||||
</div>
|
||||
<span class="page-name">
|
||||
{{ palette.data.title }}
|
||||
{% if palette.data.isPro %}<wa-badge class="pro">PRO</wa-badge>{% endif %}
|
||||
{% if currentPalette %}<wa-badge variant="neutral" appearance="outlined">Theme default</wa-badge>{% endif %}
|
||||
</span>
|
||||
</wa-card>
|
||||
</wa-option>
|
||||
{% endfor %}
|
||||
{% set palette = defaultPalette %}
|
||||
</wa-select>
|
||||
|
||||
<wa-select name="brand" label="Brand color" value="" clearable>
|
||||
<div class="selected-swatch" slot="prefix"></div>
|
||||
{% for hue in hues %}
|
||||
{% set currentBrand = hue == brand %}
|
||||
<wa-option label="{{ hue | capitalize }}" value="{{ hue if not currentBrand }}" {{ (hue if currentBrand) | attr('data-id') }} style="--color: var(--wa-color-{{ hue }})">
|
||||
{{ hue | capitalize }}
|
||||
{% if currentBrand %}<wa-badge variant="neutral" appearance="outlined">Theme default</wa-badge>{% endif %}
|
||||
</wa-option>
|
||||
{% endfor %}
|
||||
</wa-select>
|
||||
|
||||
<wa-select name="typography" label="Typography from…" size="small">
|
||||
<wa-select name="typography" label="Typography from…" clearable>
|
||||
<wa-icon name="font-case" slot="prefix"></wa-icon>
|
||||
<wa-option value="">(Theme default)</wa-option>
|
||||
<wa-divider></wa-divider>
|
||||
{% for theme in collections.theme | sort %}
|
||||
{% if theme.fileSlug !== page.fileSlug %}
|
||||
<wa-option value="{{ theme.fileSlug }}">{{ theme.data.title }}</wa-option>
|
||||
{% endif %}
|
||||
{% set currentTheme = theme.fileSlug == page.fileSlug %}
|
||||
<wa-option label="{{ theme.data.title }}" value="{{ theme.fileSlug if not currentTheme }}" {{ (theme.fileSlug if currentTheme) | attr('data-id') }}>
|
||||
<wa-card with-header>
|
||||
<div slot="header">
|
||||
{% include "svgs/theme-typography.njk" %}
|
||||
</div>
|
||||
<span class="page-name">
|
||||
{{ theme.data.title }}
|
||||
{% if theme.data.isPro %}<wa-badge class="pro">PRO</wa-badge>{% endif %}
|
||||
{% if currentTheme %}<wa-badge variant="neutral" appearance="outlined">This theme</wa-badge>{% endif %}
|
||||
</span>
|
||||
</wa-card>
|
||||
</wa-option>
|
||||
{% endfor %}
|
||||
</wa-select>
|
||||
</wa-details>
|
||||
|
||||
|
||||
</p>
|
||||
<script>
|
||||
document.querySelector('#mix_and_match').addEventListener('change', function(event) {
|
||||
let selects = document.querySelectorAll('#mix_and_match wa-select');
|
||||
let url = new URL(demo.src);
|
||||
|
||||
for (let select of selects) {
|
||||
url.searchParams.set(select.name, select.value);
|
||||
}
|
||||
|
||||
demo.src = url;
|
||||
});
|
||||
</script>
|
||||
|
||||
<h2>Default Color Palette</h2>
|
||||
<h2>Color</h2>
|
||||
{% set paletteURL = '/docs/palettes/' + palette + '/' %}
|
||||
{% set themePage = page %}
|
||||
{% set page = paletteURL | getCollectionItemFromUrl %}
|
||||
<div class="index-grid">
|
||||
{% include 'page-card.njk' %}
|
||||
</div>
|
||||
{% set page = themePage %}
|
||||
|
||||
|
||||
<div class="index-grid">
|
||||
{% set themePage = page %}
|
||||
{% set page = paletteURL | getCollectionItemFromUrl %}
|
||||
{% set pageSubtitle = "Default color palette" %}
|
||||
{% include 'page-card.njk' %}
|
||||
{% set page = themePage %}
|
||||
|
||||
<wa-card style="--header-background: var(--wa-color-{{ brand }})" class="wa-palette-{{ palette }}">
|
||||
<div slot="header"></div>
|
||||
<div class="page-name">{{ brand | capitalize }}</div>
|
||||
<div class="wa-caption-s">Default brand color</div>
|
||||
</wa-card>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block afterContent %}
|
||||
{% markdown %}
|
||||
## How to use this theme
|
||||
|
||||
<h2 id="usage">How to use this theme</h2>
|
||||
|
||||
{% markdown %}
|
||||
You can import this theme from the Web Awesome CDN.
|
||||
|
||||
{% set stylesheet = 'styles/themes/' + page.fileSlug + '.css' %}
|
||||
{% include 'import-stylesheet-code.md.njk' %}
|
||||
|
||||
### Remixing { #remixing }
|
||||
|
||||
If you want to combine the **colors** from this theme with another theme, you can import this CSS file *after* the other theme’s CSS file:
|
||||
|
||||
{% set stylesheet = 'styles/themes/' + page.fileSlug + '/color.css' %}
|
||||
{% include 'import-stylesheet-code.md.njk' %}
|
||||
|
||||
To use the **typography** from this theme with another theme, you can import this CSS file *after* the other theme’s CSS file:
|
||||
|
||||
{% set stylesheet = 'styles/themes/' + page.fileSlug + '/typography.css' %}
|
||||
{% include 'import-stylesheet-code.md.njk' %}
|
||||
|
||||
<wa-callout variant="warning">
|
||||
<wa-icon slot="icon" name="triangle-exclamation" variant="regular"></wa-icon>
|
||||
Please note that not all combinations will look good — once you’re mixing and matching, you’re on your own!
|
||||
</wa-callout>
|
||||
|
||||
## Dark mode
|
||||
|
||||
To activate the dark color scheme of the theme on any element and its contents, apply the class `wa-dark` to it.
|
||||
|
||||
@@ -37,7 +37,8 @@ export function anchorHeadingsPlugin(options = {}) {
|
||||
}
|
||||
|
||||
// Look for headings
|
||||
container.querySelectorAll(options.headingSelector).forEach(heading => {
|
||||
let selector = `:is(${options.headingSelector}):not([data-no-anchor], [data-no-anchor] *)`;
|
||||
container.querySelectorAll(selector).forEach(heading => {
|
||||
const hasAnchor = heading.querySelector('a');
|
||||
const existingId = heading.getAttribute('id');
|
||||
const clone = parse(heading.outerHTML);
|
||||
|
||||
@@ -3,30 +3,39 @@ import { parse } from 'node-html-parser';
|
||||
/**
|
||||
* Eleventy plugin to add copy buttons to code blocks.
|
||||
*/
|
||||
export function copyCodePlugin(options = {}) {
|
||||
export function copyCodePlugin(eleventyConfig, options = {}) {
|
||||
options = {
|
||||
container: 'body',
|
||||
...options,
|
||||
};
|
||||
|
||||
return function (eleventyConfig) {
|
||||
eleventyConfig.addTransform('copy-code', content => {
|
||||
const doc = parse(content, { blockTextElements: { code: true } });
|
||||
const container = doc.querySelector(options.container);
|
||||
let codeCount = 0;
|
||||
eleventyConfig.addTransform('copy-code', content => {
|
||||
const doc = parse(content, { blockTextElements: { code: true } });
|
||||
const container = doc.querySelector(options.container);
|
||||
|
||||
if (!container) {
|
||||
return content;
|
||||
if (!container) {
|
||||
return content;
|
||||
}
|
||||
|
||||
// Look for code blocks
|
||||
container.querySelectorAll('pre > code').forEach(code => {
|
||||
const pre = code.closest('pre');
|
||||
let preId = pre.getAttribute('id') || `code-block-${++codeCount}`;
|
||||
let codeId = code.getAttribute('id') || `${preId}-inner`;
|
||||
|
||||
if (!code.getAttribute('id')) {
|
||||
code.setAttribute('id', codeId);
|
||||
}
|
||||
if (!pre.getAttribute('id')) {
|
||||
pre.setAttribute('id', preId);
|
||||
}
|
||||
|
||||
// Look for code blocks
|
||||
container.querySelectorAll('pre > code').forEach(code => {
|
||||
const pre = code.closest('pre');
|
||||
|
||||
// Add a copy button (we set the copy data at runtime to reduce page bloat)
|
||||
pre.innerHTML = `<wa-copy-button class="copy-button" hoist></wa-copy-button>` + pre.innerHTML;
|
||||
});
|
||||
|
||||
return doc.toString();
|
||||
// Add a copy button
|
||||
pre.innerHTML += `<wa-icon-button href="#${preId}" class="block-link-icon" name="link"></wa-icon-button>
|
||||
<wa-copy-button from="${codeId}" class="copy-button" hoist></wa-copy-button>`;
|
||||
});
|
||||
};
|
||||
|
||||
return doc.toString();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
function setCopyValue() {
|
||||
document.querySelectorAll('.copy-button').forEach(copyButton => {
|
||||
const pre = copyButton.closest('pre');
|
||||
const code = pre?.querySelector('code');
|
||||
|
||||
if (code) {
|
||||
copyButton.value = code.textContent;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Set data for all copy buttons when the page loads
|
||||
setCopyValue();
|
||||
|
||||
document.addEventListener('turbo:load', setCopyValue);
|
||||
8
docs/assets/scripts/prism-downloaded.js
Normal file
8
docs/assets/scripts/prism-downloaded.js
Normal file
File diff suppressed because one or more lines are too long
8
docs/assets/scripts/prism.js
Normal file
8
docs/assets/scripts/prism.js
Normal file
@@ -0,0 +1,8 @@
|
||||
globalThis.Prism = globalThis.Prism || {};
|
||||
globalThis.Prism.manual = true;
|
||||
|
||||
await import('./prism-downloaded.js');
|
||||
|
||||
Prism.plugins.customClass.prefix('code-');
|
||||
|
||||
export default Prism;
|
||||
206
docs/assets/scripts/sidebar-tweaks.js
Normal file
206
docs/assets/scripts/sidebar-tweaks.js
Normal file
@@ -0,0 +1,206 @@
|
||||
const sidebar = (globalThis.sidebar = {});
|
||||
|
||||
sidebar.palettes = {
|
||||
render() {
|
||||
if (this.saved.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let palette of this.saved) {
|
||||
sidebar.palette.render(palette);
|
||||
}
|
||||
|
||||
sidebar.updateCurrent();
|
||||
},
|
||||
|
||||
saved: localStorage.savedPalettes ? JSON.parse(localStorage.savedPalettes) : [],
|
||||
|
||||
save(saved = this.saved) {
|
||||
this.saved = saved ?? [];
|
||||
|
||||
if (saved.length > 0) {
|
||||
localStorage.savedPalettes = JSON.stringify(saved);
|
||||
} else {
|
||||
delete localStorage.savedPalettes;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
sidebar.palette = {
|
||||
getUid() {
|
||||
let savedPalettes = sidebar.palettes.saved;
|
||||
let uids = new Set(savedPalettes.map(p => p.uid));
|
||||
|
||||
if (savedPalettes.length === 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Find first available number
|
||||
for (let i = 1; i < savedPalettes.length + 1; i++) {
|
||||
if (!uids.has(i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
equals(p1, p2) {
|
||||
if (!p1 || !p2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return p1.id === p2.id && p1.uid === p2.uid;
|
||||
},
|
||||
|
||||
delete(palette) {
|
||||
let savedPalettes = sidebar.palettes.saved;
|
||||
let count = savedPalettes.length;
|
||||
if (count === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO improve UX of this
|
||||
if (!confirm(`Are you sure you want to delete palette “${palette.title}”?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
savedPalettes = savedPalettes.filter(p => !sidebar.palette.equals(palette, p));
|
||||
|
||||
if (savedPalettes.length === count) {
|
||||
// Nothing was removed
|
||||
return;
|
||||
}
|
||||
|
||||
// Update UI
|
||||
let pathname = `/docs/palettes/${palette.id}/`;
|
||||
let url = pathname + palette.search;
|
||||
let uls = new Set();
|
||||
|
||||
for (let a of document.querySelectorAll(`#sidebar a[href="${url}"]`)) {
|
||||
let li = a.closest('li');
|
||||
let ul = li.closest('ul');
|
||||
uls.add(ul);
|
||||
li.remove();
|
||||
}
|
||||
|
||||
// Remove empty lists
|
||||
for (let ul of uls) {
|
||||
if (!ul.children.length) {
|
||||
ul.remove();
|
||||
}
|
||||
}
|
||||
|
||||
sidebar.updateCurrent();
|
||||
|
||||
sidebar.palettes.save(savedPalettes);
|
||||
|
||||
if (sidebar.palette.equals(globalThis.paletteApp?.saved, palette)) {
|
||||
paletteApp.saved = null;
|
||||
}
|
||||
},
|
||||
|
||||
getSaved(palette, savedPalettes = sidebar.palettes.saved) {
|
||||
return savedPalettes.find(p => sidebar.palette.equals(p, palette));
|
||||
},
|
||||
|
||||
render(palette) {
|
||||
// Find existing <a>
|
||||
let { title, id, search, uid } = palette;
|
||||
|
||||
for (let a of document.querySelectorAll(`#sidebar a[href^="/docs/palettes/${id}/"][data-uid="${uid}"]`)) {
|
||||
// Palette already in sidebar, just update it
|
||||
a.textContent = palette.title;
|
||||
a.href = `/docs/palettes/${id}/${search}`;
|
||||
return;
|
||||
}
|
||||
|
||||
let pathname = `/docs/palettes/${id}/`;
|
||||
let url = pathname + search;
|
||||
let parentA = document.querySelector(`a[href="${pathname}"]`);
|
||||
let parentLi = parentA?.closest('li');
|
||||
let a;
|
||||
|
||||
if (parentLi) {
|
||||
a = Object.assign(document.createElement('a'), { href: url, textContent: title });
|
||||
a.dataset.uid = uid;
|
||||
let badges = [...parentLi.querySelectorAll('wa-badge')].map(badge => badge.cloneNode(true));
|
||||
let ul = parentLi.querySelector('ul') ?? parentLi.appendChild(document.createElement('ul'));
|
||||
let li = document.createElement('li');
|
||||
let deleteButton = Object.assign(document.createElement('wa-icon-button'), {
|
||||
name: 'trash',
|
||||
label: 'Delete',
|
||||
className: 'delete',
|
||||
});
|
||||
|
||||
deleteButton.addEventListener('click', () => {
|
||||
let palette = { id, uid, title: a.textContent, search: a.search };
|
||||
sidebar.palette.delete(palette);
|
||||
});
|
||||
|
||||
li.append(a, ' ', ...badges, deleteButton);
|
||||
ul.appendChild(li);
|
||||
}
|
||||
},
|
||||
|
||||
save(palette, saved) {
|
||||
let savedPalettes = sidebar.palettes.saved;
|
||||
let existing = this.getSaved(saved ?? palette, savedPalettes);
|
||||
let oldValues;
|
||||
|
||||
if (existing) {
|
||||
// Rename
|
||||
oldValues = { ...existing };
|
||||
Object.assign(existing, palette);
|
||||
} else {
|
||||
savedPalettes.push(palette);
|
||||
}
|
||||
|
||||
this.render(palette, oldValues);
|
||||
sidebar.updateCurrent();
|
||||
|
||||
sidebar.palettes.save(savedPalettes);
|
||||
},
|
||||
};
|
||||
|
||||
sidebar.updateCurrent = function () {
|
||||
// Find the sidebar link with the longest shared prefix with the current URL
|
||||
let pathParts = location.pathname.split('/').filter(Boolean);
|
||||
let prefixes = [];
|
||||
|
||||
if (pathParts.length === 1) {
|
||||
// If at /docs/ we just use that, otherwise we want at least two parts (/docs/xxx/)
|
||||
prefixes.push('/' + pathParts[0] + '/');
|
||||
} else {
|
||||
for (let i = 2; i <= pathParts.length; i++) {
|
||||
prefixes.push('/' + pathParts.slice(0, i).join('/') + '/');
|
||||
}
|
||||
}
|
||||
|
||||
// Last prefix includes the search too (if any)
|
||||
if (location.search) {
|
||||
let params = new URLSearchParams(location.search);
|
||||
params.sort();
|
||||
prefixes.push(prefixes.at(-1) + location.search);
|
||||
}
|
||||
|
||||
// We want to start from the longest prefix
|
||||
prefixes.reverse();
|
||||
|
||||
for (let prefix of prefixes) {
|
||||
let a = document.querySelector(`#sidebar a[href^="${prefix}"]`);
|
||||
|
||||
if (a) {
|
||||
for (let current of document.querySelectorAll('#sidebar a.current')) {
|
||||
current.classList.remove('current');
|
||||
}
|
||||
a.classList.add('current');
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sidebar.render = function () {
|
||||
this.palettes.render();
|
||||
};
|
||||
|
||||
sidebar.render();
|
||||
window.addEventListener('turbo:render', () => sidebar.render());
|
||||
6
docs/assets/scripts/tweak.js
Normal file
6
docs/assets/scripts/tweak.js
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Get import code for remixed themes and tweaked palettes.
|
||||
*/
|
||||
export { theme as getThemeCode } from './tweak/code.js';
|
||||
export { cdnUrl, hueRanges, hues, selectors, tints, urls } from './tweak/data.js';
|
||||
export { default as Permalink } from './tweak/permalink.js';
|
||||
49
docs/assets/scripts/tweak/code.js
Normal file
49
docs/assets/scripts/tweak/code.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Get import code for remixed themes and tweaked palettes.
|
||||
*/
|
||||
import { urls } from './data.js';
|
||||
|
||||
export function cssImport(url, options = {}) {
|
||||
let { language = 'html', cdnUrl = '/dist/', attributes } = options;
|
||||
url = cdnUrl + url;
|
||||
|
||||
if (language === 'css') {
|
||||
return `@import url('${url}');`;
|
||||
} else {
|
||||
attributes = attributes ? ` ${attributes}` : '';
|
||||
return `<link rel="stylesheet" href="${url}"${attributes} />`;
|
||||
}
|
||||
}
|
||||
|
||||
export function cssLiteral(value, options = {}) {
|
||||
let { language = 'html' } = options;
|
||||
|
||||
if (language === 'css') {
|
||||
return value;
|
||||
} else {
|
||||
return `<style>\n${value}\n</style>`;
|
||||
}
|
||||
}
|
||||
|
||||
export function theme(base, params, options) {
|
||||
let ret = [];
|
||||
|
||||
if (base) {
|
||||
ret.push(urls.theme(base));
|
||||
}
|
||||
|
||||
ret.push(
|
||||
...Object.entries(params)
|
||||
.filter(([aspect, id]) => Boolean(id))
|
||||
.map(([aspect, id]) => urls[aspect](id)),
|
||||
);
|
||||
|
||||
return ret.map(url => cssImport(url, options)).join('\n');
|
||||
}
|
||||
|
||||
export function cssRule(selector, declarations, { indent = ' ' } = {}) {
|
||||
selector = Array.isArray(selector) ? selector.flat().join(',\n') : selector;
|
||||
declarations = Array.isArray(declarations) ? declarations.flat() : declarations;
|
||||
declarations = declarations.map(declaration => indent + declaration.trim()).join('\n');
|
||||
return `${selector} {\n${declarations.trimEnd()}\n}`;
|
||||
}
|
||||
34
docs/assets/scripts/tweak/data.js
Normal file
34
docs/assets/scripts/tweak/data.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Data related to theme remixing and palette tweaking
|
||||
* Must work in both browser and Node.js
|
||||
*/
|
||||
export const cdnUrl = globalThis.document ? document.documentElement.dataset.cdnUrl : '/dist/';
|
||||
|
||||
export const urls = {
|
||||
theme: id => `styles/themes/${id}.css`,
|
||||
colors: id => `styles/themes/${id}/color.css`,
|
||||
palette: id => `styles/color/${id}.css`,
|
||||
brand: id => `styles/brand/${id}.css`,
|
||||
typography: id => `styles/themes/${id}/typography.css`,
|
||||
};
|
||||
|
||||
export const selectors = {
|
||||
palette: id =>
|
||||
[':where(:root)', ':host', ":where([class^='wa-theme-'], [class*=' wa-theme-'])", `.wa-palette-${id}`].join(',\n'),
|
||||
};
|
||||
|
||||
export const hueRanges = {
|
||||
red: { min: 5, max: 35 }, // 30
|
||||
orange: { min: 35, max: 60 }, // 25
|
||||
yellow: { min: 60, max: 112 }, // 45
|
||||
green: { min: 112, max: 170 }, // 55
|
||||
cyan: { min: 170, max: 220 }, // 50
|
||||
blue: { min: 220, max: 265 }, // 45
|
||||
indigo: { min: 265, max: 290 }, // 25
|
||||
purple: { min: 290, max: 320 }, // 30
|
||||
pink: { min: 320, max: 365 }, // 45
|
||||
};
|
||||
|
||||
export const hues = Object.keys(hueRanges);
|
||||
|
||||
export const tints = ['05', '10', '20', '30', '40', '50', '60', '70', '80', '90', '95'];
|
||||
81
docs/assets/scripts/tweak/permalink.js
Normal file
81
docs/assets/scripts/tweak/permalink.js
Normal file
@@ -0,0 +1,81 @@
|
||||
const IDENTITY = x => x;
|
||||
|
||||
export default class Permalink extends URLSearchParams {
|
||||
/** Params changed since last URL I/O */
|
||||
changed = false;
|
||||
|
||||
constructor(params) {
|
||||
super(location.search);
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return Object.fromEntries(this.entries());
|
||||
}
|
||||
|
||||
#mappings = new WeakMap();
|
||||
|
||||
mapObject(obj, mapping = {}) {
|
||||
this.#mappings.set(obj, mapping);
|
||||
}
|
||||
|
||||
readFrom(obj) {
|
||||
let mapping = this.#mappings.get(obj) ?? {};
|
||||
let { keyFrom = IDENTITY, valueFrom = IDENTITY } = mapping;
|
||||
|
||||
for (let key in obj) {
|
||||
let value = obj[key];
|
||||
let mappedValue = valueFrom(value);
|
||||
let mappedKey = keyFrom(key);
|
||||
this.set(mappedKey, mappedValue);
|
||||
}
|
||||
}
|
||||
|
||||
writeTo(obj) {
|
||||
let mapping = this.#mappings.get(obj) ?? {};
|
||||
let { keyTo = IDENTITY, valueTo = IDENTITY, canExtend = false } = mapping;
|
||||
|
||||
for (let [key, value] of this) {
|
||||
let mappedKey = keyTo(key);
|
||||
let mappedValue = valueTo(value);
|
||||
|
||||
if (canExtend || mappedKey in obj) {
|
||||
obj[mappedKey] = mappedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set(key, value, defaultValue) {
|
||||
let oldValue = this.get(key);
|
||||
|
||||
if (!value || value == defaultValue) {
|
||||
super.delete(key);
|
||||
|
||||
if (oldValue) {
|
||||
this.changed = true;
|
||||
}
|
||||
} else {
|
||||
super.set(key, value);
|
||||
|
||||
if (String(value) !== String(oldValue)) {
|
||||
this.changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update page URL if it has changed since last time
|
||||
*/
|
||||
updateLocation() {
|
||||
if (this.changed) {
|
||||
// If there’s already a search, replace it.
|
||||
// We don’t want to clog the user’s history while they iterate
|
||||
let search = this.toString();
|
||||
let historyAction = location.search && search ? 'replaceState' : 'pushState';
|
||||
history[historyAction](null, '', `?${search}`);
|
||||
this.changed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,3 +27,19 @@ wa-copy-button.copy-button {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.block-link-icon {
|
||||
position: absolute;
|
||||
inset-block-start: 0;
|
||||
inset-inline-end: calc(100% + var(--wa-space-s));
|
||||
|
||||
transition: var(--wa-transition-slow);
|
||||
|
||||
&:not(:hover, :focus) {
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
:not(:hover, :focus-within) > & {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ wa-page > header {
|
||||
}
|
||||
|
||||
/* Pro badges */
|
||||
wa-badge.pro::part(base) {
|
||||
wa-badge.pro {
|
||||
background-color: var(--wa-brand-orange);
|
||||
border-color: var(--wa-brand-orange);
|
||||
}
|
||||
@@ -188,6 +188,29 @@ wa-badge.pro::part(base) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wa-icon-button.delete {
|
||||
vertical-align: -0.2em;
|
||||
margin-inline-start: var(--wa-space-xs);
|
||||
|
||||
&:not(li:hover > *, :focus) {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wa-icon-button.delete {
|
||||
&:hover {
|
||||
color: var(--wa-color-danger-on-quiet);
|
||||
}
|
||||
|
||||
&::part(base):hover {
|
||||
background: var(--wa-color-danger-fill-quiet);
|
||||
}
|
||||
|
||||
&:not(:hover, :focus) {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
#sidebar-close-button {
|
||||
@@ -232,16 +255,32 @@ wa-page > main {
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
h1.title wa-badge {
|
||||
vertical-align: middle;
|
||||
h1.title {
|
||||
wa-icon-button {
|
||||
font-size: var(--wa-font-size-l);
|
||||
color: var(--wa-color-text-quiet);
|
||||
|
||||
&::part(base) {
|
||||
&:not(:hover, :focus) {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
wa-badge {
|
||||
vertical-align: middle;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.block-info {
|
||||
display: flex;
|
||||
gap: var(--wa-space-xs);
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
margin-block-end: var(--wa-flow-spacing);
|
||||
|
||||
code {
|
||||
line-height: var(--wa-line-height-condensed);
|
||||
}
|
||||
}
|
||||
|
||||
/* Current link */
|
||||
@@ -360,7 +399,7 @@ wa-page > main:has(> .index-grid) {
|
||||
}
|
||||
|
||||
&::part(header) {
|
||||
background-color: var(--wa-color-neutral-fill-quiet);
|
||||
background-color: var(--header-background, var(--wa-color-neutral-fill-quiet));
|
||||
border-bottom: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -400,9 +439,18 @@ wa-page > main:has(> .index-grid) {
|
||||
|
||||
&.color {
|
||||
border-color: transparent;
|
||||
transition: background var(--wa-transition-slow);
|
||||
background: linear-gradient(var(--color-2, transparent) 0% 100%) no-repeat border-box var(--color,);
|
||||
background-position: var(--color-2-position, bottom);
|
||||
background-size: var(--color-2-width, 100%) var(--color-2-height, 50%);
|
||||
|
||||
&.contrast-fail {
|
||||
outline: 1px dashed var(--wa-color-red);
|
||||
outline-offset: calc(-1 * var(--wa-space-2xs));
|
||||
}
|
||||
}
|
||||
|
||||
wa-copy-button {
|
||||
> wa-copy-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@@ -437,6 +485,7 @@ table.colors {
|
||||
padding-block: 0;
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
border: none;
|
||||
@@ -456,6 +505,59 @@ table.colors {
|
||||
padding-block: var(--wa-space-s);
|
||||
}
|
||||
}
|
||||
|
||||
.core-column {
|
||||
padding-inline-end: var(--wa-space-xl);
|
||||
}
|
||||
}
|
||||
|
||||
.value-up,
|
||||
.value-down {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: ' ' var(--icon);
|
||||
position: absolute;
|
||||
margin-inline-start: 3em;
|
||||
scale: 1 0.6;
|
||||
color: color-mix(in oklch, oklch(from var(--icon-color) none c h) 0%, oklch(from currentColor l none none));
|
||||
font-size: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
.value-down {
|
||||
--icon: '▼';
|
||||
--icon-color: var(--wa-color-danger-fill-quiet);
|
||||
|
||||
&::after {
|
||||
margin-block-end: -0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.value-up {
|
||||
--icon: '▲';
|
||||
--icon-color: var(--wa-color-success-fill-quiet);
|
||||
}
|
||||
|
||||
.icon-modifier {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
|
||||
.modifier {
|
||||
position: absolute;
|
||||
bottom: -0.1em;
|
||||
right: -0.3em;
|
||||
font-size: 60%;
|
||||
|
||||
&::part(svg) {
|
||||
stroke: var(--background-color, var(--wa-color-surface-default));
|
||||
stroke-width: 100px;
|
||||
paint-order: stroke;
|
||||
overflow: visible;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Layout Examples */
|
||||
@@ -538,23 +640,4 @@ table.colors {
|
||||
height: 65vh;
|
||||
max-height: 21lh;
|
||||
}
|
||||
|
||||
#mix_and_match {
|
||||
strong {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--wa-space-2xs);
|
||||
margin-top: 1.2em;
|
||||
}
|
||||
|
||||
wa-select::part(label) {
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
wa-select[value='']::part(display-input),
|
||||
wa-option[value=''] {
|
||||
font-style: italic;
|
||||
color: var(--wa-color-text-quiet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
docs/assets/styles/theme-icons.css
Normal file
34
docs/assets/styles/theme-icons.css
Normal file
@@ -0,0 +1,34 @@
|
||||
.theme-color-icon {
|
||||
display: grid;
|
||||
gap: var(--wa-space-xs);
|
||||
grid-template-columns: repeat(4, auto);
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
border-radius: var(--wa-border-radius-m);
|
||||
background-color: var(--background-color);
|
||||
border: var(--wa-border-width-s) var(--wa-border-style) var(--border-color);
|
||||
padding: var(--wa-space-2xs) var(--wa-space-xs);
|
||||
color: var(--text-color);
|
||||
font-weight: var(--wa-font-weight-semibold);
|
||||
|
||||
&.plain {
|
||||
font-weight: var(--wa-font-weight-bold);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme-typography-icon {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--wa-space-xs);
|
||||
|
||||
h3,
|
||||
p {
|
||||
margin-block: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ icon: card
|
||||
---
|
||||
|
||||
```html {.example}
|
||||
<wa-card class="card-overview">
|
||||
<wa-card with-image with-footer 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"
|
||||
@@ -61,10 +61,10 @@ Basic cards aren't very exciting, but they can display any content you want them
|
||||
### Card with Header
|
||||
|
||||
Headers can be used to display titles and more.
|
||||
If using SSR, you need to also use the `ssr-slots` attribute to add a header to the card (if not, it is added automatically).
|
||||
If using SSR, you need to also use the `with-header` attribute to add a header to the card (if not, it is added automatically).
|
||||
|
||||
```html {.example}
|
||||
<wa-card ssr-slots="header" class="card-header">
|
||||
<wa-card with-header class="card-header">
|
||||
<div slot="header">
|
||||
Header Title
|
||||
<wa-icon-button name="gear" variant="solid" label="Settings"></wa-icon-button>
|
||||
@@ -97,10 +97,10 @@ If using SSR, you need to also use the `ssr-slots` attribute to add a header to
|
||||
### Card with Footer
|
||||
|
||||
Footers can be used to display actions, summaries, or other relevant content.
|
||||
If using SSR, you need to also use the `ssr-slots` attribute to add a footer to the card (if not, it is added automatically).
|
||||
If using SSR, you need to also use the `with-footer` attribute to add a footer to the card (if not, it is added automatically).
|
||||
|
||||
```html {.example}
|
||||
<wa-card ssr-slots="footer" class="card-footer">
|
||||
<wa-card with-footer class="card-footer">
|
||||
This card has a footer. You can put all sorts of things in it!
|
||||
|
||||
<div slot="footer">
|
||||
@@ -125,10 +125,10 @@ If using SSR, you need to also use the `ssr-slots` attribute to add a footer to
|
||||
### Images
|
||||
|
||||
Card images are displayed atop the card and will stretch to fit.
|
||||
If using SSR, you need to also use the `ssr-slots` attribute to add an image to the card (if not, it is added automatically).
|
||||
If using SSR, you need to also use the `with-image` attribute to add an image to the card (if not, it is added automatically).
|
||||
|
||||
```html {.example}
|
||||
<wa-card ssr-slots="image" class="card-image">
|
||||
<wa-card with-image class="card-image">
|
||||
<img
|
||||
slot="image"
|
||||
src="https://images.unsplash.com/photo-1547191783-94d5f8f6d8b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=80"
|
||||
@@ -150,7 +150,7 @@ Use the `size` attribute to change a card's size.
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-stack">
|
||||
<wa-card ssr-slots=footer size="small">
|
||||
<wa-card with-footer size="small">
|
||||
This is a small card.
|
||||
|
||||
<footer slot="footer" class="wa-flank">
|
||||
@@ -159,7 +159,7 @@ Use the `size` attribute to change a card's size.
|
||||
</footer>
|
||||
</wa-card>
|
||||
|
||||
<wa-card ssr-slots=footer size="medium">
|
||||
<wa-card with-footer size="medium">
|
||||
This is a medium card (default).
|
||||
|
||||
<footer slot="footer" class="wa-flank">
|
||||
@@ -168,7 +168,7 @@ Use the `size` attribute to change a card's size.
|
||||
</footer>
|
||||
</wa-card>
|
||||
|
||||
<wa-card ssr-slots=footer size="large">
|
||||
<wa-card with-footer size="large">
|
||||
This is a large card.
|
||||
|
||||
<footer slot="footer" class="wa-flank">
|
||||
|
||||
@@ -450,7 +450,7 @@ Use the `--aspect-ratio` custom property to customize the size of the carousel's
|
||||
const carousel = document.querySelector('wa-carousel.aspect-ratio');
|
||||
const aspectRatio = document.querySelector('wa-select[name="aspect"]');
|
||||
|
||||
aspectRatio.addEventlistener('change', () => {
|
||||
aspectRatio.addEventListener('change', () => {
|
||||
carousel.style.setProperty('--aspect-ratio', aspectRatio.value);
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -82,7 +82,7 @@ Use the `setCustomValidity()` method to set a custom validation message. This wi
|
||||
});
|
||||
|
||||
// Update validity on change
|
||||
checkbox.addEventlistener('change', () => {
|
||||
checkbox.addEventListener('change', () => {
|
||||
checkbox.setCustomValidity(checkbox.checked ? '' : errorMessage);
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@ icon: copy-button
|
||||
<wa-copy-button value="Web Awesome rocks!"></wa-copy-button>
|
||||
```
|
||||
|
||||
:::info
|
||||
Copy buttons use the browser's [`clipboard.writeText()`](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText) method, which requires a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) (HTTPS) in most browsers.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
### Custom Labels
|
||||
|
||||
@@ -52,8 +52,8 @@ wa-page {
|
||||
}
|
||||
|
||||
&[slot^='navigation'] {
|
||||
background: var(--wa-color-violet-80);
|
||||
color: var(--wa-color-violet-20);
|
||||
background: var(--wa-color-purple-80);
|
||||
color: var(--wa-color-purple-20);
|
||||
}
|
||||
|
||||
strong {
|
||||
|
||||
@@ -74,3 +74,15 @@ Add the `size` attribute to the [Radio Group](/docs/components/radio-group) to c
|
||||
<wa-radio value="3">Large 3</wa-radio>
|
||||
</wa-radio-group>
|
||||
```
|
||||
|
||||
### Hint
|
||||
|
||||
Add descriptive hint to a switch with the `hint` attribute. For hints that contain HTML, use the `hint` slot instead.
|
||||
|
||||
```html {.example}
|
||||
<wa-radio-group label="Select an option" name="a" value="1">
|
||||
<wa-radio value="1" hint="What should the user know about radio 1?">Option 1</wa-radio>
|
||||
<wa-radio value="2" hint="What should the user know about radio 2?">Option 2</wa-radio>
|
||||
<wa-radio value="3" hint="What should the user know about radio 3?">Option 3</wa-radio>
|
||||
</wa-radio-group>
|
||||
```
|
||||
|
||||
@@ -130,6 +130,15 @@ Note that multi-select options may wrap, causing the control to expand verticall
|
||||
|
||||
Use the `value` attribute to set the initial selection.
|
||||
|
||||
```html {.example}
|
||||
<wa-select value="option-1">
|
||||
<wa-option value="option-1">Option 1</wa-option>
|
||||
<wa-option value="option-2">Option 2</wa-option>
|
||||
<wa-option value="option-3">Option 3</wa-option>
|
||||
<wa-option value="option-4">Option 4</wa-option>
|
||||
</wa-select>
|
||||
```
|
||||
|
||||
When using `multiple`, the `value` _attribute_ uses space-delimited values to select more than one option. Because of this, `<wa-option>` values cannot contain spaces. If you're accessing the `value` _property_ through Javascript, it will be an array.
|
||||
|
||||
```html {.example}
|
||||
@@ -294,7 +303,7 @@ Remember that custom tags are rendered in a shadow root. To style them, you can
|
||||
return `
|
||||
<wa-tag removable>
|
||||
<wa-icon name="${name}" style="padding-inline-end: .5rem;"></wa-icon>
|
||||
${option.getTextLabel()}
|
||||
${option.label}
|
||||
</wa-tag>
|
||||
`;
|
||||
};
|
||||
|
||||
@@ -208,7 +208,7 @@ Try resizing the example below with each option and notice how the panels respon
|
||||
const splitPanel = container.querySelector('wa-split-panel');
|
||||
const select = container.querySelector('wa-select');
|
||||
|
||||
select.addEventlistener('change', () => (splitPanel.primary = select.value));
|
||||
select.addEventListener('change', () => (splitPanel.primary = select.value));
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ The `selection` attribute lets you change the selection behavior of the tree.
|
||||
const selectionMode = document.querySelector('#selection-mode');
|
||||
const tree = document.querySelector('.tree-selectable');
|
||||
|
||||
selectionMode.addEventlistener('change', () => {
|
||||
selectionMode.addEventListener('change', () => {
|
||||
tree.querySelectorAll('wa-tree-item').forEach(item => (item.selected = false));
|
||||
tree.selection = selectionMode.value;
|
||||
});
|
||||
|
||||
@@ -16,21 +16,21 @@ To use a theme, simply add a link to the theme's stylesheet to the `<head>` of y
|
||||
<link rel="stylesheet" href="{% cdnUrl 'styles/themes/awesome.css' %}" />
|
||||
```
|
||||
|
||||
You can [customize any theme](/docs/themes/creating) just with CSS — no preprocessor required. All design tokens are prefixed with `--wa-` to avoid collisions with other libraries or your own custom properties. Simply override any design token in your own stylesheet by scoping your styles to `:where(:root)`, `:host`, the class for the specific theme you want to override (if needed), and the class for the relevant color scheme (if needed). Here's an example that changes the default brand color to violet in light mode:
|
||||
You can [customize any theme](/docs/themes/creating) just with CSS — no preprocessor required. All design tokens are prefixed with `--wa-` to avoid collisions with other libraries or your own custom properties. Simply override any design token in your own stylesheet by scoping your styles to `:where(:root)`, `:host`, the class for the specific theme you want to override (if needed), and the class for the relevant color scheme (if needed). Here's an example that changes the default brand color to purple in light mode:
|
||||
|
||||
```css
|
||||
:where(:root),
|
||||
:host,
|
||||
.wa-light,
|
||||
.wa-dark .wa-invert {
|
||||
--wa-color-brand-fill-quiet: var(--wa-color-violet-95);
|
||||
--wa-color-brand-fill-normal: var(--wa-color-violet-90);
|
||||
--wa-color-brand-fill-loud: var(--wa-color-violet-50);
|
||||
--wa-color-brand-border-quiet: var(--wa-color-violet-90);
|
||||
--wa-color-brand-border-normal: var(--wa-color-violet-80);
|
||||
--wa-color-brand-border-loud: var(--wa-color-violet-60);
|
||||
--wa-color-brand-on-quiet: var(--wa-color-violet-40);
|
||||
--wa-color-brand-on-normal: var(--wa-color-violet-30);
|
||||
--wa-color-brand-fill-quiet: var(--wa-color-purple-95);
|
||||
--wa-color-brand-fill-normal: var(--wa-color-purple-90);
|
||||
--wa-color-brand-fill-loud: var(--wa-color-purple-50);
|
||||
--wa-color-brand-border-quiet: var(--wa-color-purple-90);
|
||||
--wa-color-brand-border-normal: var(--wa-color-purple-80);
|
||||
--wa-color-brand-border-loud: var(--wa-color-purple-60);
|
||||
--wa-color-brand-on-quiet: var(--wa-color-purple-40);
|
||||
--wa-color-brand-on-normal: var(--wa-color-purple-30);
|
||||
--wa-color-brand-on-loud: white;
|
||||
}
|
||||
```
|
||||
@@ -99,8 +99,8 @@ Parts allow you to style *any* standard CSS property, not just those exposed thr
|
||||
|
||||
<style>
|
||||
.gradient-button::part(base) {
|
||||
background: linear-gradient(217deg, var(--wa-color-indigo-50), var(--wa-color-violet-50), var(--wa-color-red-50));
|
||||
border: solid 1px var(--wa-color-violet-50);
|
||||
background: linear-gradient(217deg, var(--wa-color-indigo-50), var(--wa-color-purple-50), var(--wa-color-red-50));
|
||||
border: solid 1px var(--wa-color-purple-50);
|
||||
transition: transform 100ms, box-shadow 100ms;
|
||||
}
|
||||
|
||||
|
||||
47
docs/docs/experimental/clamped-colors.njk
Normal file
47
docs/docs/experimental/clamped-colors.njk
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Clamped brand tokens
|
||||
layout: block
|
||||
---
|
||||
|
||||
{% set tints = ['40-max', '50-max', '60-max', '40-min', '50-min', '60-min'] %}
|
||||
|
||||
{% for hue in hues %}
|
||||
<link href="/dist/styles/brand/{{ hue }}.css" rel="stylesheet">
|
||||
{% endfor %}
|
||||
|
||||
<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-brand-{{ hue }}">
|
||||
<th>{{ hue | capitalize }}</th>
|
||||
<td class="core-column">
|
||||
<div class="color swatch" style="background-color: var(--wa-color-brand); color: var(--wa-color-brand-on);">
|
||||
{{ palettes[paletteId][hue].maxChromaTint }}
|
||||
<wa-copy-button value="--wa-color-brand" copy-label="--wa-color-brand"></wa-copy-button>
|
||||
</div>
|
||||
</td>
|
||||
{% for tint in tints -%}
|
||||
<td>
|
||||
<div class="color swatch" style="background-color: var(--wa-color-brand-{{ tint }})">
|
||||
<wa-copy-button value="--wa-color-brand-{{ tint }}" copy-label="--wa-color-brand-{{ tint }}"></wa-copy-button>
|
||||
</div>
|
||||
</td>
|
||||
{%- endfor -%}
|
||||
</tr>
|
||||
{%- endfor %}
|
||||
</table>
|
||||
|
||||
<style>
|
||||
.core-column .color.swatch::before {
|
||||
counter-reset: key var(--wa-color-brand-key);
|
||||
content: counter(key);
|
||||
}
|
||||
</style>
|
||||
@@ -65,35 +65,6 @@ All Web Awesome components that get rendered for SSR will receive the `did-ssr`
|
||||
|
||||
This can help if you need some styling prior to the element connecting.
|
||||
|
||||
### The `ssr-slots` Attribute
|
||||
|
||||
When using certain slots in a component rendered through SSR, you may need to use the `ssr-slots` attribute
|
||||
to declare which slots have content.
|
||||
|
||||
E.g. when using a `<wa-card>` component without SSR, you can do this:
|
||||
|
||||
```html
|
||||
<wa-card>
|
||||
<img src="cat.jpg" slot="image">
|
||||
<header slot=header>Card Header</header>
|
||||
Card body
|
||||
<footer slot=footer>Card Footer</footer>
|
||||
</wa-card>
|
||||
```
|
||||
|
||||
However, when server-side rendering the very same card, you need to duplicate the information about what is in the slots in the `ssr-slots` attribute:
|
||||
|
||||
```html
|
||||
<wa-card ssr-slots="image header footer">
|
||||
<img src="cat.jpg" slot="image">
|
||||
<header slot=header>Card Header</header>
|
||||
Card body
|
||||
<footer slot=footer>Card Footer</footer>
|
||||
</wa-card>
|
||||
```
|
||||
|
||||
We are hoping to eventually be able to remove this requirement.
|
||||
|
||||
### Timing Issues
|
||||
|
||||
Before setting any properties on your frontend, it is important to first wait for the element to be defined and then wait for its first update to complete.
|
||||
@@ -145,4 +116,4 @@ Here are some known issues and things we're still working on.
|
||||
- `@shoelace-style/localize` (our localization library) has no way to set a language currently so it always falls back to `en`.
|
||||
- `<wa-icon>` has no fallback if there's no JS besides a blank `<svg>`. There's perhaps some backend mechanisms we can use to fetch. But requires altering APIs. Should also have a way to set height / widths, but we don't want to increase pain for SSR users.
|
||||
- `<wa-qr-code>` QR Code will not error on the backend and will render a blank canvas at the appropriate size, but will not render the canvas until the client component connects.
|
||||
- `setBasePath` and `kit codes` may need reconfiguring to work with SSR.
|
||||
- `setBasePath` and `kit codes` may need reconfiguring to work with SSR.
|
||||
@@ -576,10 +576,10 @@ hasOutline: false
|
||||
<wa-radio value="yellow"><span style="background-color:var(--wa-color-yellow-60);"></span></wa-radio>
|
||||
<wa-radio value="lime"><span style="background-color:var(--wa-color-lime-60);"></span></wa-radio>
|
||||
<wa-radio value="green"><span style="background-color:var(--wa-color-green-60);"></span></wa-radio>
|
||||
<wa-radio value="teal"><span style="background-color:var(--wa-color-teal-60);"></span></wa-radio>
|
||||
<wa-radio value="cyan"><span style="background-color:var(--wa-color-cyan-60);"></span></wa-radio>
|
||||
<wa-radio value="blue"><span style="background-color:var(--wa-color-blue-60);"></span></wa-radio>
|
||||
<wa-radio value="indigo"><span style="background-color:var(--wa-color-indigo-60);"></span></wa-radio>
|
||||
<wa-radio value="violet"><span style="background-color:var(--wa-color-violet-60);"></span></wa-radio>
|
||||
<wa-radio value="purple"><span style="background-color:var(--wa-color-purple-60);"></span></wa-radio>
|
||||
<wa-radio value="gray"><span style="background-color:var(--wa-color-gray-60);"></span></wa-radio>
|
||||
</wa-radio-group>
|
||||
</wa-details>
|
||||
@@ -803,7 +803,7 @@ hasOutline: false
|
||||
const queue = [];
|
||||
let inputTimeout;
|
||||
|
||||
variantInput.addEventlistener('change', () => {
|
||||
variantInput.addEventListener('change', () => {
|
||||
iconList.dataset.variant = variantInput.value;
|
||||
});
|
||||
|
||||
@@ -1084,10 +1084,10 @@ hasOutline: false
|
||||
el.classList.add(`wa-theme-${theme}-${colorMode}`);
|
||||
}
|
||||
|
||||
colorModeSelect.addEventlistener('change', setColorMode);
|
||||
colorModeSelect.addEventListener('change', setColorMode);
|
||||
|
||||
// Theme Switcher
|
||||
themeSelect.addEventlistener('change', event => {
|
||||
themeSelect.addEventListener('change', event => {
|
||||
const theme = event.target.value
|
||||
const newStylesheet = Object.assign(document.createElement("link"), {
|
||||
// This media: "print" allows us to lazy load the stylesheet then hot swap it on load.
|
||||
@@ -1132,14 +1132,14 @@ hasOutline: false
|
||||
});
|
||||
|
||||
// Color Palette
|
||||
colorSelect.addEventlistener('change', event => {
|
||||
colorSelect.addEventListener('change', event => {
|
||||
const colorPalette = event.target.value;
|
||||
|
||||
colorStylesheet.href = `/dist/styles/themes/color/${colorPalette}.css`;
|
||||
});
|
||||
|
||||
// Brand Color
|
||||
brandColor.addEventlistener('change', event => {
|
||||
brandColor.addEventListener('change', event => {
|
||||
const documentStyles = document.documentElement.style
|
||||
documentStyles.setProperty('--wa-color-primary-95', `var(--wa-color-${event.target.value}-95)`);
|
||||
documentStyles.setProperty('--wa-color-primary-90', `var(--wa-color-${event.target.value}-90)`);
|
||||
@@ -1223,7 +1223,7 @@ hasOutline: false
|
||||
})
|
||||
|
||||
// Pre-generated logos
|
||||
logoSelector.addEventlistener('change', event => {
|
||||
logoSelector.addEventListener('change', event => {
|
||||
const value = event.currentTarget.value
|
||||
|
||||
const projectLogo = previewContainer.querySelector("#project-logo");
|
||||
@@ -1279,7 +1279,7 @@ hasOutline: false
|
||||
})
|
||||
}
|
||||
|
||||
themeSelect.addEventlistener('change', setLogoIcons);
|
||||
themeSelect.addEventListener('change', setLogoIcons);
|
||||
|
||||
// Project Name
|
||||
container.querySelector('[name="project-name"]').addEventListener('input', event => {
|
||||
@@ -1293,7 +1293,7 @@ hasOutline: false
|
||||
});
|
||||
|
||||
// Heading text
|
||||
fontFamilyHeading.addEventlistener('change', event => {
|
||||
fontFamilyHeading.addEventListener('change', event => {
|
||||
let fontFamily;
|
||||
switch (event.target.value) {
|
||||
case 'assistant':
|
||||
@@ -1351,7 +1351,7 @@ hasOutline: false
|
||||
})
|
||||
|
||||
// Body text
|
||||
fontFamilyBody.addEventlistener('change', event => {
|
||||
fontFamilyBody.addEventListener('change', event => {
|
||||
let fontFamily;
|
||||
switch (event.target.value) {
|
||||
case 'assistant':
|
||||
@@ -1580,7 +1580,7 @@ hasOutline: false
|
||||
}
|
||||
|
||||
// Swaps icons to the preferred set for the selected theme
|
||||
themeSelect.addEventlistener('change', event => {
|
||||
themeSelect.addEventListener('change', event => {
|
||||
setPreferredIcons();
|
||||
showIconStyleOptions();
|
||||
syncLogoIcon();
|
||||
@@ -1599,13 +1599,13 @@ hasOutline: false
|
||||
});
|
||||
|
||||
// Changes available Icon Styles and swaps icons based on the selected Icon Family
|
||||
iconFamily.addEventlistener('change', event => {
|
||||
iconFamily.addEventListener('change', event => {
|
||||
useFaIcons();
|
||||
showIconStyleOptions();
|
||||
});
|
||||
|
||||
// Swaps icons based on the selected Icon Style
|
||||
iconStyle.addEventlistener('change', useFaIcons);
|
||||
iconStyle.addEventListener('change', useFaIcons);
|
||||
|
||||
|
||||
// Corners
|
||||
|
||||
@@ -9,19 +9,11 @@ elements:
|
||||
file: styles/native/radio.css
|
||||
---
|
||||
|
||||
<style>
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
label + label {
|
||||
margin-inline-start: var(--wa-space);
|
||||
}
|
||||
</style>
|
||||
|
||||
```html {.example}
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<div class="wa-cluster">
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Examples
|
||||
@@ -31,9 +23,11 @@ file: styles/native/radio.css
|
||||
To set the initial value and checked state, use the `checked` attribute on the corresponding radio.
|
||||
|
||||
```html {.example}
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3" checked> Option 3</label>
|
||||
<div class="wa-cluster">
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3" checked> Option 3</label>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Disabled
|
||||
@@ -41,9 +35,11 @@ To set the initial value and checked state, use the `checked` attribute on the c
|
||||
Use the `disabled` attribute to disable a radio.
|
||||
|
||||
```html {.example}
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2" disabled> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3"> Option 3</label>
|
||||
<div class="wa-cluster">
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2" disabled> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3"> Option 3</label>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Sizes
|
||||
@@ -51,27 +47,47 @@ Use the `disabled` attribute to disable a radio.
|
||||
Use the [size utilities](/docs/utilities/size) to change the radios' size.
|
||||
|
||||
```html {.example}
|
||||
<fieldset class="wa-size-s">
|
||||
<legend>Small</legend>
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3"> Option 3</label>
|
||||
<fieldset class="wa-size-s wa-cluster">
|
||||
<legend>Small</legend>
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3"> Option 3</label>
|
||||
</fieldset>
|
||||
|
||||
<br />
|
||||
<fieldset class="wa-size-m">
|
||||
<legend>Medium</legend>
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3"> Option 3</label>
|
||||
<fieldset class="wa-size-m wa-cluster">
|
||||
<legend>Medium</legend>
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3"> Option 3</label>
|
||||
</fieldset>
|
||||
|
||||
<br />
|
||||
|
||||
<fieldset class="wa-size-l">
|
||||
<legend>Large</legend>
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3"> Option 3</label>
|
||||
<fieldset class="wa-size-l wa-cluster">
|
||||
<legend>Large</legend>
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3"> Option 3</label>
|
||||
</fieldset>
|
||||
```
|
||||
|
||||
### Orientation
|
||||
|
||||
You can wrap native radios in a flex container to give them a horizontal or vertical orientation with even spacing. The convenience [`wa-cluster`](/docs/utilities/cluster) and [`wa-stack`](/docs/utilities/stack) utilities make this easy.
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-cluster">
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3" checked> Option 3</label>
|
||||
</div>
|
||||
```
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-stack">
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3" checked> Option 3</label>
|
||||
</div>
|
||||
```
|
||||
|
||||
27
docs/docs/palettes/data.json.njk
Normal file
27
docs/docs/palettes/data.json.njk
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
layout: null
|
||||
permalink: "/docs/palettes/data.json"
|
||||
eleventyExcludeFromCollections: true
|
||||
---
|
||||
{
|
||||
{% for palette in collections.palettes %}
|
||||
{% set paletteId = palette.fileSlug -%}
|
||||
{% set colors = palettes[paletteId] -%}
|
||||
"{{ paletteId }}": {
|
||||
"title": "{{ palette.data.title }}",
|
||||
"colors": {
|
||||
{% for hue, tints in colors -%}
|
||||
"{{ hue }}": {
|
||||
{% for tint, value in tints -%}
|
||||
{% if tint != "05" -%}
|
||||
{% set value = value.coords or value -%}
|
||||
{% set key = "05" if tint == "5" else tint -%}
|
||||
"{{ key }}": {{ value | dump | safe }}{{ ', ' if not loop.last }}
|
||||
{%- endif %}
|
||||
{% endfor -%}
|
||||
}{{ ', ' if not loop.last }}
|
||||
{% endfor %} {# end of hue #}
|
||||
}
|
||||
}{{ ', ' if not loop.last }} {# end of palette #}
|
||||
{% endfor %}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
title: Default
|
||||
description: This is the palette used in the default theme.
|
||||
order: 0
|
||||
---
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
{
|
||||
"layout": "palette.njk",
|
||||
"tags": ["palettes", "palette"],
|
||||
"wide": true,
|
||||
"eleventyComputed": {
|
||||
"snippet": ".wa-palette-{{ page.fileSlug }}",
|
||||
"icon": "palette"
|
||||
"icon": "palette",
|
||||
"file": "styles/color/{{ page.fileSlug }}.css"
|
||||
}
|
||||
}
|
||||
|
||||
164
docs/docs/palettes/tweak.css
Normal file
164
docs/docs/palettes/tweak.css
Normal file
@@ -0,0 +1,164 @@
|
||||
:root {
|
||||
--fa-sliders-simple: '\f1de';
|
||||
}
|
||||
|
||||
.core-column {
|
||||
position: relative;
|
||||
|
||||
> wa-dropdown {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
wa-dropdown > .color.swatch {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.decorated-slider {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
margin-block-end: var(--wa-space-xl);
|
||||
|
||||
wa-slider {
|
||||
grid-column: 1 / -1;
|
||||
--track-height: 1em;
|
||||
--track-color-inactive: transparent;
|
||||
--track-color-active: transparent;
|
||||
--thumb-color: var(--color-tweaked, var(--color));
|
||||
|
||||
&::part(base) {
|
||||
background: linear-gradient(to right in oklch, var(--color-1), var(--color-2));
|
||||
}
|
||||
}
|
||||
|
||||
[slot='label'] {
|
||||
min-height: 1.1lh;
|
||||
}
|
||||
|
||||
.clear-button {
|
||||
vertical-align: middle;
|
||||
font-size: var(--wa-font-size-xs);
|
||||
|
||||
&:not(.tweaked *) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.label-min,
|
||||
.label-max {
|
||||
font-size: var(--wa-font-size-xs);
|
||||
}
|
||||
|
||||
.label-min {
|
||||
grid-column: 1;
|
||||
margin-inline-start: 0.15em;
|
||||
}
|
||||
|
||||
.label-max {
|
||||
grid-column: 3;
|
||||
margin-inline-end: 0.1em;
|
||||
}
|
||||
}
|
||||
|
||||
.hue-shift-slider {
|
||||
--color-1: oklch(from var(--color) l c calc(h + var(--min, 0)));
|
||||
--color-2: oklch(from var(--color) l c calc(h + var(--max, 0)));
|
||||
}
|
||||
|
||||
.chroma-scale-slider {
|
||||
--color: var(--wa-color-brand);
|
||||
--color-1: oklch(from var(--color) l calc(c * var(--min)) h);
|
||||
--color-2: oklch(from var(--color) l calc(c * var(--max)) h);
|
||||
--color-tweaked: oklch(from var(--color) l calc(c * var(--chroma-scale)) h);
|
||||
}
|
||||
|
||||
.popup {
|
||||
background: var(--wa-color-surface-default);
|
||||
border: 1px solid var(--wa-color-surface-border);
|
||||
padding: var(--wa-space-xl);
|
||||
border-radius: var(--wa-border-radius-m);
|
||||
|
||||
code {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.color-scale {
|
||||
th {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td:not([data-hue='gray'] *) {
|
||||
--tweak-c: calc(c * var(--chroma-scale, 1));
|
||||
--tweak-h: calc(h + var(--hue-shift, 0));
|
||||
--color-tweaked: oklch(from var(--color) l var(--tweak-c) var(--tweak-h));
|
||||
--color-tweaked-no-chroma-scale: oklch(from var(--color) l c var(--tweak-h));
|
||||
--color-tweaked-no-hue-shift: oklch(from var(--color) l var(--tweak-c) h);
|
||||
|
||||
&:is([data-tint='90'], [data-tint='95']) {
|
||||
/* Work around https://bugs.webkit.org/show_bug.cgi?id=287637 */
|
||||
--color-tweaked: lch(from var(--color) l var(--tweak-c) var(--tweak-h));
|
||||
--color-tweaked-no-chroma-scale: lch(from var(--color) l c var(--tweak-h));
|
||||
--color-tweaked-no-hue-shift: lch(from var(--color) l var(--tweak-c) h);
|
||||
|
||||
/* outline: 1px dashed red; */
|
||||
}
|
||||
}
|
||||
|
||||
.color.swatch {
|
||||
--color-2: var(--color-tweaked);
|
||||
--color-2-height: 100%;
|
||||
|
||||
&:is(.tweaking *) {
|
||||
--color-2-height: 70%;
|
||||
|
||||
&:is(.tweaking-chroma *) {
|
||||
--color: var(--color-tweaked-no-chroma-scale);
|
||||
}
|
||||
|
||||
&:is(.tweaking-hue *) {
|
||||
--color: var(--color-tweaked-no-hue-shift);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tweak-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: var(--wa-space-s);
|
||||
opacity: var(--tweak-icon-opacity, 0%);
|
||||
}
|
||||
|
||||
.core-column:hover {
|
||||
--tweak-icon-opacity: 40%;
|
||||
}
|
||||
|
||||
&.tweaked .core-column {
|
||||
--tweak-icon-opacity: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
.tweaked-callout {
|
||||
padding: var(--wa-space-xs);
|
||||
padding-inline-start: var(--wa-space-m);
|
||||
align-items: center;
|
||||
|
||||
&:not(.tweaked-any *) {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&::part(message) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--wa-space-xs);
|
||||
}
|
||||
|
||||
wa-button:first-of-type {
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
}
|
||||
|
||||
[v-if='saved'] {
|
||||
display: none;
|
||||
}
|
||||
411
docs/docs/palettes/tweak.js
Normal file
411
docs/docs/palettes/tweak.js
Normal file
@@ -0,0 +1,411 @@
|
||||
// TODO move these to local imports
|
||||
import Color from 'https://colorjs.io/dist/color.js';
|
||||
import { createApp, nextTick } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
|
||||
import { cdnUrl, hueRanges, hues, Permalink, tints } from '../../assets/scripts/tweak.js';
|
||||
import { cssImport, cssLiteral, cssRule } from '../../assets/scripts/tweak/code.js';
|
||||
import { selectors, urls } from '../../assets/scripts/tweak/data.js';
|
||||
import Prism from '/assets/scripts/prism.js';
|
||||
|
||||
await Promise.all(['wa-slider'].map(tag => customElements.whenDefined(tag)));
|
||||
|
||||
// // Detect https://bugs.webkit.org/show_bug.cgi?id=287637
|
||||
// const SAFARI_OKLCH_BUG = (() => {
|
||||
// let dummy = document.createElement('div');
|
||||
// document.body.appendChild(dummy);
|
||||
// dummy.style.color = 'oklch(from #d5e0e6 l c h)';
|
||||
// let computedColor = getComputedStyle(dummy).color;
|
||||
// dummy.remove();
|
||||
// return computedColor.endsWith(' 0)');
|
||||
// })();
|
||||
|
||||
let allPalettes = await fetch('/docs/palettes/data.json').then(r => r.json());
|
||||
globalThis.allPalettes = allPalettes;
|
||||
|
||||
for (let palette in allPalettes) {
|
||||
for (let hue in allPalettes[palette].colors) {
|
||||
let scale = allPalettes[palette].colors[hue];
|
||||
for (let tint of tints) {
|
||||
let color = scale[tint];
|
||||
|
||||
if (Array.isArray(color)) {
|
||||
scale[tint] = new Color('oklch', color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let paletteAppSpec = {
|
||||
data() {
|
||||
let appRoot = document.querySelector('#palette-app');
|
||||
let paletteId = appRoot.dataset.paletteId;
|
||||
let palette = allPalettes[paletteId];
|
||||
|
||||
return {
|
||||
uid: undefined,
|
||||
paletteId,
|
||||
paletteTitle: palette.title,
|
||||
originalColors: palette.colors,
|
||||
permalink: new Permalink(),
|
||||
hueRanges,
|
||||
hueShifts: Object.fromEntries(hues.map(hue => [hue, 0])),
|
||||
chromaScale: 1,
|
||||
tweaking: {},
|
||||
saved: null,
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
// Read URL params and apply them. This facilitates permalinks.
|
||||
this.permalink.mapObject(this.hueShifts, {
|
||||
keyTo: key => key.replace(/-shift$/, ''),
|
||||
keyFrom: key => key + '-shift',
|
||||
valueFrom: value => (!value ? '' : Number(value)),
|
||||
valueTo: value => (!value ? 0 : Number(value)),
|
||||
});
|
||||
|
||||
if (location.search) {
|
||||
// Update from URL
|
||||
this.permalink.writeTo(this.hueShifts);
|
||||
|
||||
if (this.permalink.has('chroma-scale')) {
|
||||
this.chromaScale = Number(this.permalink.get('chroma-scale') || 1);
|
||||
}
|
||||
|
||||
if (this.permalink.has('uid')) {
|
||||
this.uid = Number(this.permalink.get('uid'));
|
||||
}
|
||||
|
||||
let palette = { id: this.paletteId, uid: this.uid, search: location.search };
|
||||
this.saved = sidebar.palette.getSaved(palette);
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
global() {
|
||||
return globalThis;
|
||||
},
|
||||
|
||||
tweaks() {
|
||||
return { hueShifts: this.hueShifts, chromaScale: this.chromaScale };
|
||||
},
|
||||
|
||||
isTweaked() {
|
||||
return Object.values(this.hueShifts).some(Boolean);
|
||||
},
|
||||
|
||||
code() {
|
||||
let ret = {};
|
||||
for (let language of ['html', 'css']) {
|
||||
let code = getPaletteCode(this.paletteId, this.tweaks, { language, cdnUrl });
|
||||
ret[language] = {
|
||||
raw: code,
|
||||
highlighted: Prism.highlight(code, Prism.languages[language], language),
|
||||
};
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
colors() {
|
||||
let ret = {};
|
||||
|
||||
for (let hue in this.originalColors) {
|
||||
let originalScale = this.originalColors[hue];
|
||||
let scale = (ret[hue] = {});
|
||||
let descriptors = Object.getOwnPropertyDescriptors(originalScale);
|
||||
Object.defineProperties(scale, {
|
||||
maxChromaTint: { ...descriptors.maxChromaTint, enumerable: false },
|
||||
maxChromaTintRaw: { ...descriptors.maxChromaTintRaw, enumerable: false },
|
||||
});
|
||||
|
||||
for (let tint of tints) {
|
||||
let oklch = originalScale[tint].coords.slice();
|
||||
|
||||
if (this.hueShifts[hue]) {
|
||||
oklch[2] += this.hueShifts[hue];
|
||||
}
|
||||
|
||||
if (this.chromaScale !== 1) {
|
||||
oklch[1] *= this.chromaScale;
|
||||
}
|
||||
|
||||
scale[tint] = new Color('oklch', oklch);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
tweaked() {
|
||||
return {
|
||||
chroma: this.chromaScale !== 1,
|
||||
hue: Object.values(this.hueShifts).some(Boolean),
|
||||
};
|
||||
},
|
||||
|
||||
tweaksHumanReadable() {
|
||||
let ret = {};
|
||||
|
||||
if (this.chromaScale !== 1) {
|
||||
ret.chromaScale = 'more ' + (this.chromaScale > 1 ? 'vibrant' : 'muted');
|
||||
}
|
||||
|
||||
for (let hue in this.hueShifts) {
|
||||
let shift = this.hueShifts[hue];
|
||||
|
||||
if (!shift) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let relHue = shift < 0 ? arrayPrevious(hues, hue) : arrayNext(hues, hue);
|
||||
let hueTweak =
|
||||
{
|
||||
red: 'redder',
|
||||
orange: 'oranger',
|
||||
indigo: 'more indigo',
|
||||
}[relHue] ?? relHue + 'er';
|
||||
|
||||
ret[hue] = hueTweak + ' ' + hue + 's';
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
originalContrasts() {
|
||||
let ret = {};
|
||||
|
||||
for (let hue in this.originalColors) {
|
||||
ret[hue] = {};
|
||||
|
||||
for (let tintBg of tints) {
|
||||
ret[hue][tintBg] = {};
|
||||
let bgColor = this.originalColors[hue][tintBg];
|
||||
|
||||
if (!bgColor || !bgColor.contrast) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let tintFg of tints) {
|
||||
let contrast = bgColor.contrast(this.originalColors[hue][tintFg], 'WCAG21');
|
||||
ret[hue][tintBg][tintFg] = contrast;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
contrasts() {
|
||||
let ret = {};
|
||||
|
||||
for (let hue in this.colors) {
|
||||
ret[hue] = {};
|
||||
|
||||
for (let tintBg in this.colors[hue]) {
|
||||
ret[hue][tintBg] = {};
|
||||
let bgColor = this.colors[hue][tintBg];
|
||||
|
||||
for (let tintFg in this.colors[hue]) {
|
||||
let fgColor = this.colors[hue][tintFg];
|
||||
let value = bgColor.contrast(fgColor, 'WCAG21');
|
||||
let original = this.originalContrasts[hue][tintBg][tintFg];
|
||||
ret[hue][tintBg][tintFg] = { value, original, bgColor, fgColor };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
hueShifts: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.permalink.readFrom(this.hueShifts);
|
||||
},
|
||||
},
|
||||
|
||||
chromaScale() {
|
||||
this.permalink.set('chroma-scale', this.chromaScale, 1);
|
||||
},
|
||||
|
||||
tweaks: {
|
||||
deep: true,
|
||||
async handler(value, oldValue) {
|
||||
await nextTick(); // must run after individual watchers
|
||||
|
||||
// Update page URL
|
||||
this.permalink.updateLocation();
|
||||
|
||||
if (this.saved) {
|
||||
this.save({ silent: true });
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
save({ silent } = {}) {
|
||||
let title = silent
|
||||
? (this.saved?.title ?? this.paletteTitle)
|
||||
: prompt('Palette title:', `${this.paletteTitle} (tweaked)`);
|
||||
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
|
||||
let uid = this.uid;
|
||||
|
||||
if (!uid) {
|
||||
this.uid = uid = sidebar.palette.getUid();
|
||||
|
||||
this.permalink.set('uid', uid);
|
||||
this.permalink.updateLocation();
|
||||
}
|
||||
|
||||
let palette = { title, id: this.paletteId, uid, search: location.search };
|
||||
sidebar.palette.save(palette, this.saved);
|
||||
this.saved = palette;
|
||||
},
|
||||
|
||||
rename() {
|
||||
if (!this.saved) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newTitle = prompt('New title:', this.saved.title);
|
||||
|
||||
if (!newTitle) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.saved.title = newTitle;
|
||||
sidebar.palette.save(this.saved);
|
||||
},
|
||||
|
||||
deleteSaved() {
|
||||
sidebar.palette.delete(this.saved);
|
||||
this.saved = null;
|
||||
},
|
||||
|
||||
reset() {
|
||||
for (let hue in this.hueShifts) {
|
||||
this.hueShifts[hue] = 0;
|
||||
}
|
||||
this.chromaScale = 1;
|
||||
},
|
||||
|
||||
removeTweak(param) {
|
||||
if (param === 'chromaScale') {
|
||||
this.chromaScale = 1;
|
||||
} else {
|
||||
this.hueShifts[param] = 0;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
directives: {
|
||||
// Like v-text, but doesn't complain if the element has content,
|
||||
// making it possible to use in a PE fashion, with the contents being the fallback
|
||||
content(el, { value, arg }) {
|
||||
if (!el.dataset.fallback) {
|
||||
// Store the original content as a fallback the first time
|
||||
el.dataset.fallback = el.textContent;
|
||||
}
|
||||
|
||||
if (value === '') {
|
||||
value = el.dataset.fallback;
|
||||
} else {
|
||||
if (arg === 'number') {
|
||||
value = Number(value).toLocaleString(undefined, { maximumSignificantDigits: 2 });
|
||||
}
|
||||
}
|
||||
|
||||
if (arg === 'html') {
|
||||
el.innerHTML = value;
|
||||
} else {
|
||||
el.textContent = value;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
compilerOptions: {
|
||||
isCustomElement: tag => tag.startsWith('wa-'),
|
||||
},
|
||||
};
|
||||
|
||||
function init() {
|
||||
globalThis.paletteApp?.unmount?.();
|
||||
globalThis.paletteApp = createApp(paletteAppSpec).mount('#palette-app');
|
||||
}
|
||||
|
||||
init();
|
||||
addEventListener('turbo:render', init);
|
||||
|
||||
export function getPaletteCode(paletteId, tweaks, options) {
|
||||
let palette = allPalettes[paletteId].colors;
|
||||
|
||||
let imports = [];
|
||||
|
||||
if (paletteId) {
|
||||
imports.push(urls.palette(paletteId));
|
||||
}
|
||||
|
||||
let css = '';
|
||||
|
||||
if (tweaks) {
|
||||
let { hueShifts, chromaScale = 1 } = tweaks;
|
||||
let declarations = [];
|
||||
|
||||
if (hueShifts || chromaScale !== 1) {
|
||||
for (let hue in hueShifts) {
|
||||
let shift = hueShifts[hue];
|
||||
|
||||
if ((!shift && chromaScale === 1) || hue === 'orange') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let scale = palette[hue];
|
||||
|
||||
for (let tint of ['05', '10', '20', '30', '40', '50', '60', '70', '80', '90', '95']) {
|
||||
let color = scale[tint];
|
||||
|
||||
if (Array.isArray(color)) {
|
||||
color = new Color('oklch', coords);
|
||||
} else {
|
||||
color = color.clone();
|
||||
}
|
||||
color.set({ h: h => h + shift, c: c => c * chromaScale });
|
||||
let stringified = color.toString({ format: color.inGamut('srgb') ? 'hex' : undefined });
|
||||
|
||||
declarations.push(`--wa-color-${hue}-${tint}: ${stringified};`);
|
||||
}
|
||||
|
||||
declarations.push('');
|
||||
}
|
||||
}
|
||||
|
||||
if (declarations.length > 0) {
|
||||
css += cssRule(selectors.palette(paletteId), declarations);
|
||||
}
|
||||
}
|
||||
|
||||
let ret = imports.map(url => cssImport(url, options)).join('\n');
|
||||
|
||||
if (css) {
|
||||
ret += `\n\n${cssLiteral(css, options)}`;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function arrayNext(array, element) {
|
||||
let index = array.indexOf(element);
|
||||
return array[(index + 1) % array.length];
|
||||
}
|
||||
|
||||
function arrayPrevious(array, element) {
|
||||
let index = array.indexOf(element);
|
||||
return array[(index - 1 + array.length) % array.length];
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Changelog
|
||||
description: Changes to each version of the project are documented here.
|
||||
layout: page
|
||||
layout: page-outline
|
||||
---
|
||||
|
||||
Web Awesome follows [Semantic Versioning](https://semver.org/). Breaking changes in components with the <wa-badge variant="brand" pill>Stable</wa-badge> badge will not be accepted until the next major version. As such, all contributions must consider the project's roadmap and take this into consideration. Features that are deemed no longer necessary will be deprecated but not removed.
|
||||
@@ -12,6 +12,47 @@ Components with the <wa-badge variant="warning" pill>Experimental</wa-badge> bad
|
||||
During the alpha period, things might break! We take breaking changes very seriously, but sometimes they're necessary to make the final product that much better. We appreciate your patience!
|
||||
:::
|
||||
|
||||
## Next
|
||||
|
||||
### Color Palettes
|
||||
|
||||
- Added a `pink` scale to all color palettes
|
||||
- Fixed an incorrect CSS value in `<wa-select>`'s expand icon
|
||||
- Tweaked hues of all color palettes to make them more distinct and make their hues more intentional
|
||||
|
||||
### Design Tokens
|
||||
|
||||
- Added `--wa-color-[hue]` tokens with the "core" color of each scale, regardless of which tint it lives on.
|
||||
You can find them in the first column of each color palette.
|
||||
|
||||
### Themes
|
||||
|
||||
- Updated Active, Glossy, Playful, and Premium themes so that `--wa-color-brand-fill-loud` uses the core color of the chosen brand color, regardless of tint.
|
||||
- You can now override the brand color of any theme with any of the 9 hues supported.
|
||||
- Improved UI for theme remixing, with previews and generated copyable code snippets.
|
||||
|
||||
### Components
|
||||
|
||||
- Various `<wa-radio>` improvements:
|
||||
- Dropped the `base` part. It can now be styled by directly applying CSS to the element itself.
|
||||
- Added `hint` attribute and corresponding slot.
|
||||
- Various `<wa-select>` improvements:
|
||||
- Added the `tag` part (and associated exported parts) to `<wa-select>` to allow targeting the tag that shows when more than the max number of visible items have been selected
|
||||
- Fixed a bug that prevented the placeholder color from being customized with the `--wa-form-control-placeholder-color` token
|
||||
- Dropped the `base` part from `<wa-option>` for easier styling. CSS can now be applied directly to the element itself.
|
||||
- Various `<wa-card>` improvements:
|
||||
- Fixed a bug where child elements did not have correct rounding when headers and footers were absent.
|
||||
- Re-introduced `--border-color` so that the card itself can have a different border color than its inner borders.
|
||||
- Fixed a bug that prevented slots from showing automatically without `with-` attributes
|
||||
- Fixed a bug in `<wa-select>` that prevented the description from being read by screen readers
|
||||
|
||||
|
||||
### 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.
|
||||
|
||||
1
docs/docs/themes/active.md
vendored
1
docs/docs/themes/active.md
vendored
@@ -4,4 +4,5 @@ description: Energetic and tactile, always in motion.
|
||||
isPro: true
|
||||
tags: pro
|
||||
palette: rudimentary
|
||||
brand: green
|
||||
---
|
||||
|
||||
1
docs/docs/themes/awesome.md
vendored
1
docs/docs/themes/awesome.md
vendored
@@ -3,4 +3,5 @@ title: Awesome
|
||||
description: Punchy and vibrant, the rockstar of themes.
|
||||
order: 0.2
|
||||
palette: bright
|
||||
brand: blue
|
||||
---
|
||||
|
||||
1
docs/docs/themes/brutalist.md
vendored
1
docs/docs/themes/brutalist.md
vendored
@@ -4,4 +4,5 @@ description: Sharp, square, and unapologetically bold.
|
||||
isPro: true
|
||||
tags: pro
|
||||
palette: default
|
||||
brand: blue
|
||||
---
|
||||
|
||||
1
docs/docs/themes/default.md
vendored
1
docs/docs/themes/default.md
vendored
@@ -3,4 +3,5 @@ title: Default
|
||||
description: Your trusty companion, like a perfectly broken-in pair of jeans.
|
||||
order: 0
|
||||
palette: default
|
||||
brand: blue
|
||||
---
|
||||
|
||||
40
docs/docs/themes/demo.njk
vendored
40
docs/docs/themes/demo.njk
vendored
@@ -11,7 +11,6 @@ eleventyComputed:
|
||||
forceTheme: "{{ theme.fileSlug }}"
|
||||
---
|
||||
|
||||
<link rel="stylesheet" href="/dist/styles/themes/{{ theme.fileSlug }}.css" />
|
||||
<link rel="stylesheet" href="/docs/themes/showcase.css" />
|
||||
|
||||
{% set content %}
|
||||
@@ -34,35 +33,46 @@ eleventyComputed:
|
||||
</div>
|
||||
</wa-image-comparer>
|
||||
|
||||
<script>
|
||||
<script type="module">
|
||||
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);
|
||||
let script = document.currentScript;
|
||||
const stylesheetURLs = {
|
||||
colors: id => `/dist/styles/themes/${ id }/color.css`,
|
||||
palette: id => `/dist/styles/color/${ id }.css`,
|
||||
typography: id => `/dist/styles/themes/${ id }/typography.css`
|
||||
params = Object.fromEntries(params.entries());
|
||||
|
||||
const docsURLs = {
|
||||
colors: '/docs/themes/',
|
||||
palette: '/docs/palettes/',
|
||||
typography: '/docs/themes/'
|
||||
};
|
||||
const icons = {
|
||||
colors: 'palette',
|
||||
palette: 'swatchbook',
|
||||
brand: 'droplet',
|
||||
typography: 'font-case'
|
||||
}
|
||||
};
|
||||
|
||||
for (let link of document.querySelectorAll('link.mix-and-match')) {
|
||||
link.remove();
|
||||
}
|
||||
|
||||
let msgs = [];
|
||||
let code = getThemeCode("{{ theme.fileSlug }}", params, {attributes: 'class="mix-and-match"'});
|
||||
document.head.insertAdjacentHTML('beforeend', code);
|
||||
|
||||
for (let name in stylesheetURLs) {
|
||||
if (params.get(name)) {
|
||||
let url = stylesheetURLs[name](params.get(name));
|
||||
script.insertAdjacentHTML('afterend', `<link rel="stylesheet" href="${ url }" class="mix-and-match" />`);
|
||||
let docsURL = (name === 'palette' ? '/docs/palettes/' : '/docs/themes/') + params.get(name) + '/';
|
||||
let title = params.get(name).replace(/^[a-z]/g, c => c.toUpperCase());
|
||||
let override = params[name];
|
||||
if (override) {
|
||||
let docsURL = docsURLs[name] ? docsURLs[name] + override + '/' : '';
|
||||
let title = override.replace(/^[a-z]/g, c => c.toUpperCase());
|
||||
|
||||
if (docsURL) {
|
||||
title = `<a href="${ docsURL }">${ title }</a>`;
|
||||
}
|
||||
|
||||
let icon = icons[name];
|
||||
msgs.push(`<wa-icon name="${icon}" variant="regular"></wa-icon> <a href="${ docsURL }">${ title }</a>`);
|
||||
msgs.push(`<wa-icon name="${icon}" variant="regular"></wa-icon> ${ title }`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +80,7 @@ function updateTheme() {
|
||||
p.hidden = msgs.length === 0;
|
||||
if (msgs.length) {
|
||||
let icon =
|
||||
p.innerHTML = `<strong><wa-icon name="merge"></wa-icon> Remixed</strong> ` + msgs.map(msg => `<wa-badge appearance=outlined>
|
||||
p.innerHTML = `<strong><wa-icon name="arrows-rotate"></wa-icon> Remixed</strong> ` + msgs.map(msg => `<wa-badge appearance=outlined>
|
||||
${ msg }</wa-badge>`).join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
1
docs/docs/themes/glossy.md
vendored
1
docs/docs/themes/glossy.md
vendored
@@ -4,4 +4,5 @@ description: Bustling with plenty of luster and shine.
|
||||
isPro: true
|
||||
tags: pro
|
||||
palette: elegant
|
||||
brand: indigo
|
||||
---
|
||||
|
||||
1
docs/docs/themes/matter.md
vendored
1
docs/docs/themes/matter.md
vendored
@@ -4,6 +4,7 @@ description: Digital design inspired by the real world.
|
||||
isPro: true
|
||||
tags: pro
|
||||
palette: mild
|
||||
brand: indigo
|
||||
---
|
||||
|
||||
Set the page theme to "{{ title }}" from the top right to preview the following examples.
|
||||
|
||||
1
docs/docs/themes/playful.md
vendored
1
docs/docs/themes/playful.md
vendored
@@ -4,4 +4,5 @@ description: Cheerful and engaging, like a playground on screen.
|
||||
isPro: true
|
||||
tags: pro
|
||||
palette: rudimentary
|
||||
brand: purple
|
||||
---
|
||||
|
||||
1
docs/docs/themes/premium.md
vendored
1
docs/docs/themes/premium.md
vendored
@@ -4,4 +4,5 @@ description: The ultimate in sophistication and style.
|
||||
isPro: true
|
||||
tags: pro
|
||||
palette: anodized
|
||||
brand: cyan
|
||||
---
|
||||
|
||||
127
docs/docs/themes/remix.css
vendored
Normal file
127
docs/docs/themes/remix.css
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
#mix_and_match {
|
||||
margin-block-end: var(--wa-space-2xl);
|
||||
|
||||
&::part(content) {
|
||||
display: flex;
|
||||
gap: var(--wa-space-xl);
|
||||
padding-block-start: var(--wa-space-m);
|
||||
}
|
||||
|
||||
> [slot='summary'] {
|
||||
margin: 0;
|
||||
|
||||
wa-icon:first-of-type {
|
||||
vertical-align: -0.15em;
|
||||
color: var(--wa-color-text-quiet);
|
||||
}
|
||||
|
||||
wa-icon#what-is-remixing {
|
||||
vertical-align: -0.1em;
|
||||
font-size: var(--wa-font-size-s);
|
||||
color: var(--wa-color-text-quiet);
|
||||
}
|
||||
}
|
||||
|
||||
wa-select {
|
||||
&::part(label) {
|
||||
margin-block-end: 0;
|
||||
}
|
||||
}
|
||||
|
||||
wa-select:has(> wa-option > wa-card) {
|
||||
&::part(listbox) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
width: min(90vw, 800px);
|
||||
}
|
||||
|
||||
&:state(blank)::part(display-input) {
|
||||
font-style: italic;
|
||||
color: var(--wa-color-text-quiet);
|
||||
}
|
||||
}
|
||||
|
||||
wa-option:has(> wa-card) {
|
||||
position: relative;
|
||||
padding: var(--wa-space-smaller);
|
||||
|
||||
&::part(checked-icon) {
|
||||
position: absolute;
|
||||
inset-block-start: calc(var(--wa-space-smaller) - 0.5em);
|
||||
inset-inline-end: calc(var(--wa-space-smaller) - 0.5em);
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
line-height: 1em;
|
||||
padding: 0.4em;
|
||||
border-radius: var(--wa-border-radius-circle);
|
||||
text-align: center;
|
||||
background: var(--wa-color-brand-fill-loud);
|
||||
color: var(--wa-color-brand-on-loud);
|
||||
font-size: var(--wa-font-size-xs);
|
||||
}
|
||||
|
||||
wa-card {
|
||||
--spacing: var(--wa-space-smaller);
|
||||
|
||||
&::part(body) {
|
||||
background: var(--wa-color-neutral-fill-quiet);
|
||||
padding-block: var(--wa-space-s);
|
||||
}
|
||||
}
|
||||
|
||||
&:state(current),
|
||||
&:state(hover),
|
||||
&:state(selected),
|
||||
&:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
wa-card {
|
||||
border-color: var(--wa-color-brand-border-loud);
|
||||
box-shadow: 0 0 0 var(--wa-border-width-m) var(--wa-color-brand-border-normal);
|
||||
}
|
||||
}
|
||||
|
||||
&[aria-selected='true'] {
|
||||
wa-card {
|
||||
--border-color: var(--wa-color-brand-border-loud);
|
||||
box-shadow: 0 0 0 var(--wa-border-width-m) var(--wa-color-brand-border-loud);
|
||||
|
||||
&::part(body) {
|
||||
background: var(--wa-color-brand-fill-quiet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected-swatch,
|
||||
wa-select[name='brand'] wa-option::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 1.2em;
|
||||
aspect-ratio: 1;
|
||||
flex: none;
|
||||
border-radius: var(--wa-border-radius-m);
|
||||
background: var(--color);
|
||||
border: 1px solid var(--wa-color-surface-default);
|
||||
}
|
||||
|
||||
wa-select[name='brand'] wa-option {
|
||||
white-space: nowrap;
|
||||
|
||||
&::before {
|
||||
width: 1em;
|
||||
margin-inline: var(--wa-space-xs);
|
||||
}
|
||||
|
||||
&::part(checked-icon) {
|
||||
order: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#test_select wa-option:state(selected) {
|
||||
outline: 2px solid red;
|
||||
background: yellow;
|
||||
}
|
||||
150
docs/docs/themes/remix.js
vendored
Normal file
150
docs/docs/themes/remix.js
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
import Prism from '/assets/scripts/prism.js';
|
||||
import { cdnUrl, getThemeCode, Permalink } from '/assets/scripts/tweak.js';
|
||||
await Promise.all(['wa-select', 'wa-option', 'wa-details'].map(tag => customElements.whenDefined(tag)));
|
||||
|
||||
const domChange = document.startViewTransition ? document.startViewTransition.bind(document) : fn => fn();
|
||||
|
||||
let selects, data, codeSnippets;
|
||||
|
||||
let computed = {
|
||||
get isRemixed() {
|
||||
return Object.values(data.params).filter(Boolean).length > 0;
|
||||
},
|
||||
get palette() {
|
||||
return data.params.palette || data.defaultParams.palette;
|
||||
},
|
||||
get brand() {
|
||||
return data.params.brand || data.defaultParams.brand;
|
||||
},
|
||||
};
|
||||
|
||||
function selectsChanged(event) {
|
||||
data.params[event.target.name] = event.target.value;
|
||||
render(event.target.name);
|
||||
}
|
||||
|
||||
function init() {
|
||||
selects = Object.fromEntries(
|
||||
[...document.querySelectorAll('#mix_and_match wa-select')].map(select => [select.getAttribute('name'), select]),
|
||||
);
|
||||
|
||||
codeSnippets = document.querySelector('#usage ~ wa-tab-group.import-stylesheet-code:first-of-type');
|
||||
codeSnippets = {
|
||||
html: codeSnippets.querySelector('code.language-html'),
|
||||
css: codeSnippets.querySelector('code.language-css'),
|
||||
};
|
||||
|
||||
data = {
|
||||
baseTheme: wa_data.baseTheme,
|
||||
themes: wa_data.themes,
|
||||
palettes: wa_data.palettes,
|
||||
defaultParams: {
|
||||
colors: '',
|
||||
get palette() {
|
||||
let colors = data.params.colors || data.baseTheme;
|
||||
return data.themes[colors].palette;
|
||||
},
|
||||
get brand() {
|
||||
let colors = data.params.colors || data.baseTheme;
|
||||
return data.themes[colors].brand;
|
||||
},
|
||||
typography: '',
|
||||
},
|
||||
params: { colors: '', palette: '', brand: '', typography: '' },
|
||||
urlParams: new Permalink(),
|
||||
};
|
||||
|
||||
data.urlParams.mapObject(data.params);
|
||||
data.urlParams.writeTo(data.params);
|
||||
|
||||
if (computed.isRemixed) {
|
||||
// Start with the remixing UI open if the theme has been remixed
|
||||
mix_and_match.setAttribute('open', '');
|
||||
mix_and_match.open = true;
|
||||
}
|
||||
|
||||
for (let name in selects) {
|
||||
selects[name].addEventListener('change', selectsChanged);
|
||||
}
|
||||
|
||||
Promise.all(Object.values(selects).map(select => select.updateComplete)).then(() => render());
|
||||
|
||||
return { selects, codeSnippets, data, computed, render };
|
||||
}
|
||||
|
||||
globalThis.remixApp = init();
|
||||
|
||||
// Async load CSS for other themes *before* current theme stylesheet
|
||||
let themeStylesheet = document.querySelector('#theme-stylesheet');
|
||||
|
||||
for (const theme in data.themes) {
|
||||
themeStylesheet.insertAdjacentHTML(
|
||||
'beforebegin',
|
||||
`<link rel="preload" as="style" href="/dist/styles/themes/${theme}/color.css" onload="this.rel = 'stylesheet'" />
|
||||
<link rel="preload" as="style" href="/dist/styles/themes/${theme}/typography.css" onload="this.rel = 'stylesheet'" />`,
|
||||
);
|
||||
}
|
||||
|
||||
for (const palette in data.palettes) {
|
||||
themeStylesheet.insertAdjacentHTML(
|
||||
'beforebegin',
|
||||
`<link rel="preload" as="style" href="/dist/styles/color/${palette}.css" onload="this.rel = 'stylesheet'" />`,
|
||||
);
|
||||
}
|
||||
|
||||
function setDefault(select, value) {
|
||||
let oldDefaultOption = select.querySelector(`wa-option[value=""]:not([data-id="${value}"])`);
|
||||
let newDefaultOption = select.querySelector(`wa-option[value="${value}"]`);
|
||||
|
||||
if (oldDefaultOption) {
|
||||
oldDefaultOption.value = oldDefaultOption.dataset.id;
|
||||
}
|
||||
|
||||
if (newDefaultOption) {
|
||||
newDefaultOption.dataset.id ??= newDefaultOption.value;
|
||||
newDefaultOption.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function render(changedAspect) {
|
||||
let url = new URL(demo.src);
|
||||
|
||||
if (!changedAspect || changedAspect === 'colors') {
|
||||
// Update the default palette when the theme colors change to the default palette of that theme
|
||||
setDefault(selects.palette, data.defaultParams.palette);
|
||||
setDefault(selects.brand, data.defaultParams.brand);
|
||||
}
|
||||
|
||||
let brand = data.params.brand || data.defaultParams.brand;
|
||||
selects.brand.style.setProperty('--color', `var(--wa-color-${brand})`);
|
||||
selects.brand.className = `wa-palette-${computed.palette}`;
|
||||
|
||||
for (let aspect in data.params) {
|
||||
let value = data.params[aspect];
|
||||
selects[aspect].value = value;
|
||||
}
|
||||
|
||||
data.urlParams.readFrom(data.params);
|
||||
|
||||
// Update demo URL
|
||||
domChange(() => {
|
||||
url.search = data.urlParams;
|
||||
demo.src = url;
|
||||
return new Promise(resolve => (demo.onload = resolve));
|
||||
});
|
||||
|
||||
// Update page URL
|
||||
data.urlParams.updateLocation();
|
||||
|
||||
// Update code snippets
|
||||
for (let language in codeSnippets) {
|
||||
let codeSnippet = codeSnippets[language];
|
||||
let code = getThemeCode(data.baseTheme, data.params, { language, cdnUrl });
|
||||
codeSnippet.textContent = code;
|
||||
Prism.highlightElement(codeSnippet);
|
||||
}
|
||||
}
|
||||
|
||||
addEventListener('turbo:render', event => {
|
||||
remixApp = init();
|
||||
});
|
||||
13
docs/docs/themes/showcase.css
vendored
13
docs/docs/themes/showcase.css
vendored
@@ -14,7 +14,18 @@ body,
|
||||
color: var(--wa-color-text-quiet);
|
||||
|
||||
wa-icon {
|
||||
vertical-align: middle;
|
||||
vertical-align: -0.15em;
|
||||
}
|
||||
|
||||
> strong {
|
||||
margin-inline-end: var(--wa-space-2xs);
|
||||
}
|
||||
|
||||
wa-badge {
|
||||
> a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1
docs/docs/themes/tailspin.md
vendored
1
docs/docs/themes/tailspin.md
vendored
@@ -4,4 +4,5 @@ description: Like a bird in flight, guiding you from there to here.
|
||||
isPro: true
|
||||
tags: pro
|
||||
palette: vogue
|
||||
brand: indigo
|
||||
---
|
||||
|
||||
6
docs/docs/themes/themes.json
vendored
6
docs/docs/themes/themes.json
vendored
@@ -1,5 +1,9 @@
|
||||
{
|
||||
"layout": "theme.njk",
|
||||
"wide": true,
|
||||
"tags": ["themes", "theme"]
|
||||
"tags": ["themes", "theme"],
|
||||
"brand": "blue",
|
||||
"eleventyComputed": {
|
||||
"file": "styles/themes/{{ page.fileSlug }}.css"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ You can use these numbers to ensure accessible color contrast per [WCAG 2.1 succ
|
||||
|
||||
Each Web Awesome palette defines seven literal colors each with 11 lightness values using the format `--wa-color-{hue}-{tint}`.
|
||||
|
||||
{% for hue in ["red", "yellow", "green", "teal", "blue", "indigo", "violet", "gray"] -%}
|
||||
{% 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"] -%}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { dirname, join, relative } from 'path';
|
||||
import process from 'process';
|
||||
import copy from 'recursive-copy';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { preprocessStylesheet } from './preprocess-css.js';
|
||||
import { cdnDir, distDir, docsDir, rootDir, runScript, siteDir } from './utils.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
@@ -36,6 +37,7 @@ async function buildAll() {
|
||||
await generateManifest();
|
||||
await generateReactWrappers();
|
||||
await generateTypes();
|
||||
await preprocessStyles();
|
||||
await generateStyles();
|
||||
|
||||
// copy everything to unbundled before we generate bundles.
|
||||
@@ -105,6 +107,23 @@ function generateReactWrappers() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate preprocessed CSS
|
||||
*/
|
||||
async function preprocessStyles() {
|
||||
const preprocessedCSSFiles = await globby(join(rootDir, 'src/styles/**/*.css.njk'));
|
||||
|
||||
if (preprocessedCSSFiles.length > 0) {
|
||||
spinner.start('Preprocessing stylesheets');
|
||||
|
||||
for (let filePath of preprocessedCSSFiles) {
|
||||
await preprocessStylesheet(filePath);
|
||||
}
|
||||
|
||||
spinner.succeed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies theme stylesheets to the dist.
|
||||
*/
|
||||
@@ -371,6 +390,7 @@ if (isDeveloping) {
|
||||
try {
|
||||
const isTestFile = filename.includes('.test.ts');
|
||||
const isCssStylesheet = filename.includes('.css');
|
||||
const isPreprocessedStylesheet = filename.endsWith('.css.njk');
|
||||
const isComponent =
|
||||
filename.includes('components/') && filename.includes('.ts') && !isCssStylesheet && !isTestFile;
|
||||
|
||||
@@ -381,6 +401,10 @@ if (isDeveloping) {
|
||||
|
||||
await regenerateBundle();
|
||||
|
||||
if (isPreprocessedStylesheet || filename.endsWith('src/styles/data.js')) {
|
||||
await preprocessStyles();
|
||||
}
|
||||
|
||||
// Copy stylesheets when CSS files change
|
||||
if (isCssStylesheet) {
|
||||
await generateStyles();
|
||||
|
||||
61
scripts/preprocess-css.js
Normal file
61
scripts/preprocess-css.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
import nunjucks from 'nunjucks';
|
||||
import * as prettier from 'prettier';
|
||||
import prettierConfig from '../prettier.config.js';
|
||||
import * as globalData from '../src/styles/data.js';
|
||||
|
||||
const prelude = inputFilename => `/* DO NOT EDIT THIS FILE. It is generated from "${inputFilename}" */`;
|
||||
|
||||
let env = nunjucks.configure({ autoescape: false });
|
||||
let filenameVariables = Object.keys(globalData)
|
||||
.filter(key => key.endsWith('s'))
|
||||
.map(key => key.slice(0, -1));
|
||||
let filenameVariablesRegex = RegExp(`\\{\\{\\s*(${filenameVariables.join('|')})\\s*\\}\\}`, 'g');
|
||||
|
||||
export async function preprocessStylesheet(inputPath) {
|
||||
const content = await readFile(inputPath, 'utf-8');
|
||||
let outputPath = inputPath.replace(/\.njk$/, '');
|
||||
let filename = outputPath.split('/').pop();
|
||||
|
||||
if (filenameVariablesRegex.test(filename)) {
|
||||
// NOTE only supports a single variable right now
|
||||
filenameVariablesRegex.lastIndex = 0;
|
||||
let ret = [];
|
||||
|
||||
for (let match of filename.matchAll(filenameVariablesRegex)) {
|
||||
let variable = match[1];
|
||||
let values = globalData[variable + 's'];
|
||||
|
||||
for (let value of values) {
|
||||
let data = { [variable]: value };
|
||||
let css = await renderCSS(content, { data, inputPath, outputPath });
|
||||
|
||||
// Now use Nunjucks *again*, to render the actual filename
|
||||
let localOutputFilePath = env.renderString(outputPath, data);
|
||||
|
||||
ret.push(writeFile(localOutputFilePath, css, 'utf-8'));
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(ret);
|
||||
} else {
|
||||
let css = await renderCSS(content, { inputPath, outputPath });
|
||||
return writeFile(outputPath, css, 'utf-8');
|
||||
}
|
||||
|
||||
// TODO add generated files to .gitignore?
|
||||
}
|
||||
|
||||
export async function renderCSS(content, { data, inputPath, outputPath } = {}) {
|
||||
let inputFilename = inputPath.split('/').pop();
|
||||
data = data ? { ...globalData, ...data } : globalData;
|
||||
let css = env.renderString(content, data);
|
||||
|
||||
if (prelude) {
|
||||
css = prelude(inputFilename) + '\n' + css;
|
||||
}
|
||||
|
||||
css = await prettier.format(css, { ...prettierConfig, filepath: outputPath });
|
||||
|
||||
return css;
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
--spacing: var(--wa-space);
|
||||
--border-width: var(--wa-panel-border-width);
|
||||
--border-color: var(--wa-color-surface-border);
|
||||
--border-radius: var(--wa-panel-border-radius);
|
||||
|
||||
--inner-border-radius: calc(var(--border-radius) - var(--border-width));
|
||||
@@ -12,7 +13,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--wa-color-surface-default);
|
||||
border-color: var(--wa-color-surface-border);
|
||||
border-color: var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
border-style: var(--wa-panel-border-style);
|
||||
box-shadow: var(--wa-shadow-s);
|
||||
@@ -20,12 +21,20 @@
|
||||
color: var(--wa-color-text-normal);
|
||||
}
|
||||
|
||||
/* Take care of top and bottom radii */
|
||||
.image,
|
||||
:host(:not([ssr-slots~='image'])) .header {
|
||||
:host(:not([with-image])) .header,
|
||||
:host(:not([with-image], [with-header])) .body {
|
||||
border-start-start-radius: var(--inner-border-radius);
|
||||
border-start-end-radius: var(--inner-border-radius);
|
||||
}
|
||||
|
||||
:host(:not([with-footer])) .body,
|
||||
.footer {
|
||||
border-end-start-radius: var(--inner-border-radius);
|
||||
border-end-end-radius: var(--inner-border-radius);
|
||||
}
|
||||
|
||||
.image {
|
||||
display: flex;
|
||||
|
||||
@@ -39,7 +48,9 @@
|
||||
|
||||
.header {
|
||||
display: block;
|
||||
border-bottom: inherit;
|
||||
border-block-end-style: inherit;
|
||||
border-block-end-color: var(--border-color);
|
||||
border-block-end-width: var(--border-width);
|
||||
padding: calc(var(--spacing) / 2) var(--spacing);
|
||||
}
|
||||
|
||||
@@ -50,14 +61,14 @@
|
||||
|
||||
.footer {
|
||||
display: block;
|
||||
border-top: inherit;
|
||||
border-end-start-radius: var(--inner-border-radius);
|
||||
border-end-end-radius: var(--inner-border-radius);
|
||||
border-block-start-style: inherit;
|
||||
border-block-start-color: var(--border-color);
|
||||
border-block-start-width: var(--border-width);
|
||||
padding: var(--spacing);
|
||||
}
|
||||
|
||||
:host(:not([ssr-slots~='header'])) .header,
|
||||
:host(:not([ssr-slots~='footer'])) .footer,
|
||||
:host(:not([ssr-slots~='image'])) .image {
|
||||
:host(:not([with-header])) .header,
|
||||
:host(:not([with-footer])) .footer,
|
||||
:host(:not([with-image])) .image {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { html } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import sizeStyles from '../../styles/utilities/size.css';
|
||||
import styles from './card.css';
|
||||
@@ -21,40 +21,42 @@ import styles from './card.css';
|
||||
* @csspart body - The container that wraps the card's main content.
|
||||
* @csspart footer - The container that wraps the card's footer.
|
||||
*
|
||||
* @cssproperty --border-radius - The radius for the card's corners. Expects a single value. Defaults to `var(--wa-panel-border-radius)`.
|
||||
* @cssproperty --border-width - The width of the card's borders. Expects a single value. Defaults to `var(--wa-panel-border-width)`.
|
||||
* @cssproperty --spacing - The amount of space around and between sections of the card. Expects a single value. Defaults to `var(--wa-space)`.
|
||||
* @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, 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, 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';
|
||||
|
||||
static SSR_SLOTS = ['image', 'header', 'footer'];
|
||||
/** Renders the card with a header. Only needed for SSR, otherwise is automatically added. */
|
||||
@property({ attribute: 'with-header', type: Boolean, reflect: true }) withHeader = false;
|
||||
|
||||
/** Renders the card with an image. Only needed for SSR, otherwise is automatically added. */
|
||||
@property({ attribute: 'with-image', type: Boolean, reflect: true }) withImage = false;
|
||||
|
||||
/** Renders the card with a footer. Only needed for SSR, otherwise is automatically added. */
|
||||
@property({ attribute: 'with-footer', type: Boolean, reflect: true }) withFooter = false;
|
||||
|
||||
updated() {
|
||||
// Enable the respective slots when detected
|
||||
if (!this.withHeader && this.hasSlotController.test('header')) this.withHeader = true;
|
||||
if (!this.withImage && this.hasSlotController.test('image')) this.withImage = true;
|
||||
if (!this.withFooter && this.hasSlotController.test('footer')) this.withFooter = true;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<slot
|
||||
name="image"
|
||||
part="image"
|
||||
class="${classMap({ image: true, 'has-slotted': this.hasSlotted.has('image') })}"
|
||||
@slotchange=${this.slotUpdate}
|
||||
></slot>
|
||||
<slot
|
||||
name="header"
|
||||
part="header"
|
||||
class="${classMap({ header: true, 'has-slotted': this.hasSlotted.has('header') })}"
|
||||
@slotchange=${this.slotUpdate}
|
||||
></slot>
|
||||
<slot name="image" part="image" class="image"></slot>
|
||||
<slot name="header" part="header" class="header"></slot>
|
||||
<slot part="body" class="body"></slot>
|
||||
<slot
|
||||
name="footer"
|
||||
part="footer"
|
||||
class="${classMap({ footer: true, 'has-slotted': this.hasSlotted.has('footer') })}"
|
||||
@slotchange=${this.slotUpdate}
|
||||
></slot>
|
||||
<slot name="footer" part="footer" class="footer"></slot>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
|
||||
@property({ type: Boolean, reflect: true }) required = false;
|
||||
|
||||
/** The checkbox's hint. If you need to display HTML, use the `hint` slot instead. */
|
||||
@property({ attribute: 'hint' }) hint = '';
|
||||
@property() hint = '';
|
||||
|
||||
private handleClick() {
|
||||
this.hasInteracted = true;
|
||||
|
||||
@@ -55,12 +55,6 @@ details {
|
||||
padding-block-start: var(--spacing);
|
||||
}
|
||||
|
||||
.show {
|
||||
}
|
||||
|
||||
.hide {
|
||||
}
|
||||
|
||||
@keyframes show {
|
||||
from {
|
||||
}
|
||||
|
||||
@@ -60,9 +60,16 @@ describe('<wa-menu-item>', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a text label when calling getTextLabel()', async () => {
|
||||
it('defaultLabel should return a text label', async () => {
|
||||
const el = await fixture<WaMenuItem>(html` <wa-menu-item>Test</wa-menu-item> `);
|
||||
expect(el.getTextLabel()).to.equal('Test');
|
||||
expect(el.defaultLabel).to.equal('Test');
|
||||
expect(el.label).to.equal('Test');
|
||||
});
|
||||
|
||||
it('label attribute should override default label', async () => {
|
||||
const el = await fixture<WaMenuItem>(html` <wa-menu-item label="Manual label">Text content</wa-menu-item> `);
|
||||
expect(el.defaultLabel).to.equal('Text content');
|
||||
expect(el.label).to.equal('Manual label');
|
||||
});
|
||||
|
||||
it('should emit the slotchange event when the label changes', async () => {
|
||||
@@ -107,7 +114,7 @@ describe('<wa-menu-item>', () => {
|
||||
expect(submenuSlot.hidden).to.be.true;
|
||||
});
|
||||
|
||||
it('should render an wa-popup if the slot="submenu" attribute is present', async () => {
|
||||
it('should render a wa-popup if the slot="submenu" attribute is present', async () => {
|
||||
const menu = await fixture<WaMenuItem>(html`
|
||||
<wa-menu>
|
||||
<wa-menu-item id="test">
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { PropertyValues } from 'lit';
|
||||
import { html } from 'lit';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { getTextContent } from '../../internal/slot.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import getText from '../../internal/get-text.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import '../icon/icon.js';
|
||||
@@ -43,7 +42,6 @@ import { SubmenuController } from './submenu-controller.js';
|
||||
export default class WaMenuItem extends WebAwesomeElement {
|
||||
static shadowStyle = styles;
|
||||
|
||||
private cachedTextLabel: string;
|
||||
private readonly localize = new LocalizeController(this);
|
||||
|
||||
@query('slot:not([name])') defaultSlot: HTMLSlotElement;
|
||||
@@ -64,6 +62,36 @@ export default class WaMenuItem extends WebAwesomeElement {
|
||||
/** Draws the menu item in a disabled state, preventing selection. */
|
||||
@property({ type: Boolean, reflect: true }) disabled = false;
|
||||
|
||||
_label: string = '';
|
||||
/**
|
||||
* The option’s plain text label.
|
||||
* Usually automatically generated, but can be useful to provide manually for cases involving complex content.
|
||||
*/
|
||||
@property()
|
||||
set label(value) {
|
||||
const oldValue = this._label;
|
||||
this._label = value || '';
|
||||
|
||||
if (this._label !== oldValue) {
|
||||
this.requestUpdate('label', oldValue);
|
||||
}
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
if (this._label) {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
if (!this.defaultLabel) {
|
||||
this.updateDefaultLabel();
|
||||
}
|
||||
|
||||
return this.defaultLabel;
|
||||
}
|
||||
|
||||
/** The default label, generated from the element contents. Will be equal to `label` in most cases. */
|
||||
@state() defaultLabel = '';
|
||||
|
||||
/**
|
||||
* Used for SSR purposes. If true, will render a ">" caret icon for showing that it has a submenu, but will be non-interactive.
|
||||
*/
|
||||
@@ -75,6 +103,7 @@ export default class WaMenuItem extends WebAwesomeElement {
|
||||
super.connectedCallback();
|
||||
this.addEventListener('click', this.handleHostClick);
|
||||
this.addEventListener('mouseover', this.handleMouseOver);
|
||||
this.updateDefaultLabel();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
@@ -93,17 +122,10 @@ export default class WaMenuItem extends WebAwesomeElement {
|
||||
}
|
||||
|
||||
private handleDefaultSlotChange() {
|
||||
const textLabel = this.getTextLabel();
|
||||
|
||||
// Ignore the first time the label is set
|
||||
if (typeof this.cachedTextLabel === 'undefined') {
|
||||
this.cachedTextLabel = textLabel;
|
||||
return;
|
||||
}
|
||||
let labelChanged = this.updateDefaultLabel();
|
||||
|
||||
// When the label changes, emit a slotchange event so parent controls see it
|
||||
if (textLabel !== this.cachedTextLabel) {
|
||||
this.cachedTextLabel = textLabel;
|
||||
if (labelChanged) {
|
||||
/** @internal - prevent the CEM from recording this event */
|
||||
this.dispatchEvent(new Event('slotchange', { bubbles: true, composed: false, cancelable: false }));
|
||||
}
|
||||
@@ -122,41 +144,48 @@ export default class WaMenuItem extends WebAwesomeElement {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
@watch('checked')
|
||||
handleCheckedChange() {
|
||||
// For proper accessibility, users have to use type="checkbox" to use the checked attribute
|
||||
if (this.checked && this.type !== 'checkbox') {
|
||||
this.checked = false;
|
||||
return;
|
||||
updated(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has('checked')) {
|
||||
// For proper accessibility, users have to use type="checkbox" to use the checked attribute
|
||||
if (this.checked && this.type !== 'checkbox') {
|
||||
this.checked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Only checkbox types can receive the aria-checked attribute
|
||||
if (this.type === 'checkbox') {
|
||||
this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
|
||||
} else {
|
||||
this.removeAttribute('aria-checked');
|
||||
}
|
||||
}
|
||||
|
||||
// Only checkbox types can receive the aria-checked attribute
|
||||
if (this.type === 'checkbox') {
|
||||
this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
|
||||
} else {
|
||||
this.removeAttribute('aria-checked');
|
||||
if (changedProperties.has('disabled')) {
|
||||
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
|
||||
}
|
||||
|
||||
if (changedProperties.has('type')) {
|
||||
if (this.type === 'checkbox') {
|
||||
this.setAttribute('role', 'menuitemcheckbox');
|
||||
this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
|
||||
} else {
|
||||
this.setAttribute('role', 'menuitem');
|
||||
this.removeAttribute('aria-checked');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@watch('disabled')
|
||||
handleDisabledChange() {
|
||||
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
|
||||
}
|
||||
private updateDefaultLabel() {
|
||||
let oldValue = this.defaultLabel;
|
||||
this.defaultLabel = getText(this).trim();
|
||||
let changed = this.defaultLabel !== oldValue;
|
||||
|
||||
@watch('type')
|
||||
handleTypeChange() {
|
||||
if (this.type === 'checkbox') {
|
||||
this.setAttribute('role', 'menuitemcheckbox');
|
||||
this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
|
||||
} else {
|
||||
this.setAttribute('role', 'menuitem');
|
||||
this.removeAttribute('aria-checked');
|
||||
if (!this._label && changed) {
|
||||
// Uses default label, and it has changed
|
||||
this.requestUpdate('label', oldValue);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a text label based on the contents of the menu item's default slot. */
|
||||
getTextLabel() {
|
||||
return getTextContent(this.defaultSlot);
|
||||
return changed;
|
||||
}
|
||||
|
||||
isSubmenu() {
|
||||
|
||||
@@ -8,13 +8,7 @@
|
||||
color: var(--wa-color-text-normal);
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
:host(:focus) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.option {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -25,19 +19,23 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:host(:not([disabled])) .option--hover:not(.option--current) {
|
||||
:host(:focus) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
:host(:not([disabled], :state(current)):is(:state(hover), :hover)) {
|
||||
background-color: var(--background-color-hover);
|
||||
color: var(--text-color-hover);
|
||||
}
|
||||
|
||||
.option--current,
|
||||
:host([disabled]) .option--current {
|
||||
:host(:state(current)),
|
||||
:host([disabled]:state(current)) {
|
||||
background-color: var(--background-color-current);
|
||||
color: var(--text-color-current);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
:host([disabled]) .option {
|
||||
:host([disabled]) {
|
||||
outline: none;
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
@@ -48,7 +46,7 @@
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.option .check {
|
||||
.check {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -58,7 +56,7 @@
|
||||
width: var(--wa-space-xl);
|
||||
}
|
||||
|
||||
.option--selected .check {
|
||||
:host(:state(selected)) .check {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
@@ -78,7 +76,7 @@
|
||||
}
|
||||
|
||||
@media (forced-colors: active) {
|
||||
:host(:hover:not([aria-disabled='true'])) .option {
|
||||
:host(:hover:not([aria-disabled='true'])) {
|
||||
outline: dashed 1px SelectedItem;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ describe('<wa-option>', () => {
|
||||
|
||||
expect(el.value).to.equal('');
|
||||
expect(el.disabled).to.be.false;
|
||||
expect(el.label).to.equal('Test');
|
||||
expect(el.getAttribute('aria-disabled')).to.equal('false');
|
||||
});
|
||||
|
||||
@@ -44,9 +45,16 @@ describe('<wa-option>', () => {
|
||||
expect(el.value).to.equal('10');
|
||||
});
|
||||
|
||||
it('should escape HTML when calling getTextLabel()', async () => {
|
||||
it('defaultLabel should escape HTML', async () => {
|
||||
const el = await fixture<WaOption>(html` <wa-option><strong>Option</strong></wa-option> `);
|
||||
expect(el.getTextLabel()).to.equal('Option');
|
||||
expect(el.defaultLabel).to.equal('Option');
|
||||
expect(el.label).to.equal('Option');
|
||||
});
|
||||
|
||||
it('label attribute should override default label', async () => {
|
||||
const el = await fixture<WaOption>(html` <wa-option label="Manual label">Text content</wa-option> `);
|
||||
expect(el.defaultLabel).to.equal('Text content');
|
||||
expect(el.label).to.equal('Manual label');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { PropertyValues } from 'lit';
|
||||
import { html } from 'lit';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import getText from '../../internal/get-text.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import '../icon/icon.js';
|
||||
@@ -25,10 +25,13 @@ import styles from './option.css';
|
||||
* @cssproperty --text-color-hover - The label color on hover.
|
||||
*
|
||||
* @csspart checked-icon - The checked icon, a `<wa-icon>` element.
|
||||
* @csspart base - The component's base wrapper.
|
||||
* @csspart label - The option's label.
|
||||
* @csspart prefix - The container that wraps the prefix.
|
||||
* @csspart suffix - The container that wraps the suffix.
|
||||
*
|
||||
* @cssstate current - The user has keyed into the option, but hasn't selected it yet (shows a highlight)
|
||||
* @cssstate selected - The option is selected and has aria-selected="true"
|
||||
* @cssstate hover - Like `:hover` but works while dragging in Safari
|
||||
*/
|
||||
@customElement('wa-option')
|
||||
export default class WaOption extends WebAwesomeElement {
|
||||
@@ -40,9 +43,10 @@ export default class WaOption extends WebAwesomeElement {
|
||||
|
||||
@query('.label') defaultSlot: HTMLSlotElement;
|
||||
|
||||
@state() current = false; // the user has keyed into the option, but hasn't selected it yet (shows a highlight)
|
||||
@state() selected = false; // the option is selected and has aria-selected="true"
|
||||
@state() hasHover = false; // we need this because Safari doesn't honor :hover styles while dragging
|
||||
// Set via the parent select
|
||||
@state() current = false;
|
||||
|
||||
@state() selected = false;
|
||||
|
||||
/**
|
||||
* The option's value. When selected, the containing form control will receive this value. The value must be unique
|
||||
@@ -54,13 +58,56 @@ export default class WaOption extends WebAwesomeElement {
|
||||
/** Draws the option in a disabled state, preventing selection. */
|
||||
@property({ type: Boolean, reflect: true }) disabled = false;
|
||||
|
||||
_label: string = '';
|
||||
/**
|
||||
* The option’s plain text label.
|
||||
* Usually automatically generated, but can be useful to provide manually for cases involving complex content.
|
||||
*/
|
||||
@property()
|
||||
set label(value) {
|
||||
const oldValue = this._label;
|
||||
this._label = value || '';
|
||||
|
||||
if (this._label !== oldValue) {
|
||||
this.requestUpdate('label', oldValue);
|
||||
}
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
if (this._label) {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
if (!this.defaultLabel) {
|
||||
this.updateDefaultLabel();
|
||||
}
|
||||
|
||||
return this.defaultLabel;
|
||||
}
|
||||
|
||||
/** The default label, generated from the element contents. Will be equal to `label` in most cases. */
|
||||
@state() defaultLabel = '';
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.setAttribute('role', 'option');
|
||||
this.setAttribute('aria-selected', 'false');
|
||||
|
||||
this.addEventListener('mouseenter', this.handleHover);
|
||||
this.addEventListener('mouseleave', this.handleHover);
|
||||
this.updateDefaultLabel();
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
|
||||
this.removeEventListener('mouseenter', this.handleHover);
|
||||
this.removeEventListener('mouseleave', this.handleHover);
|
||||
}
|
||||
|
||||
private handleDefaultSlotChange() {
|
||||
this.updateDefaultLabel();
|
||||
|
||||
if (this.isInitialized) {
|
||||
// When the label changes, tell the controller to update
|
||||
customElements.whenDefined('wa-select').then(() => {
|
||||
@@ -74,84 +121,75 @@ export default class WaOption extends WebAwesomeElement {
|
||||
}
|
||||
}
|
||||
|
||||
private handleMouseEnter() {
|
||||
this.hasHover = true;
|
||||
}
|
||||
private handleHover = (event: Event) => {
|
||||
// We need this because Safari doesn't honor :hover styles while dragging
|
||||
// Testcase: https://codepen.io/leaverou/pen/VYZOOjy
|
||||
if (event.type === 'mouseenter') {
|
||||
this.toggleCustomState('hover', true);
|
||||
} else if (event.type === 'mouseleave') {
|
||||
this.toggleCustomState('hover', false);
|
||||
}
|
||||
};
|
||||
|
||||
private handleMouseLeave() {
|
||||
this.hasHover = false;
|
||||
}
|
||||
updated(changedProperties: PropertyValues<this>) {
|
||||
super.updated(changedProperties);
|
||||
|
||||
@watch('disabled')
|
||||
handleDisabledChange() {
|
||||
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
|
||||
}
|
||||
|
||||
@watch('selected')
|
||||
handleSelectedChange() {
|
||||
this.setAttribute('aria-selected', this.selected ? 'true' : 'false');
|
||||
}
|
||||
|
||||
@watch('value')
|
||||
handleValueChange() {
|
||||
// Ensure the value is a string. This ensures the next line doesn't error and allows framework users to pass numbers
|
||||
// instead of requiring them to cast the value to a string.
|
||||
if (typeof this.value !== 'string') {
|
||||
this.value = String(this.value);
|
||||
if (changedProperties.has('disabled')) {
|
||||
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
|
||||
}
|
||||
|
||||
if (this.value.includes(' ')) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Option values cannot include a space. All spaces have been replaced with underscores.`, this);
|
||||
this.value = this.value.replace(/ /g, '_');
|
||||
if (changedProperties.has('selected')) {
|
||||
this.setAttribute('aria-selected', this.selected ? 'true' : 'false');
|
||||
this.toggleCustomState('selected', this.selected);
|
||||
}
|
||||
|
||||
if (changedProperties.has('value')) {
|
||||
// Ensure the value is a string. This ensures the next line doesn't error and allows framework users to pass numbers
|
||||
// instead of requiring them to cast the value to a string.
|
||||
if (typeof this.value !== 'string') {
|
||||
this.value = String(this.value);
|
||||
}
|
||||
|
||||
if (this.value.includes(' ')) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Option values cannot include a space. All spaces have been replaced with underscores.`, this);
|
||||
this.value = this.value.replace(/ /g, '_');
|
||||
}
|
||||
|
||||
this.handleDefaultSlotChange();
|
||||
}
|
||||
|
||||
if (changedProperties.has('current')) {
|
||||
this.toggleCustomState('current', this.current);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a plain text label based on the option's content. */
|
||||
getTextLabel() {
|
||||
const nodes = this.childNodes;
|
||||
let label = '';
|
||||
private updateDefaultLabel() {
|
||||
let oldValue = this.defaultLabel;
|
||||
this.defaultLabel = getText(this).trim();
|
||||
let changed = this.defaultLabel !== oldValue;
|
||||
|
||||
[...nodes].forEach(node => {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
if (!(node as HTMLElement).hasAttribute('slot')) {
|
||||
label += (node as HTMLElement).textContent;
|
||||
}
|
||||
}
|
||||
if (!this._label && changed) {
|
||||
// Uses default label, and it has changed
|
||||
this.requestUpdate('label', oldValue);
|
||||
}
|
||||
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
label += node.textContent;
|
||||
}
|
||||
});
|
||||
|
||||
return label.trim();
|
||||
return changed;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div
|
||||
part="base"
|
||||
class=${classMap({
|
||||
option: true,
|
||||
'option--current': this.current,
|
||||
'option--selected': this.selected,
|
||||
'option--hover': this.hasHover,
|
||||
})}
|
||||
@mouseenter=${this.handleMouseEnter}
|
||||
@mouseleave=${this.handleMouseLeave}
|
||||
>
|
||||
<wa-icon
|
||||
part="checked-icon"
|
||||
class="check"
|
||||
name="check"
|
||||
library="system"
|
||||
variant="solid"
|
||||
aria-hidden="true"
|
||||
></wa-icon>
|
||||
<slot part="prefix" name="prefix" class="prefix"></slot>
|
||||
<slot part="label" class="label" @slotchange=${this.handleDefaultSlotChange}></slot>
|
||||
<slot part="suffix" name="suffix" class="suffix"></slot>
|
||||
</div>
|
||||
<wa-icon
|
||||
part="checked-icon"
|
||||
class="check"
|
||||
name="check"
|
||||
library="system"
|
||||
variant="solid"
|
||||
aria-hidden="true"
|
||||
></wa-icon>
|
||||
<slot part="prefix" name="prefix" class="prefix"></slot>
|
||||
<slot part="label" class="label" @slotchange=${this.handleDefaultSlotChange}></slot>
|
||||
<slot part="suffix" name="suffix" class="suffix"></slot>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
--banner-height: 0px;
|
||||
--header-height: 0px;
|
||||
--subheader-height: 0px;
|
||||
--scroll-margin-top: calc(var(--header-height, 0px) + var(--subheader-height, 0px));
|
||||
--scroll-margin-top: calc(var(--header-height, 0px) + var(--subheader-height, 0px) + 0.5em);
|
||||
}
|
||||
|
||||
slot[name]:not([name='skip-to-content'], [name='navigation-toggle'])::slotted(*) {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
:host(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.radio {
|
||||
display: flex;
|
||||
:host {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
align-items: top;
|
||||
font: inherit;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:host(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.checked-icon {
|
||||
display: flex;
|
||||
fill: currentColor;
|
||||
@@ -19,6 +19,11 @@
|
||||
}
|
||||
|
||||
/* When the control isn't checked, hide the circle for Windows High Contrast mode a11y */
|
||||
.radio:not(.radio--checked) svg circle {
|
||||
:host(:not(:state(checked))) svg circle {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[part~='hint'] {
|
||||
grid-column: 2;
|
||||
margin-block-start: var(--wa-space-3xs);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { PropertyValues } from 'lit';
|
||||
import { html, isServer } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js';
|
||||
import nativeStyles from '../../styles/native/radio.css';
|
||||
import formControlStyles from '../../styles/shadow/form-control.css';
|
||||
import sizeStyles from '../../styles/utilities/size.css';
|
||||
import '../icon/icon.js';
|
||||
import styles from './radio.css';
|
||||
@@ -17,14 +19,15 @@ import styles from './radio.css';
|
||||
* @dependency wa-icon
|
||||
*
|
||||
* @slot - The radio's label.
|
||||
* @slot hint - Text that describes how to use the checkbox. Alternatively, you can use the `hint` attribute.
|
||||
*
|
||||
* @event blur - Emitted when the control loses focus.
|
||||
* @event focus - Emitted when the control gains focus.
|
||||
*
|
||||
* @csspart base - The component's base wrapper.
|
||||
* @csspart control - The circular container that wraps the radio's checked state.
|
||||
* @csspart checked-icon - The checked icon.
|
||||
* @csspart label - The container that wraps the radio's label.
|
||||
* @csspart hint - The hint's wrapper.
|
||||
*
|
||||
* @cssproperty --background-color - The radio's background color.
|
||||
* @cssproperty --background-color-checked - The radio's background color when checked.
|
||||
@@ -42,7 +45,7 @@ import styles from './radio.css';
|
||||
*/
|
||||
@customElement('wa-radio')
|
||||
export default class WaRadio extends WebAwesomeFormAssociatedElement {
|
||||
static shadowStyle = [sizeStyles, nativeStyles, styles];
|
||||
static shadowStyle = [formControlStyles, sizeStyles, nativeStyles, styles];
|
||||
|
||||
@state() checked = false;
|
||||
|
||||
@@ -63,6 +66,11 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement {
|
||||
/** Disables the radio. */
|
||||
@property({ type: Boolean }) disabled = false;
|
||||
|
||||
/** The radio's hint. If you need to display HTML, use the `hint` slot instead. */
|
||||
@property() hint = '';
|
||||
|
||||
private readonly hasSlotController = new HasSlotController(this, 'hint');
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
if (!isServer) {
|
||||
@@ -81,11 +89,19 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement {
|
||||
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
|
||||
}
|
||||
|
||||
@watch('checked')
|
||||
handleCheckedChange() {
|
||||
this.toggleCustomState('checked', this.checked);
|
||||
this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
|
||||
this.tabIndex = this.checked ? 0 : -1;
|
||||
updated(changedProperties: PropertyValues<this>) {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProperties.has('checked')) {
|
||||
this.toggleCustomState('checked', this.checked);
|
||||
this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
|
||||
this.tabIndex = this.checked ? 0 : -1;
|
||||
}
|
||||
|
||||
if (changedProperties.has('disabled')) {
|
||||
this.toggleCustomState('disabled', this.disabled);
|
||||
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,12 +111,6 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement {
|
||||
// We override `setValue` because we don't want to set form values from here. We want to do that in "RadioGroup" itself.
|
||||
}
|
||||
|
||||
@watch('disabled', { waitUntilFirstUpdate: true })
|
||||
handleDisabledChange() {
|
||||
this.toggleCustomState('disabled', this.disabled);
|
||||
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
|
||||
}
|
||||
|
||||
private handleClick = () => {
|
||||
if (!this.disabled) {
|
||||
this.checked = true;
|
||||
@@ -108,26 +118,30 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement {
|
||||
};
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<span
|
||||
part="base"
|
||||
class=${classMap({
|
||||
radio: true,
|
||||
'radio--checked': this.checked,
|
||||
})}
|
||||
>
|
||||
<span part="control" class="control">
|
||||
${this.checked
|
||||
? html`
|
||||
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" part="checked-icon" class="checked-icon">
|
||||
<circle cx="8" cy="8" r="8" />
|
||||
</svg>
|
||||
`
|
||||
: ''}
|
||||
</span>
|
||||
const hasHintSlot = isServer ? true : this.hasSlotController.test('hint');
|
||||
const hasHint = this.hint ? true : !!hasHintSlot;
|
||||
|
||||
<slot part="label" class="label"></slot>
|
||||
return html`
|
||||
<span part="control" class="control">
|
||||
${this.checked
|
||||
? html`
|
||||
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" part="checked-icon" class="checked-icon">
|
||||
<circle cx="8" cy="8" r="8" />
|
||||
</svg>
|
||||
`
|
||||
: ''}
|
||||
</span>
|
||||
|
||||
<slot part="label" class="label"></slot>
|
||||
|
||||
<slot
|
||||
name="hint"
|
||||
aria-hidden=${hasHint ? 'false' : 'true'}
|
||||
class="${classMap({ 'has-slotted': hasHint })}"
|
||||
id="hint"
|
||||
part="hint"
|
||||
>${this.hint}</slot
|
||||
>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--wa-form-controls-placeholder-color);
|
||||
color: var(--wa-form-control-placeholder-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
align-items: center;
|
||||
color: var(--wa-color-neutral-on-quiet);
|
||||
transition: rotate var(--wa-transition-slow) ease;
|
||||
rotate: 0;
|
||||
rotate: 0deg;
|
||||
margin-inline-start: var(--wa-space-s);
|
||||
|
||||
.open & {
|
||||
@@ -194,7 +194,7 @@
|
||||
background: var(--wa-color-surface-raised);
|
||||
border-color: var(--wa-color-surface-border);
|
||||
border-radius: var(--wa-border-radius-m);
|
||||
border-style: var(--border-style);
|
||||
border-style: var(--wa-border-style);
|
||||
border-width: var(--border-width);
|
||||
padding-block: var(--wa-space-xs);
|
||||
padding-inline: 0;
|
||||
|
||||
@@ -68,7 +68,6 @@ import styles from './select.css';
|
||||
* @csspart listbox - The listbox container where options are slotted.
|
||||
* @csspart tags - The container that houses option tags when `multiselect` is used.
|
||||
* @csspart tag - The individual tags that represent each multiselect option.
|
||||
* @csspart tag__base - The tag's base part.
|
||||
* @csspart tag__content - The tag's content part.
|
||||
* @csspart tag__remove-button - The tag's remove button.
|
||||
* @csspart tag__remove-button__base - The tag's remove button base part.
|
||||
@@ -253,7 +252,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
removable
|
||||
@wa-remove=${(event: WaRemoveEvent) => this.handleTagRemove(event, option)}
|
||||
>
|
||||
${option.getTextLabel()}
|
||||
${option.label}
|
||||
</wa-tag>
|
||||
`;
|
||||
};
|
||||
@@ -438,7 +437,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
for (const option of allOptions) {
|
||||
const label = option.getTextLabel().toLowerCase();
|
||||
const label = option.label.toLowerCase();
|
||||
|
||||
if (label.startsWith(this.typeToSelectString)) {
|
||||
this.setCurrentOption(option);
|
||||
@@ -643,7 +642,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
} else {
|
||||
const selectedOption = this.selectedOptions[0];
|
||||
this.value = selectedOption?.value ?? '';
|
||||
this.displayLabel = selectedOption?.getTextLabel?.() ?? '';
|
||||
this.displayLabel = selectedOption?.label ?? '';
|
||||
}
|
||||
|
||||
// Update validity
|
||||
@@ -661,7 +660,18 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
</div>`;
|
||||
} else if (index === this.maxOptionsVisible) {
|
||||
// Hit tag limit
|
||||
return html`<wa-tag>+${this.selectedOptions.length - index}</wa-tag>`;
|
||||
return html`
|
||||
<wa-tag
|
||||
part="tag"
|
||||
exportparts="
|
||||
base:tag__base,
|
||||
content:tag__content,
|
||||
remove-button:tag__remove-button,
|
||||
remove-button__base:tag__remove-button__base
|
||||
"
|
||||
>+${this.selectedOptions.length - index}</wa-tag
|
||||
>
|
||||
`;
|
||||
}
|
||||
return html``;
|
||||
});
|
||||
@@ -917,6 +927,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
</div>
|
||||
|
||||
<slot
|
||||
id="hint"
|
||||
name="hint"
|
||||
part="hint"
|
||||
class=${classMap({
|
||||
|
||||
@@ -24,7 +24,6 @@ let id = 0;
|
||||
@customElement('wa-tab')
|
||||
export default class WaTab extends WebAwesomeElement {
|
||||
static shadowStyle = styles;
|
||||
public slot = 'nav'; // Auto-slot into nav slot
|
||||
|
||||
private readonly attrId = ++id;
|
||||
private readonly componentId = `wa-tab-${this.attrId}`;
|
||||
@@ -47,6 +46,9 @@ export default class WaTab extends WebAwesomeElement {
|
||||
@property({ type: Number, reflect: true }) tabIndex = 0;
|
||||
|
||||
connectedCallback() {
|
||||
// Auto-slot into nav slot
|
||||
this.slot ||= 'nav';
|
||||
|
||||
super.connectedCallback();
|
||||
this.setAttribute('role', 'tab');
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import type { ComplexAttributeConverter } from 'lit';
|
||||
|
||||
export const setConverter: ComplexAttributeConverter<Set<string> | null, Set<string> | null> = {
|
||||
fromAttribute(value): Set<string> | null {
|
||||
if (value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Set(value.split(/\s+/));
|
||||
},
|
||||
toAttribute: value => {
|
||||
if (value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [...(value as Set<string>)].join(' ');
|
||||
},
|
||||
};
|
||||
46
src/internal/get-text.ts
Normal file
46
src/internal/get-text.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Like textContent, but better:
|
||||
* - Uses assignedNodes to get text content from slots (and falls back to content if nothing is slotted)
|
||||
* - Ignores script and style elements
|
||||
* @param root - One or more nodes to get text content from.
|
||||
* @param depth - By default, will just return element.textContent for any child elements instead of calling the function recursively.
|
||||
* Set to a positive integer to recurse that many levels. Generally a tradeoff between performance and accuracy.
|
||||
* @returns
|
||||
*/
|
||||
export default function getText(root: Node | Iterable<Node>, depth = 0): string {
|
||||
if (!root || !globalThis.Node) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (typeof (root as any)[Symbol.iterator] === 'function') {
|
||||
let nodes = Array.isArray(root) ? root : [...(root as Iterable<Node>)];
|
||||
return nodes.map(node => getText(node, --depth)).join('');
|
||||
}
|
||||
|
||||
let node = root as Node;
|
||||
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
return node.textContent ?? '';
|
||||
}
|
||||
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
let element = node as HTMLElement;
|
||||
|
||||
if (element.hasAttribute('slot') || element.matches('style, script')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (element instanceof HTMLSlotElement) {
|
||||
let assignedNodes = element.assignedNodes({ flatten: true });
|
||||
|
||||
if (assignedNodes.length > 0) {
|
||||
// If no assigned nodes, we still want the slot contents
|
||||
return getText(assignedNodes, --depth);
|
||||
}
|
||||
}
|
||||
|
||||
return depth > -1 ? getText(element, --depth) : (element.textContent ?? '');
|
||||
}
|
||||
|
||||
return node.hasChildNodes() ? getText(node.childNodes, --depth) : '';
|
||||
}
|
||||
@@ -12,11 +12,11 @@ export class HasSlotController implements ReactiveController {
|
||||
|
||||
private hasDefaultSlot() {
|
||||
return [...this.host.childNodes].some(node => {
|
||||
if (node.nodeType === node.TEXT_NODE && node.textContent!.trim() !== '') {
|
||||
if (node.nodeType === Node.TEXT_NODE && node.textContent!.trim() !== '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (node.nodeType === node.ELEMENT_NODE) {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const el = node as HTMLElement;
|
||||
const tagName = el.tagName.toLowerCase();
|
||||
|
||||
@@ -90,23 +90,3 @@ export function getInnerHTML(nodes: Iterable<Node>, callback?: (node: Node) => s
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a slot, this function iterates over all of its assigned text nodes and returns the concatenated text as a
|
||||
* string. This is useful because we can't use slot.textContent as an alternative.
|
||||
*/
|
||||
export function getTextContent(slot: HTMLSlotElement | undefined | null): string {
|
||||
if (!slot) {
|
||||
return '';
|
||||
}
|
||||
const nodes = slot.assignedNodes({ flatten: true });
|
||||
let text = '';
|
||||
|
||||
[...nodes].forEach(node => {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
text += node.textContent;
|
||||
}
|
||||
});
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { CSSResult, CSSResultGroup, PropertyDeclaration, PropertyValues } f
|
||||
import { LitElement, defaultConverter, isServer, unsafeCSS } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import componentStyles from '../styles/shadow/component.css';
|
||||
import { setConverter } from './converters.js';
|
||||
|
||||
// Augment Lit's module
|
||||
declare module 'lit' {
|
||||
@@ -41,20 +40,6 @@ export default class WebAwesomeElement extends LitElement {
|
||||
@property() dir: string;
|
||||
@property() lang: string;
|
||||
|
||||
@property({
|
||||
reflect: true,
|
||||
attribute: 'ssr-slots',
|
||||
converter: setConverter,
|
||||
})
|
||||
ssrSlots: Set<string> | null = null;
|
||||
|
||||
/**
|
||||
* All slots whose slotted status we need to know on the root for SSR.
|
||||
* Subclasses are expected to override this with their own list of slots.
|
||||
*/
|
||||
static SSR_SLOTS: string[] = [];
|
||||
protected hasSlotted = new Set();
|
||||
|
||||
/**
|
||||
* One or more styles for the element’s own shadow DOM.
|
||||
* Shared component styles will automatically be added.
|
||||
@@ -87,13 +72,6 @@ export default class WebAwesomeElement extends LitElement {
|
||||
|
||||
internals: ElementInternals;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
let Self = this.constructor as typeof WebAwesomeElement;
|
||||
this.ssrSlots = new Set(Self.SSR_SLOTS.filter(name => this.slotUpdate(name)));
|
||||
}
|
||||
|
||||
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
|
||||
if (!this.#hasRecordedInitialProperties) {
|
||||
(this.constructor as typeof WebAwesomeElement).elementProperties.forEach(
|
||||
@@ -191,44 +169,6 @@ export default class WebAwesomeElement extends LitElement {
|
||||
return this.hasStatesSupport() ? this.internals.states.has(state) : false;
|
||||
}
|
||||
|
||||
protected async slotUpdate(target: Event | HTMLSlotElement | string) {
|
||||
await this.updateComplete;
|
||||
|
||||
let slot = target;
|
||||
|
||||
if (target instanceof Event) {
|
||||
slot = target.target as HTMLSlotElement;
|
||||
} else if (typeof target === 'string') {
|
||||
slot = this.shadowRoot!.querySelector(`slot[name="${target}"]`) as HTMLSlotElement;
|
||||
} else {
|
||||
slot = slot as HTMLSlotElement;
|
||||
}
|
||||
|
||||
if (!slot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const slotName = slot.name;
|
||||
const hasSlotted = slot.assignedNodes().length > 0;
|
||||
|
||||
let previousHasSlotted = this.hasSlotted.has(slotName);
|
||||
|
||||
if (previousHasSlotted === hasSlotted) {
|
||||
let ssrSlots = new Set(this.ssrSlots);
|
||||
|
||||
if (hasSlotted) {
|
||||
ssrSlots.add(slotName);
|
||||
} else {
|
||||
ssrSlots.delete(slotName);
|
||||
}
|
||||
|
||||
this.ssrSlots = ssrSlots;
|
||||
}
|
||||
|
||||
slot.classList.toggle('has-slotted', hasSlotted);
|
||||
return hasSlotted;
|
||||
}
|
||||
|
||||
getComputed(prop: PropertyKey) {
|
||||
let value = this[prop as keyof this];
|
||||
if (value !== 'inherit') {
|
||||
|
||||
80
src/styles/brand/base.css
Normal file
80
src/styles/brand/base.css
Normal file
@@ -0,0 +1,80 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "base.css.njk" */
|
||||
:where(:root),
|
||||
:host,
|
||||
:where([class^='wa-theme-'], [class*=' wa-theme-']),
|
||||
:where([class^='wa-palette-'], [class*=' wa-palette-']),
|
||||
:where([class^='wa-brand-'], [class*=' wa-brand-']) {
|
||||
/**
|
||||
* Conditional tokens for use in color-mix()
|
||||
* --wa-color-brand-if-lt-N ➡️ 100% if key < N, 0% otherwise
|
||||
* --wa-color-brand-if-gte-N ➡️ 100% if key >= N, 0% otherwise
|
||||
*/
|
||||
|
||||
--wa-color-brand-if-lt-40: calc(clamp(0, 40 - var(--wa-color-brand-key), 1) * 100%);
|
||||
--wa-color-brand-if-gte-40: calc(100% - var(--wa-color-brand-if-lt-40));
|
||||
|
||||
--wa-color-brand-if-lt-50: calc(clamp(0, 50 - var(--wa-color-brand-key), 1) * 100%);
|
||||
--wa-color-brand-if-gte-50: calc(100% - var(--wa-color-brand-if-lt-50));
|
||||
|
||||
--wa-color-brand-if-lt-60: calc(clamp(0, 60 - var(--wa-color-brand-key), 1) * 100%);
|
||||
--wa-color-brand-if-gte-60: calc(100% - var(--wa-color-brand-if-lt-60));
|
||||
|
||||
--wa-color-brand-if-lt-70: calc(clamp(0, 70 - var(--wa-color-brand-key), 1) * 100%);
|
||||
--wa-color-brand-if-gte-70: calc(100% - var(--wa-color-brand-if-lt-70));
|
||||
|
||||
--wa-color-brand-if-lt-80: calc(clamp(0, 80 - var(--wa-color-brand-key), 1) * 100%);
|
||||
--wa-color-brand-if-gte-80: calc(100% - var(--wa-color-brand-if-lt-80));
|
||||
|
||||
/*
|
||||
* Convenience tokens for common tint cutoffs
|
||||
* --wa-color-brand-N-max ➡️ var(--color-brand) if key <= N, var(--color-brand-N) otherwise
|
||||
* --wa-color-brand-N-min ➡️ var(--color-brand) if key >= N, var(--color-brand-N) otherwise
|
||||
*/
|
||||
|
||||
--wa-color-brand-40-max: color-mix(
|
||||
in oklab,
|
||||
var(--wa-color-brand) var(--wa-color-brand-if-lt-40),
|
||||
var(--wa-color-brand-40)
|
||||
);
|
||||
--wa-color-brand-40-min: color-mix(
|
||||
in oklab,
|
||||
var(--wa-color-brand) var(--wa-color-brand-if-gte-40),
|
||||
var(--wa-color-brand-40)
|
||||
);
|
||||
|
||||
--wa-color-brand-50-max: color-mix(
|
||||
in oklab,
|
||||
var(--wa-color-brand) var(--wa-color-brand-if-lt-50),
|
||||
var(--wa-color-brand-50)
|
||||
);
|
||||
--wa-color-brand-50-min: color-mix(
|
||||
in oklab,
|
||||
var(--wa-color-brand) var(--wa-color-brand-if-gte-50),
|
||||
var(--wa-color-brand-50)
|
||||
);
|
||||
|
||||
--wa-color-brand-60-max: color-mix(
|
||||
in oklab,
|
||||
var(--wa-color-brand) var(--wa-color-brand-if-lt-60),
|
||||
var(--wa-color-brand-60)
|
||||
);
|
||||
--wa-color-brand-60-min: color-mix(
|
||||
in oklab,
|
||||
var(--wa-color-brand) var(--wa-color-brand-if-gte-60),
|
||||
var(--wa-color-brand-60)
|
||||
);
|
||||
|
||||
--wa-color-brand-70-max: color-mix(
|
||||
in oklab,
|
||||
var(--wa-color-brand) var(--wa-color-brand-if-lt-70),
|
||||
var(--wa-color-brand-70)
|
||||
);
|
||||
--wa-color-brand-70-min: color-mix(
|
||||
in oklab,
|
||||
var(--wa-color-brand) var(--wa-color-brand-if-gte-70),
|
||||
var(--wa-color-brand-70)
|
||||
);
|
||||
|
||||
/* Text color: white if key < 60, brand-10 otherwise */
|
||||
--wa-color-brand-on: color-mix(in oklab, var(--wa-color-brand-10) var(--wa-color-brand-if-gte-60), white);
|
||||
}
|
||||
36
src/styles/brand/base.css.njk
Normal file
36
src/styles/brand/base.css.njk
Normal file
@@ -0,0 +1,36 @@
|
||||
:where(:root),
|
||||
:host,
|
||||
:where([class^='wa-theme-'], [class*=' wa-theme-']),
|
||||
:where([class^='wa-palette-'], [class*=' wa-palette-']),
|
||||
:where([class^='wa-brand-'], [class*=' wa-brand-']) {
|
||||
/**
|
||||
* Conditional tokens for use in color-mix()
|
||||
* --wa-color-brand-if-lt-N ➡️ 100% if key < N, 0% otherwise
|
||||
* --wa-color-brand-if-gte-N ➡️ 100% if key >= N, 0% otherwise
|
||||
*/
|
||||
{% for tint in ['40', '50', '60', '70', '80'] %}
|
||||
--wa-color-brand-if-lt-{{ tint }}: calc(clamp(0, {{ tint }} - var(--wa-color-brand-key), 1) * 100%);
|
||||
--wa-color-brand-if-gte-{{ tint }}: calc(100% - var(--wa-color-brand-if-lt-{{ tint }}));
|
||||
{% endfor %}
|
||||
|
||||
/*
|
||||
* Convenience tokens for common tint cutoffs
|
||||
* --wa-color-brand-N-max ➡️ var(--color-brand) if key <= N, var(--color-brand-N) otherwise
|
||||
* --wa-color-brand-N-min ➡️ var(--color-brand) if key >= N, var(--color-brand-N) otherwise
|
||||
*/
|
||||
{% for tint in ['40', '50', '60', '70'] %}
|
||||
--wa-color-brand-{{ tint }}-max: color-mix(
|
||||
in oklab,
|
||||
var(--wa-color-brand) var(--wa-color-brand-if-lt-{{ tint }}),
|
||||
var(--wa-color-brand-{{ tint }})
|
||||
);
|
||||
--wa-color-brand-{{ tint }}-min: color-mix(
|
||||
in oklab,
|
||||
var(--wa-color-brand) var(--wa-color-brand-if-gte-{{ tint }}),
|
||||
var(--wa-color-brand-{{ tint }})
|
||||
);
|
||||
{% endfor %}
|
||||
|
||||
/* Text color: white if key < 60, brand-10 otherwise */
|
||||
--wa-color-brand-on: color-mix(in oklab, var(--wa-color-brand-10) var(--wa-color-brand-if-gte-60), white);
|
||||
}
|
||||
22
src/styles/brand/blue.css
Normal file
22
src/styles/brand/blue.css
Normal file
@@ -0,0 +1,22 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
:host,
|
||||
:where([class^='wa-theme-'], [class*=' wa-theme-']),
|
||||
:where([class^='wa-palette-'], [class*=' wa-palette-']),
|
||||
.wa-brand-blue {
|
||||
--wa-color-brand-95: var(--wa-color-blue-95);
|
||||
--wa-color-brand-90: var(--wa-color-blue-90);
|
||||
--wa-color-brand-80: var(--wa-color-blue-80);
|
||||
--wa-color-brand-70: var(--wa-color-blue-70);
|
||||
--wa-color-brand-60: var(--wa-color-blue-60);
|
||||
--wa-color-brand-50: var(--wa-color-blue-50);
|
||||
--wa-color-brand-40: var(--wa-color-blue-40);
|
||||
--wa-color-brand-30: var(--wa-color-blue-30);
|
||||
--wa-color-brand-20: var(--wa-color-blue-20);
|
||||
--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);
|
||||
}
|
||||
22
src/styles/brand/cyan.css
Normal file
22
src/styles/brand/cyan.css
Normal file
@@ -0,0 +1,22 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
:host,
|
||||
:where([class^='wa-theme-'], [class*=' wa-theme-']),
|
||||
:where([class^='wa-palette-'], [class*=' wa-palette-']),
|
||||
.wa-brand-cyan {
|
||||
--wa-color-brand-95: var(--wa-color-cyan-95);
|
||||
--wa-color-brand-90: var(--wa-color-cyan-90);
|
||||
--wa-color-brand-80: var(--wa-color-cyan-80);
|
||||
--wa-color-brand-70: var(--wa-color-cyan-70);
|
||||
--wa-color-brand-60: var(--wa-color-cyan-60);
|
||||
--wa-color-brand-50: var(--wa-color-cyan-50);
|
||||
--wa-color-brand-40: var(--wa-color-cyan-40);
|
||||
--wa-color-brand-30: var(--wa-color-cyan-30);
|
||||
--wa-color-brand-20: var(--wa-color-cyan-20);
|
||||
--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);
|
||||
}
|
||||
22
src/styles/brand/gray.css
Normal file
22
src/styles/brand/gray.css
Normal file
@@ -0,0 +1,22 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
:host,
|
||||
:where([class^='wa-theme-'], [class*=' wa-theme-']),
|
||||
:where([class^='wa-palette-'], [class*=' wa-palette-']),
|
||||
.wa-brand-gray {
|
||||
--wa-color-brand-95: var(--wa-color-gray-95);
|
||||
--wa-color-brand-90: var(--wa-color-gray-90);
|
||||
--wa-color-brand-80: var(--wa-color-gray-80);
|
||||
--wa-color-brand-70: var(--wa-color-gray-70);
|
||||
--wa-color-brand-60: var(--wa-color-gray-60);
|
||||
--wa-color-brand-50: var(--wa-color-gray-50);
|
||||
--wa-color-brand-40: var(--wa-color-gray-40);
|
||||
--wa-color-brand-30: var(--wa-color-gray-30);
|
||||
--wa-color-brand-20: var(--wa-color-gray-20);
|
||||
--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);
|
||||
}
|
||||
22
src/styles/brand/green.css
Normal file
22
src/styles/brand/green.css
Normal file
@@ -0,0 +1,22 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
:host,
|
||||
:where([class^='wa-theme-'], [class*=' wa-theme-']),
|
||||
:where([class^='wa-palette-'], [class*=' wa-palette-']),
|
||||
.wa-brand-green {
|
||||
--wa-color-brand-95: var(--wa-color-green-95);
|
||||
--wa-color-brand-90: var(--wa-color-green-90);
|
||||
--wa-color-brand-80: var(--wa-color-green-80);
|
||||
--wa-color-brand-70: var(--wa-color-green-70);
|
||||
--wa-color-brand-60: var(--wa-color-green-60);
|
||||
--wa-color-brand-50: var(--wa-color-green-50);
|
||||
--wa-color-brand-40: var(--wa-color-green-40);
|
||||
--wa-color-brand-30: var(--wa-color-green-30);
|
||||
--wa-color-brand-20: var(--wa-color-green-20);
|
||||
--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);
|
||||
}
|
||||
22
src/styles/brand/indigo.css
Normal file
22
src/styles/brand/indigo.css
Normal file
@@ -0,0 +1,22 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
:host,
|
||||
:where([class^='wa-theme-'], [class*=' wa-theme-']),
|
||||
:where([class^='wa-palette-'], [class*=' wa-palette-']),
|
||||
.wa-brand-indigo {
|
||||
--wa-color-brand-95: var(--wa-color-indigo-95);
|
||||
--wa-color-brand-90: var(--wa-color-indigo-90);
|
||||
--wa-color-brand-80: var(--wa-color-indigo-80);
|
||||
--wa-color-brand-70: var(--wa-color-indigo-70);
|
||||
--wa-color-brand-60: var(--wa-color-indigo-60);
|
||||
--wa-color-brand-50: var(--wa-color-indigo-50);
|
||||
--wa-color-brand-40: var(--wa-color-indigo-40);
|
||||
--wa-color-brand-30: var(--wa-color-indigo-30);
|
||||
--wa-color-brand-20: var(--wa-color-indigo-20);
|
||||
--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);
|
||||
}
|
||||
21
src/styles/brand/orange.css
Normal file
21
src/styles/brand/orange.css
Normal file
@@ -0,0 +1,21 @@
|
||||
@import url('base.css');
|
||||
|
||||
: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-key: var(--wa-color-orange-key);
|
||||
}
|
||||
22
src/styles/brand/pink.css
Normal file
22
src/styles/brand/pink.css
Normal file
@@ -0,0 +1,22 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
:host,
|
||||
:where([class^='wa-theme-'], [class*=' wa-theme-']),
|
||||
:where([class^='wa-palette-'], [class*=' wa-palette-']),
|
||||
.wa-brand-pink {
|
||||
--wa-color-brand-95: var(--wa-color-pink-95);
|
||||
--wa-color-brand-90: var(--wa-color-pink-90);
|
||||
--wa-color-brand-80: var(--wa-color-pink-80);
|
||||
--wa-color-brand-70: var(--wa-color-pink-70);
|
||||
--wa-color-brand-60: var(--wa-color-pink-60);
|
||||
--wa-color-brand-50: var(--wa-color-pink-50);
|
||||
--wa-color-brand-40: var(--wa-color-pink-40);
|
||||
--wa-color-brand-30: var(--wa-color-pink-30);
|
||||
--wa-color-brand-20: var(--wa-color-pink-20);
|
||||
--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);
|
||||
}
|
||||
22
src/styles/brand/purple.css
Normal file
22
src/styles/brand/purple.css
Normal file
@@ -0,0 +1,22 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
:host,
|
||||
:where([class^='wa-theme-'], [class*=' wa-theme-']),
|
||||
:where([class^='wa-palette-'], [class*=' wa-palette-']),
|
||||
.wa-brand-purple {
|
||||
--wa-color-brand-95: var(--wa-color-purple-95);
|
||||
--wa-color-brand-90: var(--wa-color-purple-90);
|
||||
--wa-color-brand-80: var(--wa-color-purple-80);
|
||||
--wa-color-brand-70: var(--wa-color-purple-70);
|
||||
--wa-color-brand-60: var(--wa-color-purple-60);
|
||||
--wa-color-brand-50: var(--wa-color-purple-50);
|
||||
--wa-color-brand-40: var(--wa-color-purple-40);
|
||||
--wa-color-brand-30: var(--wa-color-purple-30);
|
||||
--wa-color-brand-20: var(--wa-color-purple-20);
|
||||
--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);
|
||||
}
|
||||
22
src/styles/brand/red.css
Normal file
22
src/styles/brand/red.css
Normal file
@@ -0,0 +1,22 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
:host,
|
||||
:where([class^='wa-theme-'], [class*=' wa-theme-']),
|
||||
:where([class^='wa-palette-'], [class*=' wa-palette-']),
|
||||
.wa-brand-red {
|
||||
--wa-color-brand-95: var(--wa-color-red-95);
|
||||
--wa-color-brand-90: var(--wa-color-red-90);
|
||||
--wa-color-brand-80: var(--wa-color-red-80);
|
||||
--wa-color-brand-70: var(--wa-color-red-70);
|
||||
--wa-color-brand-60: var(--wa-color-red-60);
|
||||
--wa-color-brand-50: var(--wa-color-red-50);
|
||||
--wa-color-brand-40: var(--wa-color-red-40);
|
||||
--wa-color-brand-30: var(--wa-color-red-30);
|
||||
--wa-color-brand-20: var(--wa-color-red-20);
|
||||
--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);
|
||||
}
|
||||
22
src/styles/brand/yellow.css
Normal file
22
src/styles/brand/yellow.css
Normal file
@@ -0,0 +1,22 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
:host,
|
||||
:where([class^='wa-theme-'], [class*=' wa-theme-']),
|
||||
:where([class^='wa-palette-'], [class*=' wa-palette-']),
|
||||
.wa-brand-yellow {
|
||||
--wa-color-brand-95: var(--wa-color-yellow-95);
|
||||
--wa-color-brand-90: var(--wa-color-yellow-90);
|
||||
--wa-color-brand-80: var(--wa-color-yellow-80);
|
||||
--wa-color-brand-70: var(--wa-color-yellow-70);
|
||||
--wa-color-brand-60: var(--wa-color-yellow-60);
|
||||
--wa-color-brand-50: var(--wa-color-yellow-50);
|
||||
--wa-color-brand-40: var(--wa-color-yellow-40);
|
||||
--wa-color-brand-30: var(--wa-color-yellow-30);
|
||||
--wa-color-brand-20: var(--wa-color-yellow-20);
|
||||
--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);
|
||||
}
|
||||
13
src/styles/brand/{{ hue }}.css.njk
Normal file
13
src/styles/brand/{{ hue }}.css.njk
Normal file
@@ -0,0 +1,13 @@
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
:host,
|
||||
:where([class^='wa-theme-'], [class*=' wa-theme-']),
|
||||
:where([class^='wa-palette-'], [class*=' wa-palette-']),
|
||||
.wa-brand-{{ hue }} {
|
||||
{%- for tint in tints | reverse %}
|
||||
--wa-color-brand-{{ tint }}: var(--wa-color-{{ hue }}-{{ tint }});
|
||||
{%- endfor %}
|
||||
--wa-color-brand: var(--wa-color-{{ hue }});
|
||||
--wa-color-brand-key: var(--wa-color-{{ hue }}-key);
|
||||
}
|
||||
@@ -2,107 +2,129 @@
|
||||
:host,
|
||||
:where([class^='wa-theme-'], [class*=' wa-theme-']),
|
||||
.wa-palette-anodized {
|
||||
--wa-color-red-95: #fbeeeb /* oklch(95.851% 0.01469 33.071) */;
|
||||
--wa-color-red-90: #f8e0d9 /* oklch(92.431% 0.02823 36.865) */;
|
||||
--wa-color-red-80: #efbdb1 /* oklch(83.972% 0.06035 33.801) */;
|
||||
--wa-color-red-70: #e39f8d /* oklch(76.38% 0.08577 35.424) */;
|
||||
--wa-color-red-60: #d1806b /* oklch(68.083% 0.10573 35.329) */;
|
||||
--wa-color-red-50: #b35e49 /* oklch(57.764% 0.11532 34.927) */;
|
||||
--wa-color-red-40: #8e4330 /* oklch(47.527% 0.10671 35.325) */;
|
||||
--wa-color-red-30: #733223 /* oklch(40.293% 0.09564 34.326) */;
|
||||
--wa-color-red-20: #562317 /* oklch(32.748% 0.07905 34.548) */;
|
||||
--wa-color-red-10: #36130a /* oklch(23.979% 0.05887 35.518) */;
|
||||
--wa-color-red-05: #240a05 /* oklch(18.708% 0.04625 34.412) */;
|
||||
--wa-color-red: var(--wa-color-red-50);
|
||||
--wa-color-red-95: #ffefee /* oklch(96.448% 0.01742 21.806) */;
|
||||
--wa-color-red-90: #fededd /* oklch(92.674% 0.03556 19.95) */;
|
||||
--wa-color-red-80: #fabab8 /* oklch(84.695% 0.0745 20.791) */;
|
||||
--wa-color-red-70: #f19695 /* oklch(76.595% 0.11008 20.609) */;
|
||||
--wa-color-red-60: #d47c7e /* oklch(68.199% 0.10963 18.885) */;
|
||||
--wa-color-red-50: #af5e5a /* oklch(57.551% 0.10599 23.859) */;
|
||||
--wa-color-red-40: #89453f /* oklch(47.393% 0.09388 26.419) */;
|
||||
--wa-color-red-30: #6d352f /* oklch(40.077% 0.08085 27.557) */;
|
||||
--wa-color-red-20: #522521 /* oklch(32.622% 0.06818 26.677) */;
|
||||
--wa-color-red-10: #331512 /* oklch(23.979% 0.04918 27.221) */;
|
||||
--wa-color-red-05: #220b09 /* oklch(18.625% 0.04007 26.766) */;
|
||||
--wa-color-red: var(--wa-color-red-70);
|
||||
--wa-color-red-key: 70;
|
||||
|
||||
--wa-color-yellow-95: #fff2b8 /* oklch(95.764% 0.07527 96.955) */;
|
||||
--wa-color-yellow-90: #ffe578 /* oklch(92.163% 0.13215 96.194) */;
|
||||
--wa-color-yellow-80: #eac46c /* oklch(83.54% 0.11551 86.482) */;
|
||||
--wa-color-yellow-70: #d5a766 /* oklch(75.67% 0.0989 74.916) */;
|
||||
--wa-color-yellow-60: #bd8a5e /* oklch(67.327% 0.08618 61.351) */;
|
||||
--wa-color-yellow-50: #9d6a4f /* oklch(57.149% 0.0766 48.607) */;
|
||||
--wa-color-yellow-40: #794f3c /* oklch(46.965% 0.06418 45.076) */;
|
||||
--wa-color-yellow-30: #603d2f /* oklch(39.723% 0.05466 42.556) */;
|
||||
--wa-color-yellow-20: #482b21 /* oklch(32.256% 0.04681 39.773) */;
|
||||
--wa-color-yellow-10: #2c1912 /* oklch(23.678% 0.03339 40.974) */;
|
||||
--wa-color-yellow-05: #1d0e0a /* oklch(18.423% 0.02718 35.681) */;
|
||||
--wa-color-yellow: var(--wa-color-yellow-90);
|
||||
--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) */;
|
||||
--wa-color-yellow-70: #d4a85b /* oklch(75.609% 0.10842 79.92) */;
|
||||
--wa-color-yellow-60: #bf8b4a /* oklch(67.445% 0.10375 70.58) */;
|
||||
--wa-color-yellow-50: #a06938 /* oklch(56.952% 0.09579 60.65) */;
|
||||
--wa-color-yellow-40: #7f4d29 /* oklch(47.038% 0.08433 54.746) */;
|
||||
--wa-color-yellow-30: #663b1e /* oklch(39.848% 0.07399 52.93) */;
|
||||
--wa-color-yellow-20: #4c2a14 /* oklch(32.393% 0.0609 51.89) */;
|
||||
--wa-color-yellow-10: #2f1809 /* oklch(23.743% 0.04463 52.113) */;
|
||||
--wa-color-yellow-05: #1f0e04 /* oklch(18.6% 0.03542 53.04) */;
|
||||
--wa-color-yellow: var(--wa-color-yellow-80);
|
||||
--wa-color-yellow-key: 80;
|
||||
|
||||
--wa-color-green-95: #dcf8ea /* oklch(95.5% 0.03463 164.16) */;
|
||||
--wa-color-green-90: #bcf1d8 /* oklch(91.473% 0.06399 164.58) */;
|
||||
--wa-color-green-80: #6dddad /* oklch(81.852% 0.12484 163.42) */;
|
||||
--wa-color-green-70: #33c685 /* oklch(73.519% 0.15428 158.98) */;
|
||||
--wa-color-green-60: #0a6 /* oklch(64.917% 0.15588 156.54) */;
|
||||
--wa-color-green-50: #008754 /* oklch(54.923% 0.12782 158.29) */;
|
||||
--wa-color-green-40: #006646 /* oklch(45.146% 0.09722 163.25) */;
|
||||
--wa-color-green-30: #00503b /* oklch(38.299% 0.07764 167.91) */;
|
||||
--wa-color-green-20: #003a2d /* oklch(31.045% 0.06006 172.22) */;
|
||||
--wa-color-green-10: #00231b /* oklch(22.853% 0.04344 174.1) */;
|
||||
--wa-color-green-05: #001610 /* oklch(17.856% 0.03405 173.73) */;
|
||||
--wa-color-green: var(--wa-color-green-60);
|
||||
--wa-color-green-95: #ebf6e0 /* oklch(95.901% 0.03126 128.83) */;
|
||||
--wa-color-green-90: #d3efbe /* oklch(91.922% 0.0713 131.97) */;
|
||||
--wa-color-green-80: #96db86 /* oklch(82.501% 0.13433 139.79) */;
|
||||
--wa-color-green-70: #68c17c /* oklch(73.758% 0.13164 149.51) */;
|
||||
--wa-color-green-60: #4aa672 /* oklch(65.518% 0.11814 156.17) */;
|
||||
--wa-color-green-50: #2e845d /* oklch(55.093% 0.10286 160.26) */;
|
||||
--wa-color-green-40: #1b6548 /* oklch(45.377% 0.0863 162.75) */;
|
||||
--wa-color-green-30: #104f38 /* oklch(38.219% 0.07374 163.76) */;
|
||||
--wa-color-green-20: #083a29 /* oklch(31.135% 0.05985 165.09) */;
|
||||
--wa-color-green-10: #032317 /* oklch(22.865% 0.0446 164.36) */;
|
||||
--wa-color-green-05: #01160d /* oklch(17.827% 0.03566 164.38) */;
|
||||
--wa-color-green: var(--wa-color-green-80);
|
||||
--wa-color-green-key: 80;
|
||||
|
||||
--wa-color-teal-95: #cbfaf9 /* oklch(95.226% 0.04791 194.77) */;
|
||||
--wa-color-teal-90: #9ff4f3 /* oklch(91.294% 0.08228 194.86) */;
|
||||
--wa-color-teal-80: #1adfdb /* oklch(81.816% 0.13749 192.46) */;
|
||||
--wa-color-teal-70: #00c3be /* oklch(73.815% 0.1269 191.5) */;
|
||||
--wa-color-teal-60: #1aa6a0 /* oklch(65.653% 0.10892 190.15) */;
|
||||
--wa-color-teal-50: #15837e /* oklch(55.258% 0.09092 189.97) */;
|
||||
--wa-color-teal-40: #10635e /* oklch(45.271% 0.07391 188.55) */;
|
||||
--wa-color-teal-30: #0c4e4a /* oklch(38.439% 0.06211 188.63) */;
|
||||
--wa-color-teal-20: #083936 /* oklch(31.289% 0.04971 188.74) */;
|
||||
--wa-color-teal-10: #04221f /* oklch(22.881% 0.03588 185.7) */;
|
||||
--wa-color-teal-05: #021513 /* oklch(17.794% 0.0276 186.43) */;
|
||||
--wa-color-teal: var(--wa-color-teal-80);
|
||||
--wa-color-cyan-95: #cafbfd /* oklch(95.491% 0.04984 199.43) */;
|
||||
--wa-color-cyan-90: #97f5f8 /* oklch(91.286% 0.08952 198.32) */;
|
||||
--wa-color-cyan-80: #3fdee7 /* oklch(82.5% 0.12703 200.7) */;
|
||||
--wa-color-cyan-70: #00c0d4 /* oklch(73.886% 0.127 207.98) */;
|
||||
--wa-color-cyan-60: #00a3bf /* oklch(65.895% 0.11686 215.74) */;
|
||||
--wa-color-cyan-50: #007fa2 /* oklch(55.537% 0.10592 225.72) */;
|
||||
--wa-color-cyan-40: #006082 /* oklch(45.89% 0.09291 231.37) */;
|
||||
--wa-color-cyan-30: #014a6b /* oklch(38.746% 0.08372 236.7) */;
|
||||
--wa-color-cyan-20: #003652 /* oklch(31.672% 0.07138 239.03) */;
|
||||
--wa-color-cyan-10: #002034 /* oklch(23.245% 0.05366 240.46) */;
|
||||
--wa-color-cyan-05: #001422 /* oklch(18.196% 0.041 239.02) */;
|
||||
--wa-color-cyan: var(--wa-color-cyan-80);
|
||||
--wa-color-cyan-key: 80;
|
||||
|
||||
--wa-color-blue-95: #ecf3ff /* oklch(96.238% 0.01777 261.34) */;
|
||||
--wa-color-blue-90: #d9e7ff /* oklch(92.484% 0.03602 261.3) */;
|
||||
--wa-color-blue-80: #abcaff /* oklch(83.49% 0.082 261.03) */;
|
||||
--wa-color-blue-70: #83b0fe /* oklch(75.655% 0.12308 260.97) */;
|
||||
--wa-color-blue-60: #5593fe /* oklch(67.328% 0.1713 260.55) */;
|
||||
--wa-color-blue-50: #3d74d2 /* oklch(57.024% 0.15628 260.38) */;
|
||||
--wa-color-blue-40: #2e589f /* oklch(46.829% 0.12443 260.23) */;
|
||||
--wa-color-blue-30: #24457c /* oklch(39.571% 0.10098 260.03) */;
|
||||
--wa-color-blue-20: #1b325b /* oklch(32.168% 0.07881 261.06) */;
|
||||
--wa-color-blue-10: #101d35 /* oklch(23.292% 0.05008 261.75) */;
|
||||
--wa-color-blue-05: #0a1222 /* oklch(18.375% 0.0352 263.19) */;
|
||||
--wa-color-blue: var(--wa-color-blue-60);
|
||||
--wa-color-blue-95: #e5f6ff /* oklch(96.324% 0.02167 230.54) */;
|
||||
--wa-color-blue-90: #c7ebff /* oklch(92.061% 0.04647 232.28) */;
|
||||
--wa-color-blue-80: #8ed2f9 /* oklch(83.243% 0.08774 234.44) */;
|
||||
--wa-color-blue-70: #5db7f3 /* oklch(74.844% 0.12234 240.33) */;
|
||||
--wa-color-blue-60: #309aee /* oklch(66.707% 0.15592 247.36) */;
|
||||
--wa-color-blue-50: #0774df /* oklch(56.704% 0.18422 255.07) */;
|
||||
--wa-color-blue-40: #0152c1 /* oklch(47.066% 0.187 259.59) */;
|
||||
--wa-color-blue-30: #003ea2 /* oklch(40.154% 0.17138 260.8) */;
|
||||
--wa-color-blue-20: #002b7f /* oklch(32.816% 0.14852 261.7) */;
|
||||
--wa-color-blue-10: #001853 /* oklch(24.067% 0.11176 262.05) */;
|
||||
--wa-color-blue-05: #000e39 /* oklch(18.796% 0.0868 261.98) */;
|
||||
--wa-color-blue: var(--wa-color-blue-40);
|
||||
--wa-color-blue-key: 40;
|
||||
|
||||
--wa-color-indigo-95: #f5efff /* oklch(96.142% 0.02215 302.94) */;
|
||||
--wa-color-indigo-90: #ede0ff /* oklch(92.64% 0.04374 304.51) */;
|
||||
--wa-color-indigo-80: #d7bdff /* oklch(84.265% 0.09443 302.44) */;
|
||||
--wa-color-indigo-70: #bf9efe /* oklch(76.541% 0.13786 298.94) */;
|
||||
--wa-color-indigo-60: #a280fa /* oklch(68.571% 0.17519 294.14) */;
|
||||
--wa-color-indigo-50: #7f5bec /* oklch(58.581% 0.20817 289.86) */;
|
||||
--wa-color-indigo-40: #603dc8 /* oklch(48.557% 0.20262 287.93) */;
|
||||
--wa-color-indigo-30: #4c2ba5 /* oklch(41.119% 0.18229 287.94) */;
|
||||
--wa-color-indigo-20: #381d7f /* oklch(33.629% 0.15279 287.75) */;
|
||||
--wa-color-indigo-10: #210e53 /* oklch(24.589% 0.11518 287.13) */;
|
||||
--wa-color-indigo-05: #150739 /* oklch(19.258% 0.08946 287.81) */;
|
||||
--wa-color-indigo: var(--wa-color-indigo-50);
|
||||
--wa-color-indigo-95: #f0f2ff /* oklch(96.341% 0.0175 279.06) */;
|
||||
--wa-color-indigo-90: #e1e4fe /* oklch(92.448% 0.03538 280.45) */;
|
||||
--wa-color-indigo-80: #bfc6fe /* oklch(84.035% 0.07865 279.01) */;
|
||||
--wa-color-indigo-70: #9fa8fc /* oklch(75.744% 0.12084 278.41) */;
|
||||
--wa-color-indigo-60: #828bf6 /* oklch(67.774% 0.15666 277.72) */;
|
||||
--wa-color-indigo-50: #6166e8 /* oklch(57.738% 0.19274 276.85) */;
|
||||
--wa-color-indigo-40: #4743d0 /* oklch(48.143% 0.20939 276.34) */;
|
||||
--wa-color-indigo-30: #372db3 /* oklch(41.009% 0.20044 276.19) */;
|
||||
--wa-color-indigo-20: #291b90 /* oklch(33.874% 0.17803 276.48) */;
|
||||
--wa-color-indigo-10: #170a61 /* oklch(24.844% 0.13871 276.05) */;
|
||||
--wa-color-indigo-05: #0e0445 /* oklch(19.553% 0.11104 276.58) */;
|
||||
--wa-color-indigo: var(--wa-color-indigo-40);
|
||||
--wa-color-indigo-key: 40;
|
||||
|
||||
--wa-color-violet-95: #fbedfd /* oklch(96.172% 0.02584 321.94) */;
|
||||
--wa-color-violet-90: #f7defa /* oklch(92.922% 0.04588 322.59) */;
|
||||
--wa-color-violet-80: #eeb6f5 /* oklch(84.707% 0.10462 322.7) */;
|
||||
--wa-color-violet-70: #e590f0 /* oklch(77.326% 0.15957 322.79) */;
|
||||
--wa-color-violet-60: #d56ae2 /* oklch(69.373% 0.19861 323.18) */;
|
||||
--wa-color-violet-50: #b547be /* oklch(59.084% 0.2006 324.63) */;
|
||||
--wa-color-violet-40: #952598 /* oklch(48.958% 0.19474 326.91) */;
|
||||
--wa-color-violet-30: #751d79 /* oklch(41.379% 0.16261 326.12) */;
|
||||
--wa-color-violet-20: #561658 /* oklch(33.585% 0.12607 326.65) */;
|
||||
--wa-color-violet-10: #340d36 /* oklch(24.532% 0.08615 325.79) */;
|
||||
--wa-color-violet-05: #210822 /* oklch(19.065% 0.0603 326.11) */;
|
||||
--wa-color-violet: var(--wa-color-violet-50);
|
||||
--wa-color-purple-95: #fbefff /* oklch(96.613% 0.02465 317.72) */;
|
||||
--wa-color-purple-90: #f7ddff /* oklch(92.895% 0.0531 318.2) */;
|
||||
--wa-color-purple-80: #edb7ff /* oklch(85.165% 0.11316 317.53) */;
|
||||
--wa-color-purple-70: #dc91fe /* oklch(77.161% 0.16803 314.62) */;
|
||||
--wa-color-purple-60: #c670f2 /* oklch(69.273% 0.19845 312.9) */;
|
||||
--wa-color-purple-50: #a34bda /* oklch(58.947% 0.21432 309.63) */;
|
||||
--wa-color-purple-40: #802fb6 /* oklch(48.965% 0.20261 307.82) */;
|
||||
--wa-color-purple-30: #661f96 /* oklch(41.488% 0.1815 307.02) */;
|
||||
--wa-color-purple-20: #4c1372 /* oklch(33.753% 0.1509 306.93) */;
|
||||
--wa-color-purple-10: #30074a /* oklch(24.849% 0.1139 307.37) */;
|
||||
--wa-color-purple-05: #200333 /* oklch(19.47% 0.0902 307.77) */;
|
||||
--wa-color-purple: var(--wa-color-purple-50);
|
||||
--wa-color-purple-key: 50;
|
||||
|
||||
--wa-color-gray-95: #f1f2f4 /* oklch(96.095% 0.00288 264.54) */;
|
||||
--wa-color-gray-90: #e2e5e8 /* oklch(92.046% 0.00521 247.88) */;
|
||||
--wa-color-gray-80: #c2c9d0 /* oklch(83.255% 0.01246 247.98) */;
|
||||
--wa-color-gray-70: #a6b0ba /* oklch(75.244% 0.01824 248.07) */;
|
||||
--wa-color-gray-60: #8897a3 /* oklch(66.841% 0.02477 242.06) */;
|
||||
--wa-color-gray-50: #657888 /* oklch(56.309% 0.0335 243.8) */;
|
||||
--wa-color-gray-40: #435c6f /* oklch(46.235% 0.04323 241.6) */;
|
||||
--wa-color-gray-30: #2d485d /* oklch(38.946% 0.04864 242.89) */;
|
||||
--wa-color-gray-20: #18354a /* oklch(31.701% 0.05142 242.24) */;
|
||||
--wa-color-gray-10: #012034 /* oklch(23.295% 0.05299 241.23) */;
|
||||
--wa-color-gray-05: #001421 /* oklch(18.122% 0.03959 237.04) */;
|
||||
--wa-color-pink-95: #ffedf6 /* oklch(96.291% 0.02268 345.75) */;
|
||||
--wa-color-pink-90: #feddee /* oklch(92.966% 0.04248 345.5) */;
|
||||
--wa-color-pink-80: #feb2e3 /* oklch(84.978% 0.10743 340.7) */;
|
||||
--wa-color-pink-70: #f688de /* oklch(77.315% 0.16826 335.79) */;
|
||||
--wa-color-pink-60: #e560d6 /* oklch(69.598% 0.21242 332.2) */;
|
||||
--wa-color-pink-50: #c234bf /* oklch(59.18% 0.23062 328.94) */;
|
||||
--wa-color-pink-40: #9b189b /* oklch(49.224% 0.21096 328.21) */;
|
||||
--wa-color-pink-30: #7c097e /* oklch(41.626% 0.18577 327.45) */;
|
||||
--wa-color-pink-20: #5e0160 /* oklch(34.016% 0.15613 327.29) */;
|
||||
--wa-color-pink-10: #3c003c /* oklch(24.992% 0.11486 328.36) */;
|
||||
--wa-color-pink-05: #280029 /* oklch(19.507% 0.09014 327.31) */;
|
||||
--wa-color-pink: var(--wa-color-pink-50);
|
||||
--wa-color-pink-key: 50;
|
||||
|
||||
--wa-color-gray-95: #f2f2f2 /* oklch(96.115% 0 none) */;
|
||||
--wa-color-gray-90: #e6e6e6 /* oklch(92.494% 0 none) */;
|
||||
--wa-color-gray-80: #cacaca /* oklch(83.901% 0 none) */;
|
||||
--wa-color-gray-70: #aeafaf /* oklch(75.321% 0.00114 197.13) */;
|
||||
--wa-color-gray-60: #959595 /* oklch(66.984% 0 none) */;
|
||||
--wa-color-gray-50: #757575 /* oklch(56.241% 0 none) */;
|
||||
--wa-color-gray-40: #595959 /* oklch(46.4% 0 none) */;
|
||||
--wa-color-gray-30: #454444 /* oklch(38.762% 0.00135 17.215) */;
|
||||
--wa-color-gray-20: #323232 /* oklch(31.714% 0 none) */;
|
||||
--wa-color-gray-10: #1d1d1d /* oklch(23.075% 0 none) */;
|
||||
--wa-color-gray-05: #121212 /* oklch(18.22% 0 none) */;
|
||||
--wa-color-gray: var(--wa-color-gray-40);
|
||||
--wa-color-gray-key: 40;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user