Compare commits

...

12 Commits

Author SHA1 Message Date
Cory LaViska
c191709eb1 improve search grid styles 2025-03-26 11:49:57 -04:00
Cory LaViska
0454f494f5 add debounce to search so it feels more natural 2025-03-26 11:34:54 -04:00
Cory LaViska
0db9ca12e3 Remove unused SSR module and remove first load fade (#835)
* disable SSR module in 11ty

* remove first load fade
2025-03-26 14:45:29 +00:00
Lea Verou
041555fe99 border-radius: 0 on plain details 2025-03-26 10:04:25 -04:00
Lea Verou
b41dbd2de7 Fix: Specify default card background 2025-03-25 16:53:16 -04:00
Lea Verou
7c6f31e0c7 [Card docs] Use style utilities instead of custom CSS 2025-03-25 16:31:40 -04:00
Lea Verou
9e84274a93 [Card] Round all corners of the image for appearance=plain 2025-03-25 16:31:40 -04:00
Lea Verou
2b3803f91e [Card] Support appearance, closes #609 2025-03-25 16:31:40 -04:00
Lea Verou
faed8da3cd Fix broken link 2025-03-25 14:14:53 -04:00
Lea Verou
17cf902f53 Add appearance to details, closes #569
Except `accent` as that's a) far less useful and b) trickier due to the icon color
2025-03-25 14:14:53 -04:00
Lea Verou
8214ff6b2d Several fixes around overviews, outlines etc (#825)
* Fix outline for headings that have links

Previously produced blank items because it assumed any link in a heading is an anchor

* Filter unlisted items from overviews

Previously they were filtered only when the card was rendered, so their heading was still shown

* [Overview] Add id to group headings

* Hide headings from empty groups

Should never happen but you never know

* [Overview] Ensure "Other" is always last even when no sorting
2025-03-25 11:39:04 -04:00
Cory LaViska
c9979e15f8 adds a hard coded delay to drastically reduce theme picker jank (#829) 2025-03-24 20:49:08 +00:00
17 changed files with 236 additions and 87 deletions

View File

@@ -7,10 +7,10 @@ import { highlightCodePlugin } from './_utils/highlight-code.js';
import { markdown } from './_utils/markdown.js';
import { removeDataAlphaElements } from './_utils/remove-data-alpha-elements.js';
// import { formatCodePlugin } from './_utils/format-code.js';
import litPlugin from '@lit-labs/eleventy-plugin-lit';
// import litPlugin from '@lit-labs/eleventy-plugin-lit';
import { readFile } from 'fs/promises';
import nunjucks from 'nunjucks';
import componentList from './_data/componentList.js';
// import componentList from './_data/componentList.js';
import * as filters from './_utils/filters.js';
import { outlinePlugin } from './_utils/outline.js';
import { replaceTextPlugin } from './_utils/replace-text.js';
@@ -29,7 +29,6 @@ const globalData = {
package: packageData,
isAlpha,
layout: 'page.njk',
server: {
head: '',
loginOrAvatar: '',
@@ -190,30 +189,30 @@ export default function (eleventyConfig) {
eleventyConfig.addPassthroughCopy(glob);
}
// SSR plugin
// Make sure this is the last thing, we dont want to run the risk of accidentally transforming shadow roots with the nunjucks 2nd transform.
if (!isDev) {
//
// Problematic components in SSR land:
// - animation (breaks on navigation + ssr with Turbo)
// - mutation-observer (why SSR this?)
// - resize-observer (why SSR this?)
// - tooltip (why SSR this?)
//
const omittedModules = [];
const componentModules = componentList
.filter(component => !omittedModules.includes(component.tagName.split(/wa-/)[1]))
.map(component => {
const name = component.tagName.split(/wa-/)[1];
const componentDirectory = process.env.UNBUNDLED_DIST_DIRECTORY || path.join('.', 'dist');
return path.join(componentDirectory, 'components', name, `${name}.js`);
});
eleventyConfig.addPlugin(litPlugin, {
mode: 'worker',
componentModules,
});
}
// // SSR plugin
// // Make sure this is the last thing, we don't want to run the risk of accidentally transforming shadow roots with the nunjucks 2nd transform.
// if (!isDev) {
// //
// // Problematic components in SSR land:
// // - animation (breaks on navigation + ssr with Turbo)
// // - mutation-observer (why SSR this?)
// // - resize-observer (why SSR this?)
// // - tooltip (why SSR this?)
// //
// const omittedModules = [];
// const componentModules = componentList
// .filter(component => !omittedModules.includes(component.tagName.split(/wa-/)[1]))
// .map(component => {
// const name = component.tagName.split(/wa-/)[1];
// const componentDirectory = process.env.UNBUNDLED_DIST_DIRECTORY || path.join('.', 'dist');
// return path.join(componentDirectory, 'components', name, `${name}.js`);
// });
//
// eleventyConfig.addPlugin(litPlugin, {
// mode: 'worker',
// componentModules,
// });
// }
return {
markdownTemplateEngine: 'njk',

View File

@@ -3,8 +3,8 @@
<section id="grid" class="index-grid">
{% set groupedPages = allPages | groupPages(categories, page) %}
{% for category, pages in groupedPages -%}
{% if groupedPages.meta.groupCount > 1 %}
<h2 class="index-category">
{% if groupedPages.meta.groupCount > 1 and pages.length > 0 %}
<h2 class="index-category" id="{{ category | slugify }}">
{% if pages.meta.url %}<a href="{{ pages.meta.url }}">{{ pages.meta.title }}</a>
{% else %}
{{ pages.meta.title }}

View File

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

View File

@@ -178,6 +178,10 @@ export function sort(arr, by = { 'data.order': 1, 'data.title': '' }) {
});
}
export function show(page) {
return !(page.data.noAlpha && page.data.isAlpha) && !page.data.unlisted;
}
/**
* Group an 11ty collection (or any array of objects with a `data.tags` property) by certain tags.
* @param {object[]} collection
@@ -198,7 +202,7 @@ export function groupPages(collection, options = {}, page) {
options = { tags: options };
}
let { tags, groups, titles = {}, other = 'Other' } = options;
let { tags, groups, titles = {}, other = 'Other', filter = show } = options;
if (groups === undefined && Array.isArray(tags)) {
groups = tags;
@@ -237,6 +241,10 @@ export function groupPages(collection, options = {}, page) {
let byUrl = {};
let byParentUrl = {};
if (filter) {
collection = collection.filter(filter);
}
for (let item of collection) {
let url = item.page.url;
let parentUrl = item.data.parentUrl;
@@ -313,6 +321,13 @@ export function groupPages(collection, options = {}, page) {
if (sortedGroups) {
ret = sortObject(ret, sortedGroups);
} else {
// At least make sure other is last
if (ret.other) {
let otherGroup = ret.other;
delete ret.other;
ret.other = otherGroup;
}
}
Object.defineProperty(ret, 'meta', {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,10 +2,12 @@ details:where(:not(:host *)),
:host {
--icon-color: var(--wa-color-text-quiet);
--spacing: var(--wa-space-m);
--outlined-border-color: var(--wa-color-surface-border);
background-color: var(--wa-color-surface-default);
border: var(--wa-panel-border-width) var(--wa-color-surface-border) var(--wa-panel-border-style);
background-color: var(--background-color, var(--wa-color-surface-default));
border: var(--wa-panel-border-width) var(--border-color, var(--wa-color-surface-border)) var(--wa-panel-border-style);
border-radius: var(--wa-panel-border-radius);
color: var(--text-color, inherit);
padding: var(--spacing);
/* Print styles */
@@ -19,6 +21,12 @@ details:where(:not(:host *)),
}
}
details.wa-plain,
:host(wa-details[appearance='plain']),
:host(wa-details.wa-plain) {
border-radius: 0;
}
details:where(:not(:host *)) summary,
:host summary /* nesting these summary styles in the rule above breaks in Firefox and Safari */ {
display: flex;