Compare commits

..

1 Commits

Author SHA1 Message Date
Kelsey Jackson
e3dbe8c3f9 fixed native carat 2025-01-03 16:03:07 -06:00
139 changed files with 1521 additions and 1327 deletions

View File

@@ -31,7 +31,7 @@ jobs:
- name: Lint
run: npm run prettier
- name: Build
run: npm run build:alpha
run: npm run build
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run CSR tests

View File

@@ -30,7 +30,7 @@ jobs:
run: npm run prettier
- name: Build
run: npm run build:alpha
run: npm run build
- name: Install Playwright
run: npx playwright install --with-deps

View File

@@ -48,9 +48,6 @@ export default function (eleventyConfig) {
return `https://early.webawesome.com/webawesome@${packageData.version}/dist/` + location.replace(/^\//, '');
});
// Paired shortcodes - {% shortCode %}content{% endShortCode %}
eleventyConfig.addPairedShortcode('markdown', content => markdown.render(content || ''));
// Helpers
// Remove elements that have [data-alpha="remove"]

View File

@@ -4,9 +4,10 @@
{% for category, pages in allPages | groupByTags(categories) -%}
<h2 class="index-category">{{ category | getCategoryTitle(categories) }}</h2>
{%- for page in pages -%}
{%- if not page.data.parent or listChildren -%}
{%- if not page.data.unlisted and not page.data.parent -%}
{% include "page-card.njk" %}
{%- endif -%}
{%- endfor -%}
{%- endfor -%}
</section>

View File

@@ -1,5 +1,4 @@
{%- if not page.data.unlisted -%}
<a href="{{ page.url }}"{{ page.data.keywords | attr('data-keywords') }}>
<a href="{{ page.url }}" data-keywords="{{ page.data.keywords }}">
<wa-card with-header>
<div slot="header">
{% include "svgs/" + (page.data.icon or "thumbnail-placeholder") + ".njk" %}
@@ -7,4 +6,3 @@
<span class="page-name">{{ page.data.title }}</span>
</wa-card>
</a>
{% endif %}

View File

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

View File

@@ -1,16 +0,0 @@
{% if not (isAlpha and page.data.noAlpha) and page.fileSlug != tag and not page.data.unlisted -%}
<li>
<a href="/docs/{{ tag }}/{{ page.fileSlug }}">{{ page.data.title }}</a>
{% if page.data.status == 'experimental' %}<wa-icon name="flask"></wa-icon>{% endif %}
{% if page.data.isPro %}<wa-badge class="pro">PRO</wa-badge>{% endif %}
{% set children = page.data.children %}
{% if children.length > 0 %}
<ul>
{% for page in children %}
{% include 'sidebar-link.njk' %}
{% endfor %}
</ul>
{% endif %}
</li>
{%- endif %}

View File

@@ -19,13 +19,121 @@
<li><a href="/docs/resources/changelog">Changelog</a></li>
</ul>
{% for tag, title in {
'components': 'Components',
'native': 'Native Styles',
'utilities': 'Style Utilities',
'layout': 'Layout',
'patterns': 'Patterns',
'theming': 'Theming'
} %}
{% include 'sidebar-group.njk' %}
{# Components #}
<wa-details {{ 'open' if '/components/' in page.url }}>
<h2 slot=summary>
<a href="/docs/components/" title="Overview">Components
<wa-icon name="grid-2"></wa-icon>
</a>
</h2>
<ul>
{% for component in collections.components | sort %}
{% if not component.data.parent and not (isAlpha and component.data.noAlpha) and not component.data.unlisted %}
<li>
<a href="/docs/components/{{ component.fileSlug }}">{{ component.data.title }}</a>
{% if components[component.fileSlug].status == 'experimental' %}<wa-icon name="flask"></wa-icon>{% endif %}
{% if component.data.isPro %}<wa-badge class="pro">PRO</wa-badge>{% endif %}
<ul>
{% for child in collections.components | sort %}
{% if child.data.parent == component.fileSlug and not (isAlpha and child.data.noAlpha) %}
<li>
<a href="/docs/components/{{ child.fileSlug }}">{{ child.data.title }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
</li>
{% endif %}
{% endfor %}
</ul>
</wa-details>
<wa-details {{ 'open' if '/native/' in page.url }}>
<h2 slot=summary>
<a href="/docs/native/" title="Overview">Native Styles
<wa-icon name="grid-2"></wa-icon>
</a>
</h2>
<ul>
{% for page in collections.native | sort %}
{% if page.fileSlug != 'native' and not page.unlisted %}
<li>
<a href="/docs/native/{{ page.fileSlug }}">{{ page.data.title }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
</wa-details>
<wa-details {{ 'open' if '/utilities/' in page.url }}>
<h2 slot=summary>
<a href="/docs/utilities/" title="Overview">Style Utilities
<wa-icon name="grid-2"></wa-icon>
</a>
</h2>
<ul>
{% for page in collections.utilities | sort %}
{% if page.fileSlug != 'utilities' and not page.unlisted %}
<li>
<a href="/docs/utilities/{{ page.fileSlug }}">{{ page.data.title }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
</wa-details>
{# Layout #}
<h2>
<a href="/docs/layout" title="Overview">Layout
<wa-icon name="grid-2"></wa-icon>
</a>
</h2>
<ul>
{% for page in collections.layout | sort %}
{% if page.fileSlug != 'layout' and not page.unlisted %}
<li>
<a href="{{ page.url }}">{{ page.data.title }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
{# Patterns #}
{% if not isAlpha %}
<h2>Patterns</h2>
<ul>
<li><a href="/docs/patterns/app">Web App</a></li>
<li><a href="/docs/patterns/ecommerce">E-commerce</a>
<ul>
<li><a href="/docs/patterns/ecommerce-product-review">Product Reviews</a></li>
<li><a href="/docs/patterns/ecommerce-product-list">Product Lists</a></li>
<li><a href="/docs/patterns/ecommerce-category-preview">Category Previews</a></li>
<li><a href="/docs/patterns/ecommerce-shopping-cart">Shopping Carts</a></li>
<li><a href="/docs/patterns/ecommerce-category-filter">Category Filters</a></li>
<li><a href="/docs/patterns/ecommerce-product-detail">Product Detail</a></li>
<li><a href="/docs/patterns/ecommerce-order-summary">Order Summaries</a></li>
<li><a href="/docs/patterns/ecommerce-order-history">Order History</a></li>
</ul>
</li>
<li><a href="/docs/patterns/blog">Blog</a></li>
<li><a href="/docs/patterns/news">News</a></li>
</ul>
{% endif %}
{# Theming #}
<h2>
<a href="/docs/theming" title="Overview">Theming
<wa-icon name="grid-2"></wa-icon>
</a>
</h2>
<ul>
{% for page in collections.theming | sort %}
{% if page.fileSlug != 'theming' and not page.unlisted %}
<li>
<a href="/docs/theming/{{ page.fileSlug }}">{{ page.data.title }}</a>
</li>
{% endif %}
{% endfor %}
</ul>

View File

Before

Width:  |  Height:  |  Size: 337 B

After

Width:  |  Height:  |  Size: 337 B

View File

@@ -1,37 +0,0 @@
{% set hasSidebar = true %}
{% set hasOutline = true %}
{% extends '../_includes/base.njk' %}
{# Component header #}
{% block beforeContent %}
<h1 class="title">{{ title }}</h1>
<div class="block-info">
{% set snippets = (elements or element or snippets or snippet) | dict %}
{% for snippet, link in snippets %}
{% if snippet %}
<code class="class">
{%- if link -%}
<a href="{{ link }}">{{ snippet }}</a>
{%- else -%}
{{ snippet }}
{%- endif-%}
</code>
{%- endif %}
{% endfor %}
{% include '../_includes/status.njk' %}
</div>
{% if description %}
<p class="summary">
{{ description | inlineMarkdown | safe }}
</p>
{% endif %}
{% block notes %}{% endblock %}
{% endblock %}
{# Content #}
{% block content %}
{{ content | safe }}
{% endblock %}

View File

@@ -1,8 +1,23 @@
{% extends '../_layouts/block.njk' %}
{% set hasSidebar = true %}
{% set hasOutline = true %}
{% set component = components[page.fileSlug] %}
{% set description = component.summary %}
{% set status = component.status %}
{% set since = component.since %}
{% extends '../_includes/base.njk' %}
{# Component header #}
{% block notes %}
{% block beforeContent %}
<h1 class="title">{{ title }}</h1>
<div class="component-info">
<code class="tag">&lt;{{ component.tagName }}&gt;</code>
{% include '../_includes/status.njk' %}
</div>
<p class="summary">
{{ component.summary | inlineMarkdown | safe }}
</p>
{% if native %}
<wa-callout variant="success">
<wa-icon slot="icon" name="lightbulb" variant="regular"></wa-icon>
@@ -12,6 +27,11 @@
{% endif %}
{% endblock %}
{# Content #}
{% block content %}
{{ content | safe }}
{% endblock %}
{# Component API #}
{% block afterContent %}
{# Slots #}

View File

@@ -1,18 +1,37 @@
{% extends '../_layouts/block.njk' %}
{% set hasSidebar = true %}
{% set hasOutline = true %}
{% extends '../_includes/base.njk' %}
{# Component header #}
{% block notes %}
{% block beforeContent %}
<h1 class="title">{{ title }}</h1>
<div class="component-info">
{% for tag, url in elements %}
<code class="tag"><a href="{{ url }}">{{ tag }}</a></code>
{% endfor %}
{% include '../_includes/status.njk' %}
</div>
{% if description -%}
<p class="summary">{{ description | inlineMarkdown | safe }}</p>
{%- endif %}
{% if component %}
<wa-callout variant="success">
<wa-icon slot="icon" name="lightbulb" variant="regular"></wa-icon>
Want to do more?
Check out the {% for name in (component | toList) -%}
Check out the {% for name in (component | toArray) -%}
{{ ' and ' if loop.last and not loop.first }}<a href="/docs/components/{{ name }}"><code>&lt;wa-{{ name }}&gt;</code></a>{{ ', ' if not loop.last }}
{%- endfor %} component{{ 's' if (component | isList) }}</a>!
{%- endfor %} component{{ 's' if (component | isArray) }}</a>!
</wa-callout>
{% endif %}
{% endblock %}
{# Content #}
{% block content %}
{{ content | safe }}
{% endblock %}
{# Component API #}
{% block afterContent %}
{# Slots #}
{% if css_file %}

View File

@@ -1,25 +0,0 @@
---
layout: page-outline
tags: ["overview"]
---
{% set forTag = forTag or (page.url | split('/') | last) %}
{% if description %}
<div class="index-summary">{{ description | markdown | safe }}</div>
{% endif %}
<div id="block-filter">
<wa-input type="search" placeholder="Search {{ title }}" clearable autofocus>
<wa-icon slot="prefix" name="search"></wa-icon>
</wa-input>
</div>
{% set allPages = collections[forTag] %}
{% include "grouped-pages.njk" %}
<link href="/assets/styles/filter.css" rel="stylesheet">
<script type="module" src="/assets/scripts/filter.js"></script>
{% if content | trim %}
<br> {# Temp fix for spacing issue #}
{{ content | safe }}
{% endif %}

View File

@@ -0,0 +1,4 @@
{% set hasSidebar = true %}
{% set hasOutline = true %}
{% extends "../_includes/base.njk" %}

View File

@@ -0,0 +1,25 @@
{% set hasSidebar = true %}
{% set hasOutline = true %}
{% extends '../_includes/base.njk' %}
{# Component header #}
{% block beforeContent %}
<h1 class="title">{{ title }}</h1>
<div class="component-info">
{% for className in classes %}
<code class="class">.{{ className }}</code>
{% endfor %}
{% include '../_includes/status.njk' %}
</div>
{% if description -%}
<p class="summary">{{ description | inlineMarkdown | safe }}</p>
{%- endif %}
{% endblock %}
{# Content #}
{% block content %}
{{ content | safe }}
{% endblock %}

View File

@@ -38,12 +38,8 @@ export function getTitleFromUrl(url, collection) {
return item?.data.title || '';
}
export function split(text, separator) {
return (text + '').split(separator).filter(Boolean);
}
export function breadcrumbs(url, { withCurrent = false } = {}) {
const parts = split(url, '/');
const parts = url.split('/').filter(Boolean);
const ret = [];
while (parts.length) {
@@ -74,30 +70,12 @@ export function breadcrumbs(url, { withCurrent = false } = {}) {
return ret;
}
export function isObject(value) {
return typeof value === 'object' && value !== null && !Array.isArray(value);
export function isArray(value) {
return Array.isArray(value);
}
export function isList(value) {
return Array.isArray(value) || value instanceof Set;
}
/** Get an Array or Set */
export function toList(value) {
return isList(value) ? value : [value];
}
/**
* Convert any value to something that can be iterated over with a for key, value loop.
* Arrays and sets will be converted to a Map of value -> undefined
*/
export function dict(value) {
if (value instanceof Map || isObject(value)) {
return value;
}
let list = toList(value);
return new Map([...list].map(item => [item, undefined]));
export function toArray(value) {
return isArray(value) ? value : [value];
}
export function deepValue(obj, key) {
@@ -105,15 +83,11 @@ export function deepValue(obj, key) {
return key.reduce((subObj, property) => subObj?.[property], obj);
}
export function isNumeric(value) {
function isNumeric(value) {
return typeof value === 'number' || (typeof value === 'string' && !isNaN(value));
}
export function isString(value) {
return typeof value === 'string';
}
export function isEmpty(value) {
function isEmpty(value) {
return value === null || value === undefined || value === '';
}
@@ -139,13 +113,13 @@ function compare(a, b) {
return (a + '').localeCompare(b);
}
/** Sort an array of objects by one or more of their properties */
export function sort(arr, by = { 'data.order': 1, 'data.title': '' }) {
let keys = Array.isArray(by) ? by : Object.keys(by);
/** Sort an array of objects */
export function sort(arr, keys = ['data.order', 'data.title']) {
keys = toArray(keys);
return arr.sort((a, b) => {
let aValues = keys.map(key => deepValue(a, key) ?? by[key]);
let bValues = keys.map(key => deepValue(b, key) ?? by[key]);
let aValues = keys.map(key => deepValue(a, key));
let bValues = keys.map(key => deepValue(b, key));
for (let i = 0; i < aValues.length; i++) {
let aVal = aValues[i];
@@ -224,41 +198,3 @@ export function getCategoryTitle(category, categories) {
// Capitalized
return category.charAt(0).toUpperCase() + category.slice(1);
}
const IDENTITY = x => x;
/**
* Helper to print out one or more HTML attributes, especially conditional ones.
* Usage in 11ty:
* - Single attribute: `<foo{{ value | attr(name) }}>`
* - Multiple attributes: `<foo{{ { name1: value1, name2: value2 } | attr }}>`
*
* @overload
* @param {any} value - The attribute value If falsey, the attribute is not printed. If `true` the attribute is printed without a value.
* @param {string} name - The name of the attribute
*
* @overload
* @param {Object<string, any>} obj - Map of attribute names to values
*
* @returns {string} The attribute string. No `| safe` is needed.
*/
export function attr(value, name) {
const safe = this?.env.filters.safe ?? IDENTITY;
if (arguments.length === 1 && value && typeof value === 'object') {
// Called with a single object argument of names to values
let ret = Object.entries(obj)
.map(([name, value]) => attr(value, name))
.join('');
return safe(ret);
}
if (!value) {
// false, "", null, undefined
return '';
}
let ret = ' ' + name + (value === true ? '' : `="${value}"`);
return safe(ret);
}

View File

@@ -1,25 +0,0 @@
function updateResults(input) {
const filter = input.value.toLowerCase().trim();
let filtered = Boolean(filter);
for (let grid of document.querySelectorAll('.index-grid')) {
grid.classList.toggle('filtered', filtered);
for (let item of grid.querySelectorAll('a:has(> wa-card)')) {
let isMatch = true;
if (filter) {
const content = item.textContent.toLowerCase() + ' ' + (item.getAttribute('data-keywords') + ' ');
isMatch = content.includes(filter);
}
item.hidden = !isMatch;
}
}
}
document.documentElement.addEventListener('wa-input', e => {
if (e.target?.matches('#block-filter wa-input')) {
updateResults(e.target);
}
});

View File

@@ -230,7 +230,7 @@ h1.title wa-badge {
}
}
.block-info {
.component-info {
margin-block-end: var(--wa-flow-spacing);
}

View File

@@ -1,37 +0,0 @@
wa-card#drawer-card::part(header) {
--spacing: 0;
justify-content: flex-end;
overflow: hidden;
}
#block-filter {
margin-block-end: var(--wa-space-xl);
}
.index-grid.filtered {
h2 {
/* Hide headings while filtering */
display: none;
}
&:not(:has(> a:not([hidden]))) {
/* Were filtering and there are no results */
&::before {
content: var(--empty-message);
grid-column: 1 / -1;
color: var(--wa-color-on-quiet);
font-style: italic;
font-weight: var(--wa-font-weight-action);
}
/* Show empty state when there's a search filter and no results */
&[data-empty] {
--empty-message: attr(data-empty);
}
&:not([data-empty]) {
--empty-message: 'No results';
}
}
}

View File

@@ -58,71 +58,6 @@ Set the `variant` attribute to change the callout's variant.
</wa-callout>
```
### Appearance
Use the `appearance` attribute to change the callout's visual appearance (the default is `outlined filled`).
```html {.example}
<wa-callout variant="brand" appearance="outlined accent">
<wa-icon slot="icon" name="check-to-slot"></wa-icon>
This <strong>accent</strong> callout is also <strong>outlined</strong>
</wa-callout>
<br />
<wa-callout variant="brand" appearance="accent">
<wa-icon slot="icon" name="square-check"></wa-icon>
This <strong>accent</strong> callout draws attention without an outline
</wa-callout>
<br />
<wa-callout variant="brand" appearance="outlined filled">
<wa-icon slot="icon" name="fill-drip" variant="regular"></wa-icon>
This callout is both <strong>filled</strong> and <strong>outlined</strong>
</wa-callout>
<br />
<wa-callout variant="brand" appearance="filled">
<wa-icon slot="icon" name="fill" variant="regular"></wa-icon>
This callout is only <strong>filled</strong>
</wa-callout>
<br />
<wa-callout variant="brand" appearance="outlined">
<wa-icon slot="icon" name="lines-leaning" variant="regular"></wa-icon>
Here's an <strong>outlined</strong> callout
</wa-callout>
<br />
<wa-callout variant="brand" appearance="plain">
<wa-icon slot="icon" name="font" variant="regular"></wa-icon>
No bells and whistles on this <strong>plain</strong> callout
</wa-callout>
```
### Sizes
Use the `size` attribute to change a callout's size.
```html {.example}
<wa-callout variant="brand" appearance="outlined accent" size="large">
<wa-icon slot="icon" name="circle-info" variant="solid"></wa-icon>
This is meant to be very emphasized.
</wa-callout>
<wa-callout>
<wa-icon slot="icon" name="circle-info" variant="regular"></wa-icon>
Normal-sized callout.
</wa-callout>
<wa-callout variant="plain" appearance="plain" size="small">
<wa-icon slot="icon" name="circle-info" variant="regular"></wa-icon>
Just a small tip!
</wa-callout>
```
### Without Icons
Icons are optional. Simply omit the `icon` slot if you don't want them.

View File

@@ -60,8 +60,7 @@ 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 `with-header` attribute to add a header to the card (if not, it is added automatically).
Headers can be used to display titles and more. Use the `with-header` attribute to add a header to the card.
```html {.example}
<wa-card with-header class="card-header">
@@ -96,8 +95,7 @@ If using SSR, you need to also use the `with-header` attribute to add a header t
### Card with Footer
Footers can be used to display actions, summaries, or other relevant content.
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).
Footers can be used to display actions, summaries, or other relevant content. Use the `with-footer` attribute to add a footer to the card.
```html {.example}
<wa-card with-footer class="card-footer">
@@ -124,8 +122,7 @@ If using SSR, you need to also use the `with-footer` attribute to add a footer t
### Images
Card images are displayed atop the card and will stretch to fit.
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).
Card images are displayed atop the card and will stretch to fit. Use the `with-image` attribute to add an image to the card.
```html {.example}
<wa-card with-image class="card-image">

View File

@@ -1,11 +1,4 @@
{
"layout": "component.njk",
"tags": ["components"],
"eleventyComputed": {
"component": "{{ components[page.fileSlug] }}",
"description": "{{ components[page.fileSlug].summary }}",
"status": "{{ components[page.fileSlug].status }}",
"since": "{{ components[page.fileSlug].since }}",
"element": "<{{ components[page.fileSlug].tagName }}>"
}
"tags": ["components"]
}

View File

@@ -0,0 +1,81 @@
---
title: Components
description: Components are the essential building blocks to create intuitive, cohesive experiences. Browse the library of customizable, framework-friendly web components included in Web Awesome.
layout: page-outline
categories:
- actions
- feedback: 'Feedback & Status'
- imagery
- inputs
- navigation
- organization
- helpers: 'Utilities'
override:tags: []
---
<div id="component-filter">
<wa-input type="search" placeholder="Search components" clearable autofocus></wa-input>
</div>
{% set allPages = collections.components %}
{% include "grouped-pages.njk" %}
<div id="component-filter-empty" hidden>
No results
</div>
<script type="module">
const container = document.getElementById('component-filter');
const empty = document.getElementById('component-filter-empty');
const grid = document.getElementById('content');
const input = container.querySelector('wa-input');
function updateResults() {
const filter = input.value.toLowerCase().trim();
// Hide headings while filtering
grid.querySelectorAll('h2').forEach(heading => {
heading.hidden = filter === '' ? false : true;
});
// Show matching components
grid.querySelectorAll('a:has(> wa-card)').forEach(link => {
console.log(link);
const content = link.textContent.toLowerCase();
const keywords = link.getAttribute('data-keywords') || '';
const isMatch = filter === '' || (content + keywords).includes(filter);
link.hidden = !isMatch;
});
// Show empty state when there's a search filter and no results
if (filter !== '' && grid.querySelector('a:not([hidden])') === null) {
empty.hidden = false;
} else {
empty.hidden = true;
}
}
input.addEventListener('wa-input', updateResults);
</script>
<style>
wa-card#drawer-card::part(header) {
--spacing: 0;
justify-content: flex-end;
overflow: hidden;
}
#component-filter {
margin-block-end: var(--wa-space-xl);
}
#component-filter-empty {
border: dashed var(--wa-border-width-m) var(--wa-color-neutral-border-quiet);
border-radius: var(--wa-border-radius-l);
font-size: var(--wa-font-size-l);
color: var(--wa-color-text-quiet);
text-align: center;
padding-block: var(--wa-space-2xl);
margin-block-start: 0
}
</style>

View File

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

View File

@@ -3,7 +3,7 @@ title: Page
description: Pages offer an easy way to scaffold entire page layouts using minimal markup.
tags: [organization, layout]
isPro: true
order: 0
order: 1
# icon: page
---

View File

@@ -248,7 +248,7 @@ Use the `distance` attribute to change the distance between the popup and its an
<div class="box"></div>
</wa-popup>
<wa-slider min="-50" max="50" step="1" value="0" label="Distance"></wa-slider>
<wa-range min="-50" max="50" step="1" value="0" label="Distance"></wa-range>
</div>
<style>
@@ -267,7 +267,7 @@ Use the `distance` attribute to change the distance between the popup and its an
border-radius: var(--wa-border-radius-m);
}
.popup-distance wa-slider {
.popup-distance wa-range {
max-width: 260px;
}
</style>
@@ -275,7 +275,7 @@ Use the `distance` attribute to change the distance between the popup and its an
<script>
const container = document.querySelector('.popup-distance');
const popup = container.querySelector('wa-popup');
const distance = container.querySelector('wa-slider');
const distance = container.querySelector('wa-range');
distance.addEventListener('wa-input', () => (popup.distance = distance.value));
</script>
@@ -292,7 +292,7 @@ The `skidding` attribute is similar to `distance`, but instead allows you to off
<div class="box"></div>
</wa-popup>
<wa-slider min="-50" max="50" step="1" value="0" label="Skidding"></wa-slider>
<wa-range min="-50" max="50" step="1" value="0" label="Skidding"></wa-range>
</div>
<style>
@@ -311,7 +311,7 @@ The `skidding` attribute is similar to `distance`, but instead allows you to off
border-radius: var(--wa-border-radius-m);
}
.popup-skidding wa-slider {
.popup-skidding wa-range {
max-width: 260px;
}
</style>
@@ -319,7 +319,7 @@ The `skidding` attribute is similar to `distance`, but instead allows you to off
<script>
const container = document.querySelector('.popup-skidding');
const popup = container.querySelector('wa-popup');
const skidding = container.querySelector('wa-slider');
const skidding = container.querySelector('wa-range');
skidding.addEventListener('wa-input', () => (popup.skidding = skidding.value));
</script>
@@ -747,8 +747,8 @@ When a gap exists between the anchor and the popup element, this option will add
</wa-popup>
<br>
<wa-switch checked>Hover Bridge</wa-switch><br>
<wa-slider min="0" max="50" step="1" value="10" label="Distance"></wa-slider>
<wa-slider min="-50" max="50" step="1" value="0" label="Skidding"></wa-slider>
<wa-range min="0" max="50" step="1" value="10" label="Distance"></wa-range>
<wa-range min="-50" max="50" step="1" value="0" label="Skidding"></wa-range>
</div>
<style>
.popup-hover-bridge span[slot='anchor'] {
@@ -766,7 +766,7 @@ When a gap exists between the anchor and the popup element, this option will add
border-radius: var(--wa-border-radius-m);
}
.popup-hover-bridge wa-slider {
.popup-hover-bridge wa-range {
max-width: 260px;
margin-top: .5rem;
}
@@ -780,8 +780,8 @@ When a gap exists between the anchor and the popup element, this option will add
const container = document.querySelector('.popup-hover-bridge');
const popup = container.querySelector('wa-popup');
const hoverBridge = container.querySelector('wa-switch');
const distance = container.querySelector('wa-slider[label="Distance"]');
const skidding = container.querySelector('wa-slider[label="Skidding"]');
const distance = container.querySelector('wa-range[label="Distance"]');
const skidding = container.querySelector('wa-range[label="Skidding"]');
distance.addEventListener('wa-input', () => (popup.distance = distance.value));
skidding.addEventListener('wa-input', () => (popup.skidding = skidding.value));
hoverBridge.addEventListener('wa-change', () => (popup.hoverBridge = hoverBridge.checked));

View File

@@ -1,13 +1,13 @@
---
title: Slider
title: Range
description: Ranges allow the user to select a single value within a given range using a slider.
tags: [inputs, forms]
native: slider
icon: slider
icon: range
---
```html {.example}
<wa-slider></wa-slider>
<wa-range></wa-range>
```
:::info
@@ -21,7 +21,7 @@ This component works with standard `<form>` elements. Please refer to the sectio
Use the `label` attribute to give the range an accessible label. For labels that contain HTML, use the `label` slot instead.
```html {.example}
<wa-slider label="Volume" min="0" max="100"></wa-slider>
<wa-range label="Volume" min="0" max="100"></wa-range>
```
### Hint
@@ -29,7 +29,7 @@ Use the `label` attribute to give the range an accessible label. For labels that
Add descriptive hint to a range with the `hint` attribute. For hints that contain HTML, use the `hint` slot instead.
```html {.example}
<wa-slider label="Volume" hint="Controls the volume of the current song." min="0" max="100"></wa-slider>
<wa-range label="Volume" hint="Controls the volume of the current song." min="0" max="100"></wa-range>
```
### Min, Max, and Step
@@ -37,7 +37,7 @@ Add descriptive hint to a range with the `hint` attribute. For hints that contai
Use the `min` and `max` attributes to set the range's minimum and maximum values, respectively. The `step` attribute determines the value's interval when increasing and decreasing.
```html {.example}
<wa-slider min="0" max="10" step="1"></wa-slider>
<wa-range min="0" max="10" step="1"></wa-range>
```
### Disabled
@@ -45,7 +45,7 @@ Use the `min` and `max` attributes to set the range's minimum and maximum values
Use the `disabled` attribute to disable a slider.
```html {.example}
<wa-slider disabled></wa-slider>
<wa-range disabled></wa-range>
```
### Tooltip Placement
@@ -53,7 +53,7 @@ Use the `disabled` attribute to disable a slider.
By default, the tooltip is shown on top. Set `tooltip` to `bottom` to show it below the slider.
```html {.example}
<wa-slider tooltip="bottom"></wa-slider>
<wa-range tooltip="bottom"></wa-range>
```
### Disable the Tooltip
@@ -61,7 +61,7 @@ By default, the tooltip is shown on top. Set `tooltip` to `bottom` to show it be
To disable the tooltip, set `tooltip` to `none`.
```html {.example}
<wa-slider tooltip="none"></wa-slider>
<wa-range tooltip="none"></wa-range>
```
### Custom Track Colors
@@ -69,12 +69,12 @@ To disable the tooltip, set `tooltip` to `none`.
You can customize the active and inactive portions of the track using the `--track-color-active` and `--track-color-inactive` custom properties.
```html {.example}
<wa-slider
<wa-range
style="
--track-color-active: var(--wa-color-brand-fill-loud);
--track-color-inactive: var(--wa-color-brand-fill-normal);
"
></wa-slider>
></wa-range>
```
### Custom Track Offset
@@ -82,7 +82,7 @@ You can customize the active and inactive portions of the track using the `--tra
You can customize the initial offset of the active track using the `--track-active-offset` custom property.
```html {.example}
<wa-slider
<wa-range
min="-100"
max="100"
style="
@@ -90,7 +90,7 @@ You can customize the initial offset of the active track using the `--track-acti
--track-color-inactive: var(--wa-color-brand-fill-normal);
--track-active-offset: 50%;
"
></wa-slider>
></wa-range>
```
### Custom Tooltip Formatter
@@ -98,7 +98,7 @@ You can customize the initial offset of the active track using the `--track-acti
You can change the tooltip's content by setting the `tooltipFormatter` property to a function that accepts the range's value as an argument.
```html {.example}
<wa-slider min="0" max="100" step="1" class="range-with-custom-formatter"></wa-slider>
<wa-range min="0" max="100" step="1" class="range-with-custom-formatter"></wa-range>
<script>
const range = document.querySelector('.range-with-custom-formatter');
@@ -111,8 +111,8 @@ You can change the tooltip's content by setting the `tooltipFormatter` property
The component adapts to right-to-left (RTL) languages as you would expect.
```html {.example}
<wa-slider dir="rtl"
<wa-range dir="rtl"
label="مقدار"
hint="التحكم في مستوى صوت الأغنية الحالية."
style="--track-color-active: var(--wa-color-brand-fill-loud)" value="10"></wa-slider>
style="--track-color-active: var(--wa-color-brand-fill-loud)" value="10"></wa-range>
```

View File

@@ -15,50 +15,9 @@ icon: tag
## Examples
### Appearance
Use the `size` attribute to change a tag's visual appearance.
The default appearance is `outlined filled`.
```html {.example}
<div class="wa-stack">
<p>
<wa-tag variant="brand" appearance="outlined accent">Outlined accent</wa-tag>
<wa-tag variant="brand" appearance="accent">Accent</wa-tag>
<wa-tag variant="brand" appearance="outlined">Outlined</wa-tag>
<wa-tag variant="brand" appearance="filled">Filled</wa-tag>
<wa-tag variant="brand" appearance="outlined filled">Outlined Filled</wa-tag>
</p>
<p>
<wa-tag variant="success" appearance="outlined accent">Outlined accent</wa-tag>
<wa-tag variant="success" appearance="accent">Accent</wa-tag>
<wa-tag variant="success" appearance="outlined">Outlined</wa-tag>
<wa-tag variant="success" appearance="filled">Filled</wa-tag>
<wa-tag variant="success" appearance="outlined filled">Outlined Filled</wa-tag>
<p>
<wa-tag variant="neutral" appearance="outlined accent">Outlined accent</wa-tag>
<wa-tag variant="neutral" appearance="accent">Accent</wa-tag>
<wa-tag variant="neutral" appearance="outlined">Outlined</wa-tag>
<wa-tag variant="neutral" appearance="filled">Filled</wa-tag>
<wa-tag variant="neutral" appearance="outlined filled">Outlined Filled</wa-tag>
<p>
<wa-tag variant="warning" appearance="outlined accent">Outlined accent</wa-tag>
<wa-tag variant="warning" appearance="accent">Accent</wa-tag>
<wa-tag variant="warning" appearance="outlined">Outlined</wa-tag>
<wa-tag variant="warning" appearance="filled">Filled</wa-tag>
<wa-tag variant="warning" appearance="outlined filled">Outlined Filled</wa-tag>
<p>
<wa-tag variant="danger" appearance="outlined accent">Outlined accent</wa-tag>
<wa-tag variant="danger" appearance="accent">Accent</wa-tag>
<wa-tag variant="danger" appearance="outlined">Outlined</wa-tag>
<wa-tag variant="danger" appearance="filled">Filled</wa-tag>
<wa-tag variant="danger" appearance="outlined filled">Outlined Filled</wa-tag>
</div>
```
### Sizes
Use the `size` attribute to change a tag's size.
Use the `size` attribute to change a tab's size.
```html {.example}
<wa-tag size="small">Small</wa-tag>

View File

@@ -1,10 +0,0 @@
export default {
eleventyComputed: {
children(data) {
let mainTag = data.tags?.[0];
let collection = data.collections[mainTag] ?? [];
return collection.filter(item => item.data.parent === data.page.fileSlug);
},
},
};

View File

@@ -15,7 +15,7 @@ Adding the `wa-valid` or `wa-invalid` class to a form control will change its ap
<wa-option>Well, maybe two is OK</wa-option>
</wa-select>
<wa-textarea class="wa-valid" label="Bio" hint="Tell us about yourself" placeholder="Enter a bio"></wa-textarea><br>
<wa-slider class="wa-valid" value="50" label="Volume" hint="Crank it up"></wa-slider><br>
<wa-range class="wa-valid" value="50" label="Volume" hint="Crank it up"></wa-range><br>
<wa-checkbox class="wa-valid" checked>I am awesome</wa-checkbox><br>
<wa-checkbox class="wa-valid">So am I</wa-checkbox><br><br>
<wa-switch class="wa-valid" checked>Still awesome</wa-switch><br>
@@ -35,7 +35,7 @@ Adding the `wa-valid` or `wa-invalid` class to a form control will change its ap
<wa-option>Well, maybe two is OK</wa-option>
</wa-select>
<wa-textarea class="wa-invalid" label="Bio" hint="Tell us about yourself" placeholder="Enter a bio"></wa-textarea><br>
<wa-slider class="wa-invalid" value="50" label="Volume" hint="Crank it up"></wa-slider><br>
<wa-range class="wa-invalid" value="50" label="Volume" hint="Crank it up"></wa-range><br>
<wa-checkbox class="wa-invalid" checked>I am awesome</wa-checkbox><br>
<wa-checkbox class="wa-invalid">So am I</wa-checkbox><br><br>
<wa-switch class="wa-invalid" checked>Still awesome</wa-switch><br>

View File

@@ -132,7 +132,7 @@ layout: page
<br />
<wa-switch checked>Switch on</wa-switch>
<br /><br />
<wa-slider label="Range" hint="Here's a bit of handy content." min="0" max="100"></wa-slider>
<wa-range label="Range" hint="Here's a bit of handy content." min="0" max="100"></wa-range>
<br /><br />
<wa-input label="Label" hint="Super helpful and/or contextual content" placeholder="Placeholder"></wa-input>
<br />
@@ -239,7 +239,7 @@ layout: page
<wa-badge>OCBS</wa-badge>
<wa-avatar></wa-avatar>
<wa-rating></wa-rating>
<wa-slider></wa-slider>
<wa-range></wa-range>
<wa-icon-button name="gear" label="Settings"></wa-icon-button>
<wa-progress-bar value="50" style="width: 8rem;"></wa-progress-bar>
<wa-spinner></wa-spinner>

View File

@@ -661,10 +661,10 @@ hasOutline: false
<wa-option value="dotted">Dotted</wa-option>
<wa-option value="double">Double</wa-option>
</wa-select>
<wa-slider name="border-width" label="Border width" min="1" max="5" value="1" step="1" tooltip="none"></wa-slider>
<wa-slider name="spacing" label="Spacing" min=".5" max="1.5" value="1" step="0.125" tooltip="none"></wa-slider>
<wa-slider name="corners" label="Corners" min="0" max="1.5" value=".25" step=".125" tooltip="none"></wa-slider>
<wa-slider name="depth" label="Depth" min="0" max="4" value="0" step="1" tooltip="none"></wa-slider>
<wa-range name="border-width" label="Border width" min="1" max="5" value="1" step="1" tooltip="none"></wa-range>
<wa-range name="spacing" label="Spacing" min=".5" max="1.5" value="1" step="0.125" tooltip="none"></wa-range>
<wa-range name="corners" label="Corners" min="0" max="1.5" value=".25" step=".125" tooltip="none"></wa-range>
<wa-range name="depth" label="Depth" min="0" max="4" value="0" step="1" tooltip="none"></wa-range>
</wa-details>
</form>

27
docs/docs/layout.md Normal file
View File

@@ -0,0 +1,27 @@
---
title: Layout
description: Browse Web Awesome's components and utilities for creating responsive web layouts.
layout: page-outline
categories: ["components", "utilities"]
---
<style>
wa-page > main {
max-width: 120ch;
margin-inline: auto;
}
.index-grid wa-card::part(header) {
background-color: var(--wa-color-neutral-fill-quiet);
border-bottom: none;
}
wa-card .component-name,
wa-card .page-name {
font-size: var(--wa-font-size-s);
font-weight: var(--wa-font-weight-action);
}
</style>
<p style="max-width: 80ch">Layout components and utility classes help you organize content that can adapt to any device or screen size. Browse the collection of responsive layout tools included in Web Awesome Pro.</p>
{% set allPages = collections.layout %}
{% include "grouped-pages.njk" %}

View File

@@ -1,7 +0,0 @@
---
title: Layout
description: Layout components and utility classes help you organize content that can adapt to any device or screen size. Browse the collection of responsive layout tools included in Web Awesome Pro.
layout: overview
categories: ["components", "utilities"]
override:tags: []
---

View File

@@ -4,9 +4,6 @@ description: 'Button styles apply your Web Awesome theme to native HTML buttons.
tags: forms
component: button
icon: button
snippets:
'<button>': https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button
'.wa-button': false
---
```html {.example}

View File

@@ -1,16 +1,20 @@
---
title: Native Styles
description: Native Styles use your theme to style native HTML elements to match the look and feel of Web Awesome components.
layout: overview
layout: page-outline
categories: ['forms', 'apps', 'content']
override:tags: []
---
{% markdown %}
Web Awesome works _with_ the platform, rather than trying to reinvent it.
If all you need is styles, you dont need to use new `<wa-*>` elements!
We also provide styles that make native HTML elements look good so you can continue using what you know and gradually adopt Web Awesome as you see fit.
{% set allPages = collections.native %}
{% include "grouped-pages.njk" %}
<br> {# Temp fix for spacing issue #}
## Installation
To use all Web Awesome page styles (including [utilities](/docs/utilities/)), include the following stylesheet in your project:
@@ -82,4 +86,3 @@ E.g. to opt-out of `<details>` styling:
If you find yourself opting out of entire element types too much, you could consider only including the parts of Native Styles you need instead of the whole thing.
You can find instructions for how to do that on the individual Native Styles pages.
{% endmarkdown %}

View File

@@ -3,8 +3,8 @@ title: Slider
description: Sliders allow the user to select a single value within a given range using a slider.
tags: forms
layout: element
icon: slider
component: slider
icon: range
component: range
elements:
"<input type=range>": https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range
---

View File

@@ -1,6 +1,7 @@
---
title: App
description: TODO
layout: pattern.njk
---
TODO Page Description
@@ -1044,4 +1045,4 @@ TODO Page Description
```
### With templates
### With recommendations grid
### With recommendations grid

View File

@@ -1,6 +1,7 @@
---
title: Blog
description: TODO
layout: pattern.njk
---
TODO Page Description
@@ -366,4 +367,4 @@ TODO Page Description
}
</style>
```
```

View File

@@ -1,8 +1,9 @@
---
title: Business
description: TODO
layout: pattern.njk
---
TODO Page Description
## Examples
## Examples

View File

@@ -1,8 +1,7 @@
---
title: Category Filter
title: E-commerce - Category Filter
description: TODO
parent: ecommerce
tags: e-commerce
layout: pattern.njk
---
TODO Page Description
@@ -10,4 +9,4 @@ TODO Page Description
## With inline actions and expandable sidebar filters
```html{.example}
```
```

View File

@@ -1,12 +1,11 @@
---
title: Category Preview
title: E-commerce - Category Preview
description: TODO
parent: ecommerce
tags: e-commerce
layout: pattern.njk
---
TODO Page Description
## Three Column (WIP)
```
```

View File

@@ -1,8 +1,7 @@
---
title: Order History
title: E-commerce - Order History
description: TODO
parent: ecommerce
tags: e-commerce
layout: pattern.njk
---
TODO Page Description
@@ -11,4 +10,4 @@ TODO Page Description
```html{.example}
```
```

View File

@@ -1,8 +1,7 @@
---
title: Product List
title: E-commerce - Product List
description: TODO
parent: ecommerce
tags: e-commerce
layout: pattern.njk
---
TODO Page Description
@@ -10,4 +9,4 @@ TODO Page Description
## With split image
```html{.example}
```
```

View File

@@ -1,8 +1,7 @@
---
title: Product Detail
title: E-commerce - Product Detail
description: TODO
parent: ecommerce
tags: e-commerce
layout: pattern.njk
---
TODO Page Description

View File

@@ -1,8 +1,7 @@
---
title: Product Lists
title: E-commerce - Product Lists
description: TODO
parent: ecommerce
tags: e-commerce
layout: pattern.njk
---
TODO Page Description

View File

@@ -1,8 +1,7 @@
---
title: Product Reviews
title: E-commerce - Product Reviews
description: TODO
parent: ecommerce
tags: e-commerce
layout: pattern.njk
---
TODO Page Description

View File

@@ -1,10 +1,9 @@
---
title: Shopping Cart
title: E-commerce - Shopping Cart
description: TODO
parent: ecommerce
tags: e-commerce
layout: pattern.njk
---
TODO Page Description
## Examples
## Examples

View File

@@ -2,7 +2,6 @@
title: E-commerce
description: TODO
layout: page
tags: e-commerce
---
TODO Page Description

View File

@@ -1,8 +1,9 @@
---
title: Business
description: TODO
layout: pattern.njk
---
TODO Page Description
## Examples
## Examples

View File

@@ -1,8 +1,9 @@
---
title: Entertainment
description: TODO
layout: pattern.njk
---
TODO Page Description
## Examples
## Examples

View File

@@ -0,0 +1,5 @@
---
title: Patterns
description: Browse the library of customizable, framework-friendly web components included in Web Awesome.
layout: page-outline
---

View File

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

View File

@@ -1,8 +1,9 @@
---
title: Membership
description: TODO
layout: pattern.njk
---
TODO Page Description
## Examples
## Examples

View File

@@ -1,6 +1,7 @@
---
title: News
description: TODO
layout: pattern.njk
---
TODO Page Description
@@ -156,4 +157,4 @@ TODO Page Description
}
}
</style>
```
```

View File

@@ -1,8 +1,9 @@
---
title: Non-profit
description: TODO
layout: pattern.njk
---
TODO Page Description
## Examples
## Examples

View File

@@ -1,5 +0,0 @@
{
"layout": "block.njk",
"tags": ["patterns"],
"noAlpha": true
}

View File

@@ -1,8 +1,9 @@
---
title: Portfolio
description: TODO
layout: pattern.njk
---
TODO Page Description
## Examples
## Examples

View File

@@ -1,8 +1,9 @@
---
title: Product Landing
description: TODO
layout: pattern.njk
---
TODO Page Description
## Examples
## Examples

View File

@@ -14,8 +14,6 @@ During the alpha period, things might break! We take breaking changes very serio
## Next
- Simplified the internal structure and CSS properties of `<wa-card>`, removed `base` part.
- Added `appearance` to `<wa-callout>` and `<wa-tag>`
- Fixed a bug in `<wa-switch>` where it would not properly change its "checked" state when its property changed.
- Fixed a bug in the `wa-split` CSS utility that caused it to behave incorrectly
- Improved performance of `<wa-select>` when using a large number of options
@@ -149,4 +147,4 @@ Here's a list of some of the things that have changed since Shoelace v2. For que
Did we miss something? [Let us know!](https://github.com/shoelace-style/webawesome-alpha/discussions)
Are you coming from Shoelace? [The 2.x changelog can be found here.](https://shoelace.style/resources/changelog/)
Are you coming from Shoelace? [The 2.x changelog can be found here.](https://shoelace.style/resources/changelog/)

View File

@@ -1,7 +1,6 @@
---
title: Component Groups
description: Style groups of components that share similar qualities with these Web Awesome custom properties.
order: 9999
---
For components that share similar qualities, Web Awesome includes custom properties to change the appearance of these related components all at once.
@@ -50,7 +49,7 @@ Not every form control uses all of these custom properties. For example, `<wa-ra
</wa-radio-group>
<wa-checkbox>Checkbox</wa-checkbox>
<wa-switch>Switch</wa-switch>
<wa-slider label="Range"></wa-slider>
<wa-range label="Range"></wa-range>
<wa-button>Button</wa-button>
</form>

View File

@@ -0,0 +1,73 @@
---
title: Theming
description: Browse the library of customizable, framework-friendly web components included in Web Awesome.
---
<p class="index-summary">A theme is a collection of pre-defined CSS custom properties that control global styles from color to shadows. These custom properties thread through all Web Awesome components for a consistent look and feel.</p>
<div class="index-grid">
<a href="/docs/theming/color">
<wa-card with-header>
<div slot="header">
{% include "svgs/theming/color.njk" %}
</div>
<span class="page-name">Color</span>
</wa-card>
</a>
<a href="/docs/theming/typography">
<wa-card with-header>
<div slot="header">
{% include "svgs/theming/typography.njk" %}
</div>
<span class="page-name">Typography</span>
</wa-card>
</a>
<a href="/docs/theming/space">
<wa-card with-header>
<div slot="header">
{% include "svgs/theming/space.njk" %}
</div>
<span class="page-name">Space</span>
</wa-card>
</a>
<a href="/docs/theming/borders">
<wa-card with-header>
<div slot="header">
{% include "svgs/theming/borders.njk" %}
</div>
<span class="page-name">Borders</span>
</wa-card>
</a>
<a href="/docs/theming/focus">
<wa-card with-header>
<div slot="header">
{% include "svgs/theming/focus.njk" %}
</div>
<span class="page-name">Focus</span>
</wa-card>
</a>
<a href="/docs/theming/shadows">
<wa-card with-header>
<div slot="header">
{% include "svgs/theming/shadows.njk" %}
</div>
<span class="page-name">Shadows</span>
</wa-card>
</a>
<a href="/docs/theming/transitions">
<wa-card with-header>
<div slot="header">
{% include "svgs/theming/transitions.njk" %}
</div>
<span class="page-name">Transitions</span>
</wa-card>
</a>
<a href="/docs/theming/component-groups">
<wa-card with-header>
<div slot="header">
{% include "svgs/theming/component-groups.njk" %}
</div>
<span class="page-name">Component Groups</span>
</wa-card>
</a>
</div>

View File

@@ -1,6 +0,0 @@
---
title: Theming
description: A theme is a collection of pre-defined CSS custom properties that control global styles from color to shadows. These custom properties thread through all Web Awesome components for a consistent look and feel.
layout: overview
override:tags: []
---

View File

@@ -1,7 +1,4 @@
{
"layout": "page-outline.njk",
"tags": ["theming"],
"eleventyComputed": {
"icon": "theming/{{ page.fileSlug }}"
}
"tags": ["theming"]
}

View File

@@ -1,11 +1,11 @@
---
title: Appearance Variants
description: Appearance utilities apply a collection of properties to achieve certain effects, like making elements accented, outlined, filled, or plain.
snippets:
- .wa-accent
- .wa-outlined
- .wa-filled
- .wa-plain
classes:
- wa-accent
- wa-outlined
- wa-filled
- wa-plain
---
Some Web Awesome components, like `<wa-button>`, allow you to change their overall style by using an `appearance` attribute:

View File

@@ -2,12 +2,12 @@
title: Color Variants
description: Color utilities allow you to apply the brand, neutral, success, warning, and danger colors from your theme to any element.
icon: theming/color
snippets:
- .wa-brand
- .wa-neutral
- .wa-success
- .wa-warning
- .wa-danger
classes:
- wa-brand
- wa-neutral
- wa-success
- wa-warning
- wa-danger
---
Some Web Awesome components, like `<wa-button>`, allow you to change the color by using a `variant` attribute:

View File

@@ -2,12 +2,16 @@
title: Style Utilities
description: Web Awesome provides a few style utilities to customize styles in ways that cannot necessarily be described by semantic HTML.
Some of these correspond to component attributes, but we also expose utility classes so you can apply these styles to native elements too.
layout: overview
categories: ["layout"]
layout: page-outline
override:tags: []
categories: ["layout"]
---
{% markdown %}
{% set allPages = collections.utilities %}
{% include "grouped-pages.njk" %}
<br> {# Temp fix for spacing issue #}
## Installation
To use all Web Awesome page styles (including [native styles](/docs/native/)), include the following stylesheet in your project:
@@ -23,4 +27,3 @@ Or, to _only_ include utilities:
```
You can also include individual utilities following the instructions in their pages.
{% endmarkdown %}

View File

@@ -3,10 +3,10 @@ title: Size
description: Size utilities give elements one of three preset sizes (small, medium, or large).
icon: theming/space
status: experimental
snippets:
- .wa-size-s
- .wa-size-m
- .wa-size-l
classes:
- wa-size-s
- wa-size-m
- wa-size-l
---
Some Web Awesome components, like `<wa-button>`, allow you to change their size to one of three presets: `small`, `medium`, and `large` by using a `size` attribute:

View File

@@ -1,4 +1,4 @@
{
"layout": "block",
"layout": "utilities",
"tags": ["utilities"]
}

View File

@@ -2,9 +2,9 @@
title: Visually Hidden
description: The visually hidden utility makes content accessible to assistive devices without displaying it on the screen.
icon: visually-hidden
snippets:
- .wa-visually-hidden
- .wa-visually-hidden-force
classes:
- wa-visually-hidden
- wa-visually-hidden-force
---
> "there are real world situations where visually hiding content may be appropriate, while the content should remain available to assistive technologies, such as screen readers. For instance, hiding a search field's label as a common magnifying glass icon is used in its stead."

View File

@@ -3,6 +3,8 @@ import { html } from 'lit';
import { fixtures } from '../../internal/test/fixture.js';
import type WaAvatar from './avatar.js';
// The default avatar background just misses AA contrast, but the next step up is way too dark. Since avatars aren't
// used to display text, we're going to relax this rule.
const ignoredRules = ['color-contrast'];
describe('<wa-avatar>', () => {
@@ -20,7 +22,9 @@ describe('<wa-avatar>', () => {
});
it('should default to circle styling', () => {
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
expect(el.getAttribute('shape')).to.eq('circle');
expect(part.classList.value.trim()).to.eq('avatar avatar--circle');
});
});
@@ -32,12 +36,25 @@ describe('<wa-avatar>', () => {
});
it('should pass accessibility tests', async () => {
/**
* The image element itself is ancillary, because it's parent container contains the
* aria-label which dictates what "wa-avatar" is. This also implies that label text will
* resolve to "" when not provided and ignored by readers. This is why we use alt="" on
* the image element to pass accessibility.
* https://html.spec.whatwg.org/multipage/images.html#ancillary-images
*/
await expect(el).to.be.accessible({ ignoredRules });
});
it('renders "image" part with src and proper aria-label', () => {
it('renders "image" part, with src and a role of presentation', () => {
const part = el.shadowRoot!.querySelector('[part~="image"]')!;
expect(part.getAttribute('src')).to.eq(image);
});
it('renders the label attribute in the "base" part', () => {
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
expect(part.getAttribute('aria-label')).to.eq(label);
});
});
@@ -52,10 +69,50 @@ describe('<wa-avatar>', () => {
await expect(el).to.be.accessible({ ignoredRules });
});
it('renders "initials" part with initials and proper aria-label', () => {
it('renders "initials" part, with initials as the text node', () => {
const part = el.shadowRoot!.querySelector<HTMLElement>('[part~="initials"]')!;
expect(part.innerText).to.eq(initials);
expect(part.getAttribute('aria-label')).to.eq('Avatar');
});
});
describe('when image is present, the initials or icon part should not render', () => {
const initials = 'SL';
const image = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
const label = 'Small transparent square';
beforeEach(async () => {
el = await fixture<WaAvatar>(
html`<wa-avatar image="${image}" label="${label}" initials="${initials}"></wa-avatar>`,
);
});
it('should pass accessibility tests', async () => {
/**
* The image element itself is ancillary, because it's parent container contains the
* aria-label which dictates what "wa-avatar" is. This also implies that label text will
* resolve to "" when not provided and ignored by readers. This is why we use alt="" on
* the image element to pass accessibility.
* https://html.spec.whatwg.org/multipage/images.html#ancillary-images
*/
await expect(el).to.be.accessible({ ignoredRules });
});
it('renders "image" part, with src and a role of presentation', () => {
const part = el.shadowRoot!.querySelector('[part~="image"]')!;
expect(part.getAttribute('src')).to.eq(image);
});
it('should not render the initials part', () => {
const part = el.shadowRoot!.querySelector<HTMLElement>('[part~="initials"]')!;
expect(part).to.not.exist;
});
it('should not render the icon part', () => {
const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=icon]')!;
expect(slot).to.not.exist;
});
});
@@ -69,8 +126,11 @@ describe('<wa-avatar>', () => {
await expect(el).to.be.accessible({ ignoredRules });
});
it('reflects the shape attribute', () => {
it('appends the appropriate class on the "base" part', () => {
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
expect(el.getAttribute('shape')).to.eq(shape);
expect(part.classList.value.trim()).to.eq(`avatar avatar--${shape}`);
});
});
});
@@ -89,8 +149,11 @@ describe('<wa-avatar>', () => {
it('should accept as an assigned child in the shadow root', () => {
const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=icon]')!;
const childNodes = slot.assignedNodes({ flatten: true }) as HTMLElement[];
expect(childNodes.length).to.eq(1);
expect(childNodes[0].innerHTML).to.eq('random content');
const span = childNodes[0];
expect(span.innerHTML).to.eq('random content');
});
});
@@ -98,9 +161,11 @@ describe('<wa-avatar>', () => {
el = await fixture<WaAvatar>(html`<wa-avatar></wa-avatar>`);
el.image = 'bad_image';
await aTimeout(0);
await el.updateComplete;
await waitUntil(() => el.shadowRoot?.querySelector('img') === null);
expect(el.shadowRoot?.querySelector('img')).to.be.null;
await waitUntil(() => el.shadowRoot!.querySelector('img') === null);
expect(el.shadowRoot!.querySelector('img')).to.be.null;
});
it('should show a valid image after being passed an invalid image initially', async () => {
@@ -108,6 +173,7 @@ describe('<wa-avatar>', () => {
await aTimeout(0);
await el.updateComplete;
// await waitUntil(() => el.shadowRoot!.querySelector('img') === null);
expect(el.shadowRoot!.querySelector('img')).to.be.null;
el.image = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';

View File

@@ -66,7 +66,7 @@ export default class WaAvatar extends WebAwesomeElement {
class="image"
src="${this.image}"
loading="${this.loading}"
role="img"
alt=""
aria-label=${this.label}
@error="${this.handleImageLoadError}"
/>

View File

@@ -3,6 +3,8 @@ import { html } from 'lit';
import { fixtures } from '../../internal/test/fixture.js';
import type WaBadge from './badge.js';
// The default badge background just misses AA contrast, but the next step up is way too dark. We're going to relax this
// rule for now.
const ignoredRules = ['color-contrast'];
describe('<wa-badge>', () => {
@@ -14,7 +16,7 @@ describe('<wa-badge>', () => {
expect(el.innerText).to.eq('Badge');
});
it('should pass accessibility tests with a role of status on the base part', async () => {
it('should pass accessibility tests with a role of status on the base part.', async () => {
const el = await fixture<WaBadge>(html` <wa-badge>Badge</wa-badge> `);
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
expect(part.getAttribute('role')).to.eq('status');
@@ -23,7 +25,8 @@ describe('<wa-badge>', () => {
it('should default to square styling, with the brand color', async () => {
const el = await fixture<WaBadge>(html` <wa-badge>Badge</wa-badge> `);
expect(el.getAttribute('variant')).to.eq('brand');
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
expect(part.classList.value.trim()).to.eq('badge badge--brand');
});
});
@@ -33,9 +36,10 @@ describe('<wa-badge>', () => {
await expect(el).to.be.accessible({ ignoredRules });
});
it('should have the pill attribute', async () => {
it('should append the pill class to the classlist to render a pill', async () => {
const el = await fixture<WaBadge>(html` <wa-badge pill>Badge</wa-badge> `);
expect(el.hasAttribute('pill')).to.be.true;
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
expect(part.classList.value.trim()).to.eq('badge badge--brand badge--pill');
});
});
@@ -46,9 +50,10 @@ describe('<wa-badge>', () => {
await aTimeout(1);
});
it('should have the pulse attribute', async () => {
it('should append the pulse class to the classlist to render a pulse', async () => {
const el = await fixture<WaBadge>(html` <wa-badge pulse>Badge</wa-badge> `);
expect(el.hasAttribute('pulse')).to.be.true;
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
expect(part.classList.value.trim()).to.eq('badge badge--brand badge--pulse');
});
});
@@ -60,9 +65,10 @@ describe('<wa-badge>', () => {
await aTimeout(1);
});
it('should have the correct variant attribute', async () => {
it('should default to square styling, with the correct color', async () => {
const el = await fixture<WaBadge>(html`<wa-badge variant="${variant}">Badge</wa-badge>`);
expect(el.getAttribute('variant')).to.eq(variant);
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
expect(part.classList.value.trim()).to.eq(`badge badge--${variant}`);
});
});
});

View File

@@ -113,7 +113,7 @@ describe('<wa-button>', () => {
expect(el.title).to.equal('');
expect(el.variant).to.equal('neutral');
expect(el.appearance).to.equal('accent');
expect(el.appearance).to.equal('filled');
expect(el.size).to.equal('medium');
expect(el.disabled).to.equal(false);
expect(el.caret).to.equal(false);

View File

@@ -7,7 +7,7 @@ import { WaFocusEvent } from '../../events/focus.js';
import { WaInvalidEvent } from '../../events/invalid.js';
import { MirrorValidator } from '../../internal/validators/mirror-validator.js';
import { watch } from '../../internal/watch.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js';
import nativeStyles from '../../styles/native/button.css';
import appearanceStyles from '../../styles/utilities/appearance.css';
import sizeStyles from '../../styles/utilities/size.css';

View File

@@ -8,17 +8,13 @@
align-items: stretch;
border-radius: var(--wa-panel-border-radius);
background-color: var(--background-color, var(--wa-color-fill-quiet));
border-color: var(--border-color, transparent);
border-color: var(--border-color, var(--wa-color-border-quiet));
border-style: var(--wa-panel-border-style);
border-width: var(--wa-panel-border-width);
color: var(--text-color, var(--wa-color-on-normal));
padding: var(--spacing);
}
:host([appearance~='accent']) {
font-weight: var(--wa-font-weight-semibold);
}
[part~='icon'] {
flex: 0 0 auto;
display: flex;

View File

@@ -1,8 +1,6 @@
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import appearanceStyles from '../../styles/utilities/appearance.css';
import sizeStyles from '../../styles/utilities/size.css';
import variantStyles from '../../styles/utilities/variants.css';
import styles from './callout.css';
@@ -24,23 +22,11 @@ import styles from './callout.css';
*/
@customElement('wa-callout')
export default class WaCallout extends WebAwesomeElement {
static shadowStyle = [variantStyles, appearanceStyles, sizeStyles, styles];
static shadowStyle = [variantStyles, styles];
/** The callout's theme variant. */
@property({ reflect: true }) variant: 'brand' | 'success' | 'neutral' | 'warning' | 'danger' = 'brand';
/** The callout's visual appearance. */
@property({ reflect: true }) appearance:
| 'accent'
| 'filled'
| 'outlined'
| 'plain'
| 'outlined filled'
| 'outlined accent' = 'outlined filled';
/** The callout's size. */
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
render() {
return html`
<div part="icon">

View File

@@ -1,30 +1,45 @@
:host {
--background-color: var(--wa-color-surface-default);
--border-color: var(--wa-color-surface-border);
--border-radius: var(--wa-panel-border-radius);
--border-style: var(--wa-panel-border-style);
--border-width: var(--wa-panel-border-width);
--box-shadow: var(--wa-shadow-s);
--spacing: var(--wa-space-xl);
display: inline-block;
}
.card {
display: flex;
flex-direction: column;
background-color: var(--wa-color-surface-default);
border-color: var(--wa-color-surface-border);
border-radius: var(--wa-panel-border-radius);
border-style: var(--wa-panel-border-style);
border-width: var(--wa-panel-border-width);
box-shadow: var(--wa-shadow-s);
background-color: var(--background-color);
border-color: var(--border-color);
border-radius: var(--border-radius);
border-style: var(--border-style);
border-width: var(--border-width);
box-shadow: var(--box-shadow);
color: var(--wa-color-text-normal);
font: inherit;
}
.image {
display: flex;
border-top-left-radius: inherit;
border-top-right-radius: inherit;
border-top-left-radius: var(--border-radius);
border-top-right-radius: var(--border-radius);
margin: calc(-1 * var(--border-width));
overflow: hidden;
}
&::slotted(img) {
display: block;
width: 100%;
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.image::slotted(img) {
display: block;
width: 100%;
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.card:not(.card--has-image) .image {
display: none;
}
.header {
@@ -33,9 +48,13 @@
padding: calc(var(--spacing) / 2) var(--spacing);
}
:host(:not([with-image])) .header {
border-top-left-radius: inherit;
border-top-right-radius: inherit;
.card:not(.card--has-header) .header {
display: none;
}
.card:not(.card--has-image) .header {
border-top-left-radius: var(--border-radius);
border-top-right-radius: var(--border-radius);
}
.body {
@@ -43,16 +62,14 @@
padding: var(--spacing);
}
.footer {
.card--has-footer .footer {
display: block;
border-top: inherit;
border-bottom-left-radius: inherit;
border-bottom-right-radius: inherit;
border-bottom-left-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
padding: var(--spacing);
}
:host(:not([with-header])) .header,
:host(:not([with-footer])) .footer,
:host(:not([with-image])) .image {
.card:not(.card--has-footer) .footer {
display: none;
}

View File

@@ -22,6 +22,14 @@ describe('<wa-card>', () => {
`);
await expect(el).to.be.accessible();
});
it('should contain the class card.', async () => {
const el = await fixture<WaCard>(html`
<wa-card>This is just a basic card. No image, no header, and no footer. Just your content.</wa-card>
`);
const card = el.shadowRoot!.querySelector('.card')!;
expect(card.classList.value.trim()).to.eq('card');
});
});
describe('when provided an element in the slot "header" to render a header', () => {
@@ -68,6 +76,17 @@ describe('<wa-card>', () => {
expect(childNodes.length).to.eq(1);
});
it('should contain the class card--has-header.', async () => {
const el = await fixture<WaCard>(
html`<wa-card with-header>
<div slot="header">Header Title</div>
This card has a header. You can put all sorts of things in it!
</wa-card>`,
);
const card = el.shadowRoot!.querySelector('.card')!;
expect(card.classList.value.trim()).to.eq('card card--has-header');
});
});
describe('when provided an element in the slot "footer" to render a footer', () => {
@@ -118,6 +137,19 @@ describe('<wa-card>', () => {
expect(childNodes.length).to.eq(1);
});
it('should contain the class card--has-footer.', async () => {
const el = await fixture<WaCard>(
html`<wa-card with-footer>
This card has a footer. You can put all sorts of things in it!
<div slot="footer">Footer Content</div>
</wa-card>`,
);
const card = el.shadowRoot!.querySelector('.card')!;
expect(card.classList.value.trim()).to.eq('card card--has-footer');
});
});
describe('when provided an element in the slot "image" to render a image', () => {
@@ -169,6 +201,22 @@ describe('<wa-card>', () => {
expect(childNodes.length).to.eq(1);
});
it('should contain the class card--has-image.', async () => {
const el = await fixture<WaCard>(
html`<wa-card with-image>
<img
slot="image"
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
alt="A kitten walks towards camera on top of pallet."
/>
This is a kitten, but not just any kitten. This kitten likes walking along pallets.
</wa-card>`,
);
const card = el.shadowRoot!.querySelector('.card')!;
expect(card.classList.value.trim()).to.eq('card card--has-image');
});
});
});
}

View File

@@ -1,5 +1,6 @@
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import styles from './card.css';
@@ -14,32 +15,49 @@ import styles from './card.css';
* @slot footer - An optional footer for the card.
* @slot image - An optional image to render at the start of the card.
*
* @csspart base - The component's base wrapper.
* @csspart image - The container that wraps the card's image.
* @csspart header - The container that wraps the card's header.
* @csspart body - The container that wraps the card's main content.
* @csspart footer - The container that wraps the card's footer.
*
* @cssproperty --background-color - The card's background color.
* @cssproperty --border-color - The card's border color, including borders that occur inside the card.
* @cssproperty --border-radius - The radius for the card's corners. Expects a single value.
* @cssproperty --border-style - The style of the card's borders.
* @cssproperty --border-width - The width of the card's borders. Expects a single value.
* @cssproperty --box-shadow - The shadow effects around the edges of the card.
* @cssproperty --spacing - 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 = styles;
/** Renders the card with a header. Only needed for SSR, otherwise is automatically added. */
/** Renders the card with a header */
@property({ attribute: 'with-header', type: Boolean }) withHeader = false;
/** Renders the card with an image. Only needed for SSR, otherwise is automatically added. */
/** Renders the card with an image */
@property({ attribute: 'with-image', type: Boolean }) withImage = false;
/** Renders the card with a footer. Only needed for SSR, otherwise is automatically added. */
/** Renders the card with a footer */
@property({ attribute: 'with-footer', type: Boolean }) withFooter = false;
render() {
return html`
<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="footer"></slot>
<div
part="base"
class=${classMap({
card: true,
'card--has-footer': this.withFooter,
'card--has-image': this.withImage,
'card--has-header': this.withHeader,
})}
>
<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="footer"></slot>
</div>
`;
}
}

View File

@@ -199,7 +199,7 @@ describe('<wa-carousel>', () => {
});
describe('and user clicks on a pagination button', () => {
it.skip('should scroll the carousel to the nth slide', async () => {
it('should scroll the carousel to the nth slide', async () => {
// Arrange
const el = await fixture<WaCarousel>(html`
<wa-carousel pagination>
@@ -215,7 +215,7 @@ describe('<wa-carousel>', () => {
const paginationItem = el.shadowRoot!.querySelectorAll('.pagination-item')[2] as HTMLElement;
await clickOnElement(paginationItem);
expect(el.goToSlide).to.have.been.calledWith(1);
expect(el.goToSlide).to.have.been.calledWith(2);
});
});
});

View File

@@ -110,7 +110,7 @@ describe('<wa-checkbox>', () => {
// See: https://github.com/shoelace-style/shoelace/issues/1169
//
const el = await fixture<WaCheckbox>(html` <wa-checkbox></wa-checkbox> `);
const label = el.shadowRoot!.querySelector('label')!;
const label = el.shadowRoot!.querySelector('.checkbox')!;
const input = el.shadowRoot!.querySelector('.input')!;
const labelPosition = getComputedStyle(label).position;

View File

@@ -11,7 +11,7 @@ import { WaInputEvent } from '../../events/input.js';
import { HasSlotController } from '../../internal/slot.js';
import { RequiredValidator } from '../../internal/validators/required-validator.js';
import { watch } from '../../internal/watch.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js';
import nativeStyles from '../../styles/native/checkbox.css';
import formControlStyles from '../../styles/shadow/form-control.css';
import sizeStyles from '../../styles/utilities/size.css';

View File

@@ -4,12 +4,12 @@ import { html } from 'lit';
import sinon from 'sinon';
import { clickOnElement, dragElement } from '../../internal/test.js';
import { fixtures } from '../../internal/test/fixture.js';
// import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
import { serialize } from '../../utilities/form.js';
import type WaColorPicker from './color-picker.js';
describe('<wa-color-picker>', () => {
// runFormControlBaseTests('wa-color-picker');
runFormControlBaseTests('wa-color-picker');
for (const fixture of fixtures) {
describe(`with "${fixture.type}" rendering`, () => {

View File

@@ -15,7 +15,7 @@ import { clamp } from '../../internal/math.js';
import { HasSlotController } from '../../internal/slot.js';
import { RequiredValidator } from '../../internal/validators/required-validator.js';
import { watch } from '../../internal/watch.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js';
import formControlStyles from '../../styles/shadow/form-control.css';
import sizeStyles from '../../styles/utilities/size.css';
import visuallyHidden from '../../styles/utilities/visually-hidden.css';

View File

@@ -196,8 +196,8 @@ describe('<wa-details>', () => {
await first.show();
await second.show();
expect(firstBody.clientHeight).to.equal(200);
expect(secondBody.clientHeight).to.equal(400);
expect(firstBody.clientHeight).to.equal(232); // 200 + 16px + 16px (vertical padding)
expect(secondBody.clientHeight).to.equal(432); // 400 + 16px + 16px (vertical padding)
});
});
}

View File

@@ -4,7 +4,7 @@ import { ifDefined } from 'lit/directives/if-defined.js';
import { html, literal } from 'lit/static-html.js';
import { WaBlurEvent } from '../../events/blur.js';
import { WaFocusEvent } from '../../events/focus.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js';
import '../icon/icon.js';
import styles from './icon-button.css';

View File

@@ -170,7 +170,9 @@ describe('<wa-icon>', () => {
});
describe('svg sprite sheets', () => {
// For some reason ESLint wants to fail in CI here, but works locally.
// TODO: this test is skipped because Bootstrap sprite.svg doesn't seem to be available in CI. Will fix in a future PR. [Konnor]
/* eslint-disable */
it.skip('Should properly grab an SVG and render it from bootstrap icons', async () => {
registerIconLibrary('sprite', {
resolver: name => `/docs/assets/images/sprite.svg#${name}`,

View File

@@ -30,7 +30,6 @@ input {
background-color: rgb(118 118 118 / 0); /* ensures proper placeholder styles in webkit's date input */
height: calc(var(--wa-form-control-height) - var(--border-width) * 2);
padding-block: 0;
color: inherit;
}
input::-webkit-search-decoration,

View File

@@ -28,31 +28,45 @@ describe('<wa-input>', () => {
expect(el.value).to.equal(null);
expect(el.defaultValue).to.equal(null);
expect(el.title).to.equal('');
expect(el.appearance).to.equal('outlined'); // Added
expect(el.pill).to.equal(false);
expect(el.filled).to.be.false;
expect(el.pill).to.be.false;
expect(el.label).to.equal('');
expect(el.hint).to.equal('');
expect(el.clearable).to.equal(false);
expect(el.passwordToggle).to.equal(false);
expect(el.passwordVisible).to.equal(false);
expect(el.noSpinButtons).to.equal(false);
expect(el.clearable).to.be.false;
expect(el.passwordToggle).to.be.false;
expect(el.passwordVisible).to.be.false;
expect(el.noSpinButtons).to.be.false;
expect(el.placeholder).to.equal('');
expect(el.disabled).to.equal(false);
expect(el.readonly).to.equal(false);
expect(el.disabled).to.be.false;
expect(el.readonly).to.be.false;
expect(el.minlength).to.be.undefined;
expect(el.maxlength).to.be.undefined;
expect(el.min).to.be.undefined;
expect(el.max).to.be.undefined;
expect(el.step).to.be.undefined;
expect(el.pattern).to.be.undefined;
expect(el.required).to.be.false;
expect(el.autocapitalize).to.be.undefined;
expect(el.autocorrect).to.be.undefined;
expect(el.autocomplete).to.be.undefined;
expect(el.autofocus).to.be.undefined;
expect(el.enterkeyhint).to.be.undefined;
expect(el.spellcheck).to.be.true;
expect(el.inputmode).to.be.undefined;
});
it('should have title if title attribute is set', async () => {
const el = await fixture<WaInput>(html` <wa-input title="Test"></wa-input> `);
await el.updateComplete;
const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
const input = el.shadowRoot!.querySelector<HTMLInputElement>('[part~="input"]')!;
expect(input.title).to.equal('Test');
});
it('should be disabled with the disabled attribute', async () => {
const el = await fixture<WaInput>(html` <wa-input disabled></wa-input> `);
await el.updateComplete;
const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
expect(input.disabled).to.equal(true);
const input = el.shadowRoot!.querySelector<HTMLInputElement>('[part~="input"]')!;
expect(input.disabled).to.be.true;
});
it('should focus the input when clicking on the label', async () => {

View File

@@ -11,7 +11,7 @@ import { WaInputEvent } from '../../events/input.js';
import { HasSlotController } from '../../internal/slot.js';
import { MirrorValidator } from '../../internal/validators/mirror-validator.js';
import { watch } from '../../internal/watch.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js';
import nativeStyles from '../../styles/native/input.css';
import formControlStyles from '../../styles/shadow/form-control.css';
import appearanceStyles from '../../styles/utilities/appearance.css';

View File

@@ -1,9 +1,8 @@
import { aTimeout, expect, waitUntil } from '@open-wc/testing';
import { expect, waitUntil } from '@open-wc/testing';
import { sendKeys } from '@web/test-runner-commands';
import { html } from 'lit';
import sinon from 'sinon';
import type { WaSelectEvent } from '../../events/select.js';
import { clickOnElement } from '../../internal/test.js';
import { fixtures } from '../../internal/test/fixture.js';
import type WaMenuItem from './menu-item.js';
@@ -144,8 +143,6 @@ describe('<wa-menu-item>', () => {
menu.addEventListener('wa-select', selectHandler);
const submenu = menu.querySelector<WaMenuItem>('wa-menu-item')!;
// Sometimes Chrome fails if we dont click before triggering focus.
await clickOnElement(submenu);
submenu.focus();
await menu.updateComplete;
await sendKeys({ press: 'ArrowRight' });
@@ -176,9 +173,6 @@ describe('<wa-menu-item>', () => {
});
const outerItem = menu.querySelector<WaMenuItem>('#outer')!;
// Silly fix for CI + Chrome to focus properly.
await clickOnElement(outerItem);
outerItem.focus();
await menu.updateComplete;
await sendKeys({ press: 'ArrowRight' });

View File

@@ -35,11 +35,11 @@ describe('<wa-progress-bar>', () => {
});
it('uses the value parameter on the base, as aria-valuenow', () => {
expect(base.getAttribute('aria-valuenow')).to.equal('25');
expect(base).attribute('aria-valuenow', '25');
});
it('uses the value parameter to set the custom property on the indicator', () => {
expect(indicator.style.getPropertyValue('--value')).to.equal('25');
it('appends a % to the value, and uses it as the the value parameter to determine the width on the "indicator" part', () => {
expect(indicator).attribute('style', 'width:25%;');
});
});
@@ -57,8 +57,8 @@ describe('<wa-progress-bar>', () => {
await expect(el).to.be.accessible();
});
it('should set the indeterminate attribute on the host', () => {
expect(el.hasAttribute('indeterminate')).to.be.true;
it('should append a progress-bar--indeterminate class to the "base" part.', () => {
expect(base.classList.value.trim()).to.eq('progress-bar progress-bar--indeterminate');
});
});

View File

@@ -43,7 +43,7 @@ export default class WaProgressBar extends WebAwesomeElement {
aria-label=${this.label.length > 0 ? this.label : this.localize.term('progress')}
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow=${this.indeterminate ? '0' : this.value}
${this.indeterminate ? 0 : `aria-valuenow="${this.value}"`}
>
<div part="indicator" class="indicator" style="--value: ${this.value}">
${!this.indeterminate ? html` <slot part="label" class="label"></slot> ` : ''}

View File

@@ -23,6 +23,33 @@ describe('<wa-radio-button>', () => {
expect(radio1.checked).to.be.true;
expect(radio2.checked).to.be.false;
});
it('should receive positional classes from <wa-button-group>', async () => {
const radioGroup = await fixture<WaRadioGroup>(html`
<wa-radio-group value="1">
<wa-radio-button id="radio-1" value="1"></wa-radio-button>
<wa-radio-button id="radio-2" value="2"></wa-radio-button>
<wa-radio-button id="radio-3" value="3"></wa-radio-button>
</wa-radio-group>
`);
const radio1 = radioGroup.querySelector<WaRadioButton>('#radio-1')!;
const radio2 = radioGroup.querySelector<WaRadioButton>('#radio-2')!;
const radio3 = radioGroup.querySelector<WaRadioButton>('#radio-3')!;
await Promise.all([
radioGroup.updateComplete,
radio1.updateComplete,
radio2.updateComplete,
radio3.updateComplete,
]);
expect(radio1.classList.contains('button')).to.be.true;
expect(radio1.classList.contains('button--first')).to.be.true;
expect(radio2.classList.contains('button')).to.be.true;
expect(radio2.classList.contains('button--inner')).to.be.true;
expect(radio3.classList.contains('button')).to.be.true;
expect(radio3.classList.contains('button--last')).to.be.true;
});
});
}
});

View File

@@ -6,7 +6,7 @@ import { WaBlurEvent } from '../../events/blur.js';
import { WaFocusEvent } from '../../events/focus.js';
import { HasSlotController } from '../../internal/slot.js';
import { watch } from '../../internal/watch.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js';
import nativeStyles from '../../styles/native/button.css';
import sizeStyles from '../../styles/utilities/size.css';
import variantStyles from '../../styles/utilities/variants.css';

View File

@@ -7,7 +7,7 @@ import { uniqueId } from '../../internal/math.js';
import { HasSlotController } from '../../internal/slot.js';
import { RequiredValidator } from '../../internal/validators/required-validator.js';
import { watch } from '../../internal/watch.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js';
import formControlStyles from '../../styles/shadow/form-control.css';
import buttonGroupStyles from '../../styles/utilities/button-group.css';
import sizeStyles from '../../styles/utilities/size.css';

View File

@@ -4,7 +4,7 @@ import { classMap } from 'lit/directives/class-map.js';
import { WaBlurEvent } from '../../events/blur.js';
import { WaFocusEvent } from '../../events/focus.js';
import { watch } from '../../internal/watch.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js';
import nativeStyles from '../../styles/native/radio.css';
import sizeStyles from '../../styles/utilities/size.css';
import '../icon/icon.js';

View File

@@ -6,20 +6,20 @@ import { clickOnElement } from '../../internal/test.js';
import { fixtures } from '../../internal/test/fixture.js';
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
import { serialize } from '../../utilities/form.js';
import type WaSlider from './slider.js';
import type WaRange from './range.js';
describe('<wa-slider>', () => {
runFormControlBaseTests('wa-slider');
describe('<wa-range>', () => {
runFormControlBaseTests('wa-range');
for (const fixture of fixtures) {
describe(`with "${fixture.type}" rendering`, () => {
it('should pass accessibility tests', async () => {
const el = await fixture<WaSlider>(html`<wa-slider label="Name"></wa-slider>`);
const el = await fixture<WaRange>(html`<wa-range label="Name"></wa-range>`);
await expect(el).to.be.accessible();
});
it('default properties', async () => {
const el = await fixture<WaSlider>(html` <wa-slider></wa-slider> `);
const el = await fixture<WaRange>(html` <wa-range></wa-range> `);
expect(el.name).to.equal('');
expect(el.value).to.equal(0);
@@ -36,22 +36,53 @@ describe('<wa-slider>', () => {
});
it('should have title if title attribute is set', async () => {
const el = await fixture<WaSlider>(html` <wa-slider title="Test"></wa-slider> `);
const el = await fixture<WaRange>(html` <wa-range title="Test"></wa-range> `);
const input = el.shadowRoot!.querySelector('input')!;
expect(input.title).to.equal('Test');
});
it('should be disabled with the disabled attribute', async () => {
const el = await fixture<WaSlider>(html` <wa-slider disabled></wa-slider> `);
const input = el.shadowRoot!.querySelector<HTMLInputElement>('.control')!;
const el = await fixture<WaRange>(html` <wa-range disabled></wa-range> `);
const input = el.shadowRoot!.querySelector<HTMLInputElement>('[part~="input"]')!;
expect(input.disabled).to.be.true;
});
describe('when the value changes', () => {
it('should emit wa-change and wa-input when the value changes from clicking the slider', async () => {
const el = await fixture<WaRange>(html` <wa-range value="0"></wa-range> `);
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
await clickOnElement(el, 'right');
await el.updateComplete;
expect(el.value).to.equal(100);
expect(changeHandler).to.have.been.calledOnce;
expect(inputHandler).to.have.been.calledOnce;
});
it('should emit wa-change and wa-input and decrease the value when pressing left arrow', async () => {
const el = await fixture<WaRange>(html` <wa-range value="50"></wa-range> `);
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.focus();
await sendKeys({ press: 'ArrowLeft' });
await el.updateComplete;
expect(el.value).to.equal(49);
expect(changeHandler).to.have.been.calledOnce;
expect(inputHandler).to.have.been.calledOnce;
});
it('should emit wa-change and wa-input and decrease the value when pressing right arrow', async () => {
const el = await fixture<WaSlider>(html` <wa-slider value="50"></wa-slider> `);
const el = await fixture<WaRange>(html` <wa-range value="50"></wa-range> `);
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
@@ -67,7 +98,7 @@ describe('<wa-slider>', () => {
});
it('should not emit wa-change or wa-input when changing the value programmatically', async () => {
const el = await fixture<WaSlider>(html` <wa-slider value="0"></wa-slider> `);
const el = await fixture<WaRange>(html` <wa-range value="0"></wa-range> `);
el.addEventListener('wa-change', () => expect.fail('wa-change should not be emitted'));
el.addEventListener('wa-input', () => expect.fail('wa-input should not be emitted'));
@@ -77,7 +108,7 @@ describe('<wa-slider>', () => {
});
it('should not emit wa-change or wa-input when stepUp() is called programmatically', async () => {
const el = await fixture<WaSlider>(html` <wa-slider step="2" value="2"></wa-slider> `);
const el = await fixture<WaRange>(html` <wa-range step="2" value="2"></wa-range> `);
el.addEventListener('wa-change', () => expect.fail('wa-change should not be emitted'));
el.addEventListener('wa-input', () => expect.fail('wa-input should not be emitted'));
@@ -86,7 +117,7 @@ describe('<wa-slider>', () => {
});
it('should not emit wa-change or wa-input when stepDown() is called programmatically', async () => {
const el = await fixture<WaSlider>(html` <wa-slider step="2" value="2"></wa-slider> `);
const el = await fixture<WaRange>(html` <wa-range step="2" value="2"></wa-range> `);
el.addEventListener('wa-change', () => expect.fail('wa-change should not be emitted'));
el.addEventListener('wa-input', () => expect.fail('wa-input should not be emitted'));
@@ -97,7 +128,7 @@ describe('<wa-slider>', () => {
describe('step', () => {
it('should increment by step when stepUp() is called', async () => {
const el = await fixture<WaSlider>(html` <wa-slider step="2" value="2"></wa-slider> `);
const el = await fixture<WaRange>(html` <wa-range step="2" value="2"></wa-range> `);
el.stepUp();
await el.updateComplete;
@@ -105,7 +136,7 @@ describe('<wa-slider>', () => {
});
it('should decrement by step when stepDown() is called', async () => {
const el = await fixture<WaSlider>(html` <wa-slider step="2" value="2"></wa-slider> `);
const el = await fixture<WaRange>(html` <wa-range step="2" value="2"></wa-range> `);
el.stepDown();
await el.updateComplete;
@@ -115,49 +146,49 @@ describe('<wa-slider>', () => {
describe('when submitting a form', () => {
it('should serialize its name and value with FormData', async () => {
const form = await fixture<HTMLFormElement>(html` <form><wa-slider name="a" value="1"></wa-slider></form> `);
const form = await fixture<HTMLFormElement>(html` <form><wa-range name="a" value="1"></wa-range></form> `);
const formData = new FormData(form);
expect(formData.get('a')).to.equal('1');
});
it('should serialize its name and value with JSON', async () => {
const form = await fixture<HTMLFormElement>(html` <form><wa-slider name="a" value="1"></wa-slider></form> `);
const form = await fixture<HTMLFormElement>(html` <form><wa-range name="a" value="1"></wa-range></form> `);
const json = serialize(form);
expect(json.a).to.equal('1');
});
it('should be invalid when setCustomValidity() is called with a non-empty value', async () => {
const slider = await fixture<HTMLFormElement>(html` <wa-slider></wa-slider> `);
const range = await fixture<HTMLFormElement>(html` <wa-range></wa-range> `);
slider.setCustomValidity('Invalid selection');
await slider.updateComplete;
range.setCustomValidity('Invalid selection');
await range.updateComplete;
expect(slider.checkValidity()).to.be.false;
expect(slider.hasCustomState('invalid')).to.be.true;
expect(slider.hasCustomState('valid')).to.be.false;
expect(slider.hasCustomState('user-invalid')).to.be.false;
expect(slider.hasCustomState('user-valid')).to.be.false;
expect(range.checkValidity()).to.be.false;
expect(range.hasCustomState('invalid')).to.be.true;
expect(range.hasCustomState('valid')).to.be.false;
expect(range.hasCustomState('user-invalid')).to.be.false;
expect(range.hasCustomState('user-valid')).to.be.false;
await clickOnElement(slider);
await slider.updateComplete;
slider.blur();
await slider.updateComplete;
await clickOnElement(range);
await range.updateComplete;
range.blur();
await range.updateComplete;
expect(slider.hasCustomState('user-invalid')).to.be.true;
expect(slider.hasCustomState('user-valid')).to.be.false;
expect(range.hasCustomState('user-invalid')).to.be.true;
expect(range.hasCustomState('user-valid')).to.be.false;
});
it('should receive validation attributes ("states") even when novalidate is used on the parent form', async () => {
const el = await fixture<HTMLFormElement>(html` <form novalidate><wa-slider></wa-slider></form> `);
const slider = el.querySelector<WaSlider>('wa-slider')!;
const el = await fixture<HTMLFormElement>(html` <form novalidate><wa-range></wa-range></form> `);
const range = el.querySelector<WaRange>('wa-range')!;
slider.setCustomValidity('Invalid value');
await slider.updateComplete;
range.setCustomValidity('Invalid value');
await range.updateComplete;
expect(slider.hasCustomState('invalid')).to.be.true;
expect(slider.hasCustomState('valid')).to.be.false;
expect(slider.hasCustomState('user-invalid')).to.be.false;
expect(slider.hasCustomState('user-valid')).to.be.false;
expect(range.hasCustomState('invalid')).to.be.true;
expect(range.hasCustomState('valid')).to.be.false;
expect(range.hasCustomState('user-invalid')).to.be.false;
expect(range.hasCustomState('user-valid')).to.be.false;
});
it('should be present in form data when using the form attribute and located outside of a <form>', async () => {
@@ -166,7 +197,7 @@ describe('<wa-slider>', () => {
<form id="f">
<wa-button type="submit">Submit</wa-button>
</form>
<wa-slider form="f" name="a" value="50"></wa-slider>
<wa-range form="f" name="a" value="50"></wa-range>
</div>
`);
const form = el.querySelector('form')!;
@@ -180,12 +211,12 @@ describe('<wa-slider>', () => {
it('should reset the element to its initial value', async () => {
const form = await fixture<HTMLFormElement>(html`
<form>
<wa-slider name="a" value="99"></wa-slider>
<wa-range name="a" value="99"></wa-range>
<wa-button type="reset">Reset</wa-button>
</form>
`);
const button = form.querySelector('wa-button')!;
const input = form.querySelector('wa-slider')!;
const input = form.querySelector('wa-range')!;
input.value = 80;
await input.updateComplete;

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