mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-13 12:39:14 +00:00
Compare commits
104 Commits
pill
...
icon-fetch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b001f144c8 | ||
|
|
8a7a7dc3b0 | ||
|
|
eefa57f61a | ||
|
|
c69cabf413 | ||
|
|
7de111341e | ||
|
|
5558a0f2cd | ||
|
|
de4221365a | ||
|
|
b85e6bcdf3 | ||
|
|
ad539a00f2 | ||
|
|
1559b08a63 | ||
|
|
400db9a608 | ||
|
|
1f280b3b9f | ||
|
|
e9f2104f15 | ||
|
|
ad53a0aa56 | ||
|
|
d94589d113 | ||
|
|
3508bf6339 | ||
|
|
61e65ffcb9 | ||
|
|
d79d753f4c | ||
|
|
6306955c74 | ||
|
|
cff9d56b3f | ||
|
|
9fde4a820e | ||
|
|
34c0b562fe | ||
|
|
f499a932f9 | ||
|
|
85cca22d71 | ||
|
|
c261f25c65 | ||
|
|
ac1d412a8f | ||
|
|
aed28adbe4 | ||
|
|
15b8bde81b | ||
|
|
9ee3fb5d28 | ||
|
|
47aa376c08 | ||
|
|
69ba974a50 | ||
|
|
8dfb411e5e | ||
|
|
a35a8fd2ad | ||
|
|
2503005bbd | ||
|
|
78027170ea | ||
|
|
a20aa48992 | ||
|
|
ac8accd664 | ||
|
|
c571573063 | ||
|
|
e813440315 | ||
|
|
cfc3f181a3 | ||
|
|
7545f04c46 | ||
|
|
38bd6528fe | ||
|
|
2202ea9642 | ||
|
|
58fbcede51 | ||
|
|
971200cc88 | ||
|
|
b75d3b615c | ||
|
|
9d1c47449e | ||
|
|
003fd28cb0 | ||
|
|
2f300d8930 | ||
|
|
f13deb87bb | ||
|
|
deb9fd70b3 | ||
|
|
ff3b3d6558 | ||
|
|
6b3edb8a56 | ||
|
|
6162b8b115 | ||
|
|
cff752b600 | ||
|
|
7892a94b9b | ||
|
|
40a58ff35f | ||
|
|
0f2950c4cc | ||
|
|
b334884f57 | ||
|
|
734417d66b | ||
|
|
2cfd651d2f | ||
|
|
3e2d1b98be | ||
|
|
40f332f37c | ||
|
|
bfda64f690 | ||
|
|
883d6df2ef | ||
|
|
b4240fd321 | ||
|
|
8755a834f6 | ||
|
|
8d905296b8 | ||
|
|
8eba1e5003 | ||
|
|
21aa85acc0 | ||
|
|
404c15b303 | ||
|
|
8a26afc334 | ||
|
|
513a1e35a9 | ||
|
|
09f668fc99 | ||
|
|
d451ba98e5 | ||
|
|
fd287edd56 | ||
|
|
8424b49646 | ||
|
|
fa24c0f70e | ||
|
|
1bba87c66d | ||
|
|
0db9ca12e3 | ||
|
|
041555fe99 | ||
|
|
b41dbd2de7 | ||
|
|
7c6f31e0c7 | ||
|
|
9e84274a93 | ||
|
|
2b3803f91e | ||
|
|
faed8da3cd | ||
|
|
17cf902f53 | ||
|
|
8214ff6b2d | ||
|
|
c9979e15f8 | ||
|
|
fcfe2bde7d | ||
|
|
59dcaaff83 | ||
|
|
5bad30ec30 | ||
|
|
87c1762146 | ||
|
|
899edd1d5e | ||
|
|
872a110b1e | ||
|
|
07fe6d598e | ||
|
|
79bafc513a | ||
|
|
1d03f7bee0 | ||
|
|
a9bf1bd838 | ||
|
|
c0ca739366 | ||
|
|
a6745602d6 | ||
|
|
da4f619d95 | ||
|
|
1283a696a5 | ||
|
|
d12b97b0b0 |
9
.github/workflows/ssr_tests.js.yml
vendored
9
.github/workflows/ssr_tests.js.yml
vendored
@@ -1,11 +1,12 @@
|
||||
# # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||
# # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: SSR Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [next]
|
||||
# push:
|
||||
# branches: [next]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
ssr_test:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as path from 'node:path';
|
||||
import { anchorHeadingsPlugin } from './_utils/anchor-headings.js';
|
||||
import { codeExamplesPlugin } from './_utils/code-examples.js';
|
||||
import { copyCodePlugin } from './_utils/copy-code.js';
|
||||
@@ -6,9 +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 componentList from './_data/componentList.js';
|
||||
import nunjucks from 'nunjucks';
|
||||
// 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';
|
||||
@@ -16,7 +18,10 @@ import { searchPlugin } from './_utils/search.js';
|
||||
|
||||
import process from 'process';
|
||||
|
||||
const packageData = JSON.parse(await readFile('./package.json', 'utf-8'));
|
||||
import * as url from 'url';
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
const packageData = JSON.parse(await readFile(path.join(__dirname, '..', 'package.json'), 'utf-8'));
|
||||
const isAlpha = process.argv.includes('--alpha');
|
||||
const isDev = process.argv.includes('--develop');
|
||||
|
||||
@@ -24,12 +29,28 @@ const globalData = {
|
||||
package: packageData,
|
||||
isAlpha,
|
||||
layout: 'page.njk',
|
||||
server: {
|
||||
head: '',
|
||||
loginOrAvatar: '',
|
||||
flashes: '',
|
||||
},
|
||||
};
|
||||
|
||||
const passThroughExtensions = ['js', 'css', 'png', 'svg', 'jpg', 'mp4'];
|
||||
const passThrough = [...passThroughExtensions.map(ext => 'docs/**/*.' + ext)];
|
||||
|
||||
export default function (eleventyConfig) {
|
||||
/**
|
||||
* If you plan to add or remove any of these extensions, make sure to let either Konnor or Cory know as these passthrough extensions
|
||||
* will also need to be updated in the Web Awesome App.
|
||||
*/
|
||||
const passThroughExtensions = ['js', 'css', 'png', 'svg', 'jpg', 'mp4'];
|
||||
|
||||
const baseDir = process.env.BASE_DIR || 'docs';
|
||||
const passThrough = [...passThroughExtensions.map(ext => path.join(baseDir, '**/*.' + ext))];
|
||||
|
||||
/**
|
||||
* This is the guard we use for now to make sure our final built files dont need a 2nd pass by the server. This keeps us able to still deploy the bare HTML files on Vercel until the app is ready.
|
||||
*/
|
||||
const serverBuild = process.env.WEBAWESOME_SERVER === 'true';
|
||||
|
||||
// NOTE - alpha setting removes certain pages
|
||||
if (isAlpha) {
|
||||
eleventyConfig.ignores.add('**/experimental/**');
|
||||
@@ -55,7 +76,38 @@ export default function (eleventyConfig) {
|
||||
|
||||
// Shortcodes - {% shortCode arg1, arg2 %}
|
||||
eleventyConfig.addShortcode('cdnUrl', location => {
|
||||
return `https://early.webawesome.com/webawesome@${packageData.version}/dist/` + location.replace(/^\//, '');
|
||||
return `https://early.webawesome.com/webawesome@${packageData.version}/dist/` + (location || '').replace(/^\//, '');
|
||||
});
|
||||
|
||||
// Turns `{% server "foo" %} into `{{ server.foo | safe }}` when the WEBAWESOME_SERVER variable is set to "true"
|
||||
eleventyConfig.addShortcode('server', function (property) {
|
||||
if (serverBuild) {
|
||||
return `{{ server.${property} | safe }}`;
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
|
||||
eleventyConfig.addTransform('second-nunjucks-transform', function NunjucksTransform(content) {
|
||||
// For a server build, we expect a server to run the second transform.
|
||||
if (serverBuild) {
|
||||
return content;
|
||||
}
|
||||
|
||||
// Only run the transform on files nunjucks would transform.
|
||||
if (!this.page.inputPath.match(/.(md|html|njk)$/)) {
|
||||
return content;
|
||||
}
|
||||
|
||||
/** This largely mimics what an app would do and just stubs out what we don't care about. */
|
||||
return nunjucks.renderString(content, {
|
||||
// Stub the server EJS shortcodes.
|
||||
server: {
|
||||
head: '',
|
||||
loginOrAvatar: '',
|
||||
flashes: '',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Paired shortcodes - {% shortCode %}content{% endShortCode %}
|
||||
@@ -117,29 +169,6 @@ export default function (eleventyConfig) {
|
||||
]),
|
||||
);
|
||||
|
||||
// SSR plugin
|
||||
if (!isDev) {
|
||||
//
|
||||
// Problematic components in SSR land:
|
||||
// - animation (breaks on navigation + ssr with Turbo)
|
||||
// - mutation-observer (why SSR this?)
|
||||
// - resize-observer (why SSR this?)
|
||||
// - tooltip (why SSR this?)
|
||||
//
|
||||
const omittedModules = [];
|
||||
const componentModules = componentList
|
||||
.filter(component => !omittedModules.includes(component.tagName.split(/wa-/)[1]))
|
||||
.map(component => {
|
||||
const name = component.tagName.split(/wa-/)[1];
|
||||
return `./dist/components/${name}/${name}.js`;
|
||||
});
|
||||
|
||||
eleventyConfig.addPlugin(litPlugin, {
|
||||
mode: 'worker',
|
||||
componentModules,
|
||||
});
|
||||
}
|
||||
|
||||
// Build the search index
|
||||
eleventyConfig.addPlugin(
|
||||
searchPlugin({
|
||||
@@ -166,6 +195,31 @@ export default function (eleventyConfig) {
|
||||
eleventyConfig.addPassthroughCopy(glob);
|
||||
}
|
||||
|
||||
// // SSR plugin
|
||||
// // Make sure this is the last thing, we don't want to run the risk of accidentally transforming shadow roots with the nunjucks 2nd transform.
|
||||
// if (!isDev) {
|
||||
// //
|
||||
// // Problematic components in SSR land:
|
||||
// // - animation (breaks on navigation + ssr with Turbo)
|
||||
// // - mutation-observer (why SSR this?)
|
||||
// // - resize-observer (why SSR this?)
|
||||
// // - tooltip (why SSR this?)
|
||||
// //
|
||||
// const omittedModules = [];
|
||||
// const componentModules = componentList
|
||||
// .filter(component => !omittedModules.includes(component.tagName.split(/wa-/)[1]))
|
||||
// .map(component => {
|
||||
// const name = component.tagName.split(/wa-/)[1];
|
||||
// const componentDirectory = process.env.UNBUNDLED_DIST_DIRECTORY || path.join('.', 'dist');
|
||||
// return path.join(componentDirectory, 'components', name, `${name}.js`);
|
||||
// });
|
||||
//
|
||||
// eleventyConfig.addPlugin(litPlugin, {
|
||||
// mode: 'worker',
|
||||
// componentModules,
|
||||
// });
|
||||
// }
|
||||
|
||||
return {
|
||||
markdownTemplateEngine: 'njk',
|
||||
dir: {
|
||||
|
||||
9
docs/_data/systemIcons.js
Normal file
9
docs/_data/systemIcons.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { inlined } from '../../dist/components/icon/library.wa.js';
|
||||
|
||||
let { classic } = inlined;
|
||||
let { solid, regular } = classic;
|
||||
|
||||
export default [
|
||||
...Object.entries(solid).map(([name, svg]) => ({ name, family: 'solid', variant: 'solid', svg })),
|
||||
...Object.entries(regular).map(([name, svg]) => ({ name, family: 'regular', variant: 'regular', svg })),
|
||||
];
|
||||
@@ -1,10 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-fa-kit-code="b10bfbde90" data-cdn-url="{% cdnUrl %}">
|
||||
<html lang="en" data-fa-kit-code="b10bfbde90" data-cdn-url="{% cdnUrl %}" class="wa-cloak">
|
||||
<head>
|
||||
{% include 'head.njk' %}
|
||||
<meta name="theme-color" content="#f36944">
|
||||
|
||||
<script type="module" src="/assets/scripts/code-examples.js"></script>
|
||||
|
||||
<script type="module" src="/assets/scripts/scroll.js"></script>
|
||||
<script type="module" src="/assets/scripts/turbo.js"></script>
|
||||
@@ -20,7 +19,7 @@
|
||||
</head>
|
||||
<body class="layout-{{ layout | stripExtension }}{{ ' page-wide' if wide }}">
|
||||
<!-- use view="desktop" as default to reduce layout jank on desktop site. -->
|
||||
<wa-page view="desktop" disable-navigation-toggle="">
|
||||
<wa-page view="desktop" disable-navigation-toggle="" mobile-breakpoint="1140">
|
||||
<header slot="header" class="wa-split">
|
||||
{# Logo #}
|
||||
<div id="docs-branding">
|
||||
@@ -33,13 +32,13 @@
|
||||
<span class="wa-desktop-only">{% include "logo.njk" %}</span>
|
||||
<span class="wa-mobile-only">{% include "logo-simple.njk" %}</span>
|
||||
</a>
|
||||
<small id="version-number" class="only-desktop">{{ package.version }}</small>
|
||||
<wa-badge variant="warning" appearance="filled" class="only-desktop">Alpha</wa-badge>
|
||||
<small id="version-number" class="wa-desktop-only">{{ package.version }}</small>
|
||||
<wa-badge variant="warning" appearance="filled" class="wa-desktop-only">Alpha</wa-badge>
|
||||
</div>
|
||||
|
||||
<div id="docs-toolbar" class="wa-cluster wa-gap-xs">
|
||||
{# Desktop selectors #}
|
||||
<div class="only-desktop wa-cluster wa-gap-xs">
|
||||
<div class="wa-desktop-only wa-cluster wa-gap-xs">
|
||||
{% include "preset-theme-selector.njk" %}
|
||||
{% include "color-scheme-selector.njk" %}
|
||||
</div>
|
||||
@@ -48,8 +47,11 @@
|
||||
<wa-button id="search-trigger" appearance="outlined" size="small" data-search>
|
||||
<wa-icon slot="prefix" name="magnifying-glass"></wa-icon>
|
||||
Search
|
||||
<kbd slot="suffix" class="only-desktop">/</kbd>
|
||||
<kbd slot="suffix" class="wa-desktop-only">/</kbd>
|
||||
</wa-button>
|
||||
|
||||
{# Login #}
|
||||
{% server "loginOrAvatar" %}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -76,14 +78,19 @@
|
||||
</aside>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{# Main #}
|
||||
<main id="content">
|
||||
{# Expandable outline #}
|
||||
{% if hasOutline %}
|
||||
<nav id="outline-expandable">
|
||||
<details class="outline-links">
|
||||
<summary>On this page</summary>
|
||||
</details>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
<div id="flashes">{% server "flashes" %}</div>
|
||||
|
||||
{% block header %}
|
||||
{% include 'breadcrumbs.njk' %}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
{% set breadcrumbs = page.url | breadcrumbs %}
|
||||
{% if breadcrumbs.length > 0 %}
|
||||
{% set ancestors = page.url | ancestors %}
|
||||
|
||||
{% if ancestors.length > 0 %}
|
||||
<wa-breadcrumb id="docs-breadcrumbs">
|
||||
{% for crumb in breadcrumbs %}
|
||||
<wa-breadcrumb-item href="{{ crumb.url }}">{{ crumb.title }}</wa-breadcrumb-item>
|
||||
{% for ancestor in ancestors %}
|
||||
{% if ancestor.page.url != "/" %}
|
||||
<wa-breadcrumb-item href="{{ ancestor.page.url }}">{{ ancestor.data.title }}</wa-breadcrumb-item>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<wa-breadcrumb-item>{# Current page #}</wa-breadcrumb-item>
|
||||
</wa-breadcrumb>
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
{# Cards for pages listed by category #}
|
||||
|
||||
<section id="grid" class="index-grid">
|
||||
{% for category, pages in allPages | groupByTags(categories) -%}
|
||||
<h2 class="index-category">{{ category | getCategoryTitle(categories) }}</h2>
|
||||
{%- for page in pages -%}
|
||||
{%- if not page.data.parent or listChildren -%}
|
||||
{% include "page-card.njk" %}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{% set groupedPages = allPages | groupPages(categories, page) %}
|
||||
{% for category, pages in groupedPages -%}
|
||||
{% if groupedPages.meta.groupCount > 1 and pages.length > 0 %}
|
||||
<h2 class="index-category" id="{{ category | slugify }}">
|
||||
{% if pages.meta.url %}<a href="{{ pages.meta.url }}">{{ pages.meta.title }}</a>
|
||||
{% else %}
|
||||
{{ pages.meta.title }}
|
||||
{% endif %}
|
||||
</h2>
|
||||
{% endif %}
|
||||
{%- for page in pages -%}
|
||||
{% include "page-card.njk" %}
|
||||
{%- endfor -%}
|
||||
{%- endfor -%}
|
||||
</section>
|
||||
|
||||
@@ -23,10 +23,12 @@
|
||||
<script src="/assets/scripts/hydration-errors.js"></script>
|
||||
<link rel="stylesheet" href="/assets/styles/hydration-errors.css">
|
||||
<link rel="preconnect" href="https://cdn.jsdelivr.net">
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.10/+esm"></script>
|
||||
|
||||
{# Internal components #}
|
||||
<script type="module" src="/assets/components/scoped.js"></script>
|
||||
|
||||
{# Web Awesome #}
|
||||
<script type="module" src="/dist/webawesome.ssr-loader.js"></script>
|
||||
<script type="module" src="/dist/webawesome.loader.js"></script>
|
||||
|
||||
<script type="module" src="/assets/scripts/theme-picker.js"></script>
|
||||
{# Preset Theme #}
|
||||
@@ -47,3 +49,6 @@
|
||||
<link rel="stylesheet" href="/dist/styles/webawesome.css" />
|
||||
<link id="color-stylesheet" rel="stylesheet" href="/dist/styles/utilities.css" />
|
||||
<link rel="stylesheet" href="/dist/styles/forms.css" />
|
||||
|
||||
{# Used by Web Awesome App to inject other assets into the head. #}
|
||||
{% server "head" %}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<a href="{{ page.url }}"{{ page.data.keywords | attr('data-keywords') }}>
|
||||
<wa-card with-header>
|
||||
<div slot="header">
|
||||
{% include "svgs/" + (page.data.icon or "thumbnail-placeholder") + ".njk" %}
|
||||
{% include "svgs/" + (page.data.icon or "thumbnail-placeholder") + ".njk" ignore missing %}
|
||||
</div>
|
||||
<span class="page-name">{{ page.data.title }}</span>
|
||||
{% if pageSubtitle -%}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
{# Some collections (like "patterns") will not have any items in the alpha build for example. So this checks to make sure the collection exists. #}
|
||||
{% if collections[tag] -%}
|
||||
{% set groupUrl %}/docs/{{ tag }}/{% endset %}
|
||||
{% set groupItem = groupUrl | getCollectionItemFromUrl %}
|
||||
{% set children = groupItem.data.children if groupItem.data.children.length > 0 else (collections[tag] | sort) %}
|
||||
|
||||
<wa-details {{ ((tag in (tags or [])) or (groupUrl in page.url)) | attr('open') }}>
|
||||
<h2 slot="summary">
|
||||
{% if groupUrl | getCollectionItemFromUrl %}
|
||||
{% if groupItem %}
|
||||
<a href="{{ groupUrl }}" title="Overview">{{ title or (tag | capitalize) }}
|
||||
<wa-icon name="grid-2"></wa-icon>
|
||||
</a>
|
||||
@@ -12,10 +15,8 @@
|
||||
{% endif %}
|
||||
</h2>
|
||||
<ul>
|
||||
{% for page in collections[tag] | sort %}
|
||||
{% if not page.data.parent -%}
|
||||
{% for page in children %}
|
||||
{% include 'sidebar-link.njk' %}
|
||||
{%- endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</wa-details>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% if not (isAlpha and page.data.noAlpha) and page.fileSlug != tag 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 %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{# Getting started #}
|
||||
<h2>Getting Started</h2>
|
||||
<ul>
|
||||
<li><a href="/docs/installation">Installation</a></li>
|
||||
<li><a href="/docs/">Installation</a></li>
|
||||
<li><a href="/docs/usage">Usage</a></li>
|
||||
<li><a href="/docs/customizing">Customizing</a></li>
|
||||
<li><a href="/docs/form-controls">Form Controls</a></li>
|
||||
|
||||
8
docs/_includes/svgs/action-panel.njk
Normal file
8
docs/_includes/svgs/action-panel.njk
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg width="96" height="57" viewBox="0 0 96 57" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 1H84C90.0751 1 95 5.92487 95 12V45C95 51.0751 90.0751 56 84 56H12C5.92487 56 1 51.0751 1 45V12C1 5.92487 5.92487 1 12 1Z" fill="white" stroke="#E4E5E9" stroke-width="2"/>
|
||||
<rect x="7" y="39" width="50" height="11" rx="4" fill="#4895FD"/>
|
||||
<rect x="7" y="8" width="41" height="5" fill="#616D8A"/>
|
||||
<rect x="7" y="19.75" width="76" height="3" fill="#D9D9D9"/>
|
||||
<rect x="7" y="25" width="76" height="3" fill="#D9D9D9"/>
|
||||
<rect x="7" y="32" width="76" height="3" fill="#D9D9D9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 587 B |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -1,31 +1,20 @@
|
||||
{% set paletteId = palette.fileSlug or page.fileSlug %}
|
||||
{% set suffixes = ['-80', '', '-20'] %}
|
||||
{% set width = 20 %}
|
||||
{% set height = 12 %}
|
||||
{% set height_core = 20 %}
|
||||
{% set gap_x = 4 %}
|
||||
{% set gap_y = 4 %}
|
||||
|
||||
{% set total_width = (width + gap_x) * hues|length %}
|
||||
{% set total_height = (height + gap_y) * suffixes|length + (height_core - height) %}
|
||||
<svg viewBox="0 0 {{ total_width }} {{ total_height }}" fill="none" xmlns="http://www.w3.org/2000/svg" class="wa-palette-{{ paletteId }} palette-icon">
|
||||
<style>
|
||||
@import url('/dist/styles/color/{{ paletteId }}.css') layer(palette.{{ paletteId }});
|
||||
.palette-icon {
|
||||
height: 8ch;
|
||||
}
|
||||
</style>
|
||||
<wa-scoped class="palette-icon-host">
|
||||
<template>
|
||||
<link rel="stylesheet" href="/dist/styles/color/{{ paletteId }}.css">
|
||||
<link rel="stylesheet" href="/assets/styles/theme-icons.css">
|
||||
|
||||
{% for hue in hues -%}
|
||||
{% set hueIndex = loop.index0 %}
|
||||
{% set y = 0 %}
|
||||
{% for suffix in suffixes -%}
|
||||
{% set swatch_height = height if suffix else height_core %}
|
||||
|
||||
<rect x="{{ hueIndex * (width + gap_x) }}" y="{{ y }}"
|
||||
width="{{ width }}" height="{{ swatch_height }}"
|
||||
fill="var(--wa-color-{{ hue }}{{ suffix }})" rx="2" />
|
||||
{% set y = y + swatch_height + gap_y %}
|
||||
{%- endfor %}
|
||||
{% endfor %}
|
||||
</svg>
|
||||
<div class="palette-icon" style="--hues: {{ hues|length }}; --suffixes: {{ suffixes|length }}">
|
||||
{% for hue in hues -%}
|
||||
{% set hueIndex = loop.index %}
|
||||
{% for suffix in suffixes -%}
|
||||
<div class="swatch"
|
||||
data-hue="{{ hue }}" data-suffix="{{ suffix }}"
|
||||
style="--color: var(--wa-color-{{ hue }}{{ suffix }}); grid-column: {{ hueIndex }}; grid-row: {{ loop.index }}"> </div>
|
||||
{%- endfor %}
|
||||
{%- endfor %}
|
||||
</div>
|
||||
</template>
|
||||
</wa-scoped>
|
||||
|
||||
10
docs/_includes/svgs/preview.njk
Normal file
10
docs/_includes/svgs/preview.njk
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="120" height="87" viewBox="0 0 240 178" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="39" width="67" height="63" rx="12" fill="#D9D9D9"/>
|
||||
<rect y="115" width="67" height="63" rx="12" fill="#D9D9D9"/>
|
||||
<rect width="67" height="19" rx="6" fill="#D9D9D9"/>
|
||||
<rect x="87" y="39" width="67" height="63" rx="12" fill="#D9D9D9"/>
|
||||
<rect x="174" y="39" width="67" height="63" rx="12" fill="#D9D9D9"/>
|
||||
<rect x="174" y="7" width="67" height="4.75" rx="2.375" fill="#D9D9D9"/>
|
||||
<rect x="87" y="115" width="67" height="63" rx="12" fill="#D9D9D9"/>
|
||||
<rect x="174" y="115" width="67" height="63" rx="12" fill="#D9D9D9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 631 B |
@@ -1,13 +1,13 @@
|
||||
{% set themeId = theme.fileSlug %}
|
||||
|
||||
<div>
|
||||
<template shadowrootmode="open">
|
||||
<wa-scoped class="theme-icon-host theme-color-icon-host">
|
||||
<template>
|
||||
<link rel="stylesheet" href="/dist/styles/utilities.css">
|
||||
<link rel="stylesheet" href="/dist/styles/themes/{{ page.fileSlug or 'default' }}.css">
|
||||
<link rel="stylesheet" href="/dist/styles/themes/{{ themeId }}/color.css">
|
||||
<link rel="stylesheet" href="/assets/styles/theme-icons.css">
|
||||
|
||||
<div class="theme-color-icon wa-theme-{{ themeId }}">
|
||||
<div class="theme-icon theme-color-icon wa-theme-{{ themeId }}">
|
||||
<div class="wa-brand wa-accent">A</div>
|
||||
<div class="wa-brand wa-outlined">A</div>
|
||||
<div class="wa-brand wa-filled">A</div>
|
||||
@@ -21,4 +21,4 @@
|
||||
{# <div class="wa-warning wa-outlined wa-filled"><wa-icon slot="icon" name="triangle-exclamation" variant="regular"></wa-icon></div> #}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</wa-scoped>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{% set themeId = theme.fileSlug %}
|
||||
{% set themeId = theme.fileSlug or page.fileSlug %}
|
||||
|
||||
<div>
|
||||
<template shadowrootmode="open">
|
||||
<wa-scoped class="theme-icon-host theme-typography-icon-host">
|
||||
<template>
|
||||
<link rel="stylesheet" href="/dist/styles/native/content.css">
|
||||
<link rel="stylesheet" href="/dist/styles/native/blockquote.css">
|
||||
<link rel="stylesheet" href="/dist/styles/themes/{{ page.fileSlug or 'default' }}.css">
|
||||
<link rel="stylesheet" href="/dist/styles/themes/{{ themeId }}/typography.css">
|
||||
<link rel="stylesheet" href="/assets/styles/theme-icons.css">
|
||||
|
||||
<div class="theme-typography-icon wa-theme-{{ themeId }}" data-no-outline data-no-anchor role="presentation">
|
||||
<div class="theme-icon theme-typography-icon wa-theme-{{ themeId }}" data-no-outline data-no-anchor role="presentation">
|
||||
<h3>Title</h3>
|
||||
<p>Body text</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</wa-scoped>
|
||||
|
||||
29
docs/_includes/svgs/theme.njk
Normal file
29
docs/_includes/svgs/theme.njk
Normal file
@@ -0,0 +1,29 @@
|
||||
{% set themeId = theme.fileSlug or page.fileSlug %}
|
||||
|
||||
|
||||
<wa-scoped class="theme-icon-host theme-overall-icon-host">
|
||||
<template>
|
||||
<link rel="stylesheet" href="/dist/styles/utilities.css">
|
||||
<link rel="stylesheet" href="/dist/styles/native/content.css">
|
||||
<link rel="stylesheet" href="/dist/styles/themes/{{ themeId }}.css">
|
||||
<link rel="stylesheet" href="/assets/styles/theme-icons.css">
|
||||
|
||||
<div class="theme-icon theme-overall-icon" role="presentation" data-no-anchor data-no-outline>
|
||||
<div class="row row-1">
|
||||
<h2>Aa</h2>
|
||||
<div class="swatches">
|
||||
<div class="wa-brand"></div>
|
||||
|
||||
<div class="wa-success"></div>
|
||||
<div class="wa-warning"></div>
|
||||
<div class="wa-danger"></div>
|
||||
<div class="wa-neutral"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-2">
|
||||
<wa-input value="Input" size="small" inert></wa-input>
|
||||
<wa-button size="small" variant="brand" inert>Go</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</wa-scoped>
|
||||
@@ -255,7 +255,7 @@
|
||||
{# Importing #}
|
||||
<h2>Importing</h2>
|
||||
<p>
|
||||
The <a href="/docs/installation/#quick-start-autoloading-via-cdn">autoloader</a> is the recommended way to import components. If you prefer to do it manually, use one of the following code snippets.
|
||||
The <a href="/docs/#quick-start-autoloading-via-cdn">autoloader</a> is the recommended way to import components. If you prefer to do it manually, use one of the following code snippets.
|
||||
</p>
|
||||
|
||||
<wa-tab-group label="How would you like to import this component?">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
layout: page-outline
|
||||
tags: ["overview"]
|
||||
---
|
||||
{% set forTag = forTag or (page.url | split('/') | last) %}
|
||||
{% if description %}
|
||||
@@ -13,8 +12,10 @@ tags: ["overview"]
|
||||
</wa-input>
|
||||
</div>
|
||||
|
||||
{% set allPages = collections[forTag] %}
|
||||
{% set allPages = allPages or collections[forTag] %}
|
||||
{% if allPages and allPages.length > 0 %}
|
||||
{% include "grouped-pages.njk" %}
|
||||
{% endif %}
|
||||
|
||||
<link href="/assets/styles/filter.css" rel="stylesheet">
|
||||
<script type="module" src="/assets/scripts/filter.js"></script>
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
{% set hasSidebar = true %}
|
||||
{% set hasOutline = false %}
|
||||
{% if hasSidebar == undefined %}
|
||||
{% set hasSidebar = true %}
|
||||
{% endif %}
|
||||
|
||||
{% if hasOutline == undefined %}
|
||||
{% set hasOutline = false %}
|
||||
{% endif %}
|
||||
|
||||
{% extends "../_includes/base.njk" %}
|
||||
|
||||
@@ -30,12 +30,21 @@
|
||||
|
||||
{% include 'breadcrumbs.njk' %}
|
||||
|
||||
<h1 v-if="saved" class="title">
|
||||
{% raw %}{{ saved.title }}{% endraw %}
|
||||
<wa-icon-button name="pencil" label="Rename palette" @click="rename"></wa-icon-button>
|
||||
<wa-icon-button class="delete" name="trash" label="Delete palette" @click="deleteSaved"></wa-icon-button>
|
||||
<h1 class="title">
|
||||
<span v-content="title">{{ title }}</span>
|
||||
<template v-if="saved || tweaked">
|
||||
<wa-icon-button name="pencil" label="Rename palette" @click="rename"></wa-icon-button>
|
||||
<wa-icon-button v-if="saved" class="delete" name="trash" label="Delete palette" @click="deleteSaved"></wa-icon-button>
|
||||
<wa-button @click="save()" :disabled="!unsavedChanges"
|
||||
:variant="unsavedChanges ? 'success' : 'neutral'" size="small" :appearance="unsavedChanges ? 'accent' : 'outlined'">
|
||||
<span slot="prefix" class="icon-modifier">
|
||||
<wa-icon name="sidebar" variant="regular"></wa-icon>
|
||||
<wa-icon name="circle-plus" class="modifier" style="color: light-dark(var(--wa-color-green-70), var(--wa-color-green-60));"></wa-icon>
|
||||
</span>
|
||||
<span v-content="unsavedChanges ? 'Save' : 'Saved'">Save</span>
|
||||
</wa-button>
|
||||
</template>
|
||||
</h1>
|
||||
<h1 v-if="!saved" class="title">{{ title }}</h1>
|
||||
|
||||
<div class="block-info">
|
||||
<code class="class">.wa-palette-{{ paletteId }}</code>
|
||||
@@ -59,7 +68,7 @@
|
||||
<wa-icon name="sliders-simple" slot="icon" variant="regular"></wa-icon>
|
||||
This palette has been tweaked.
|
||||
<div class="wa-cluster wa-gap-xs">
|
||||
<wa-tag v-for="tweakHumanReadable, param in tweaksHumanReadable" removable @wa-remove="reset(param)">{% raw %}{{ tweakHumanReadable }}{% endraw %}</wa-tag>
|
||||
<wa-tag v-for="tweakHumanReadable, param in tweaksHumanReadable" removable @wa-remove="reset(param)" v-content="tweakHumanReadable"></wa-tag>
|
||||
</div>
|
||||
|
||||
<wa-button @click="reset()" appearance="outlined" variant="danger">
|
||||
@@ -68,13 +77,6 @@
|
||||
</span>
|
||||
Reset
|
||||
</wa-button>
|
||||
<wa-button v-if="!saved" @click="save" variant="success">
|
||||
<span slot="prefix" class="icon-modifier">
|
||||
<wa-icon name="sidebar" variant="regular"></wa-icon>
|
||||
<wa-icon name="circle-plus" class="modifier" style="color: light-dark(var(--wa-color-green-70), var(--wa-color-green-60));"></wa-icon>
|
||||
</span>
|
||||
Save
|
||||
</wa-button>
|
||||
</wa-callout>
|
||||
|
||||
<table class="colors main wa-palette-{{ paletteId }}">
|
||||
@@ -124,7 +126,7 @@
|
||||
{% for h in hues -%}
|
||||
{%- if h !== 'gray' -%}
|
||||
<wa-radio-button id="gray-undertone-{{ h }}" value="{{ h }}" label="{{ h | capitalize }}" style="--color: var(--wa-color-{{ h }})"></wa-radio-button>
|
||||
<wa-tooltip for="gray-undertone-{{ h }}" hoist>
|
||||
<wa-tooltip for="gray-undertone-{{ h }}">
|
||||
{{ h | capitalize }}
|
||||
</wa-tooltip>
|
||||
{%- endif -%}
|
||||
@@ -139,7 +141,7 @@
|
||||
@input="tweaking.grayChroma = true" @change="tweaking.grayChroma = false">
|
||||
<div slot="label">
|
||||
Gray colorfulness
|
||||
<wa-icon-button @click="grayChroma = originalGrayChroma" class="clear-button" name="circle-xmark" library="system" variant="regular" label="Reset"></wa-icon-button>
|
||||
<wa-icon-button @click="grayChroma = originalGrayChroma" class="clear-button" name="system:circle-xmark" variant="regular" label="Reset"></wa-icon-button>
|
||||
</div>
|
||||
</wa-slider>
|
||||
<div class="label-min">Neutral</div>
|
||||
@@ -158,7 +160,7 @@
|
||||
@change="tweaking.hue = tweaking.{{ hue }} = false">
|
||||
<div slot="label">
|
||||
Tweak {{ hue }} hue
|
||||
<wa-icon-button @click="hueShifts.{{ hue }} = 0" class="clear-button" name="circle-xmark" library="system" variant="regular" label="Reset"></wa-icon-button>
|
||||
<wa-icon-button @click="hueShifts.{{ hue }} = 0" class="clear-button" name="system:circle-xmark" variant="regular" label="Reset"></wa-icon-button>
|
||||
</div>
|
||||
</wa-slider>
|
||||
<div class="label-min">More {{hueBefore}}</div>
|
||||
@@ -202,7 +204,7 @@ style="--min: {{ chromaScaleBounds[0] }}; --max: {{ chromaScaleBounds[1] }};">
|
||||
@change="tweaking.chroma = false">
|
||||
<div slot="label">
|
||||
Overall colorfulness
|
||||
<wa-icon-button @click="chromaScale = 1" class="clear-button" name="circle-xmark" library="system" variant="regular" label="Reset"></wa-icon-button>
|
||||
<wa-icon-button @click="chromaScale = 1" class="clear-button" name="system:circle-xmark" variant="regular" label="Reset"></wa-icon-button>
|
||||
</div>
|
||||
</wa-slider>
|
||||
<div class="label-min">More muted</div>
|
||||
|
||||
5
docs/_layouts/patterns.njk
Normal file
5
docs/_layouts/patterns.njk
Normal file
@@ -0,0 +1,5 @@
|
||||
{% extends '../_layouts/block.njk' %}
|
||||
|
||||
{% block head %}
|
||||
<link href="/docs/patterns/patterns.css" rel="stylesheet">
|
||||
{% endblock %}
|
||||
@@ -25,8 +25,8 @@ wa_data.palettes = {
|
||||
{% endfor %}
|
||||
};
|
||||
</script>
|
||||
<link href="{{ page.url }}../remix.css" rel="stylesheet">
|
||||
<script src="{{ page.url }}../remix.js" type="module"></script>
|
||||
<link href="/docs/themes/remix.css" rel="stylesheet">
|
||||
<script src="/docs/themes/remix.js" type="module"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
@@ -68,7 +68,7 @@ wa_data.palettes = {
|
||||
<wa-option label="{{ palette.data.title }}" value="{{ palette.fileSlug if not currentPalette }}" {{ (palette.fileSlug if currentPalette) | attr('data-id') }}>
|
||||
<wa-card with-header>
|
||||
<div slot="header">
|
||||
{% include "svgs/" + (palette.data.icon or "thumbnail-placeholder") + ".njk" %}
|
||||
{% include "svgs/" + (palette.data.icon or "thumbnail-placeholder") + ".njk" ignore missing %}
|
||||
</div>
|
||||
<span class="page-name">
|
||||
{{ palette.data.title }}
|
||||
|
||||
@@ -102,12 +102,7 @@ const templates = {
|
||||
export function codeExamplesPlugin(eleventyConfig, options = {}) {
|
||||
const defaultOptions = {
|
||||
container: 'body',
|
||||
defaultOpen: (code, { outputPathIndex }) => {
|
||||
return (
|
||||
outputPathIndex === 1 && // is first
|
||||
code.textContent.length < 500
|
||||
); // is short
|
||||
},
|
||||
defaultOpen: () => false,
|
||||
};
|
||||
options = { ...defaultOptions, ...options };
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export function copyCodePlugin(eleventyConfig, options = {}) {
|
||||
|
||||
// Add a copy button
|
||||
pre.innerHTML += `<wa-icon-button href="#${preId}" class="block-link-icon" name="link"></wa-icon-button>
|
||||
<wa-copy-button from="${codeId}" class="copy-button" hoist></wa-copy-button>`;
|
||||
<wa-copy-button from="${codeId}" class="copy-button"></wa-copy-button>`;
|
||||
});
|
||||
|
||||
return doc.toString();
|
||||
|
||||
@@ -29,6 +29,9 @@ function getCollection(name) {
|
||||
}
|
||||
|
||||
export function getCollectionItemFromUrl(url, collection) {
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
collection ??= getCollection.call(this, 'all') || [];
|
||||
return collection.find(item => item.url === url);
|
||||
}
|
||||
@@ -42,35 +45,33 @@ export function split(text, separator) {
|
||||
return (text + '').split(separator).filter(Boolean);
|
||||
}
|
||||
|
||||
export function breadcrumbs(url, { withCurrent = false } = {}) {
|
||||
const parts = split(url, '/');
|
||||
const ret = [];
|
||||
export function ancestors(url, { withCurrent = false, withRoot = false } = {}) {
|
||||
let ret = [];
|
||||
let currentUrl = url;
|
||||
let currentItem = getCollectionItemFromUrl.call(this, url);
|
||||
|
||||
while (parts.length) {
|
||||
let partialUrl = '/' + parts.join('/') + '/';
|
||||
let item = getCollectionItemFromUrl.call(this, partialUrl);
|
||||
|
||||
if (item && (partialUrl !== url || withCurrent)) {
|
||||
let title = item.data.title;
|
||||
if (title) {
|
||||
ret.unshift({ url: partialUrl, title });
|
||||
}
|
||||
}
|
||||
|
||||
parts.pop();
|
||||
|
||||
if (item?.data.parent) {
|
||||
let parentURL = item.data.parent;
|
||||
if (!item.data.parent.startsWith('/')) {
|
||||
// Parent is in the same directory
|
||||
parts.push(item.data.parent);
|
||||
parentURL = '/' + parts.join('/') + '/';
|
||||
}
|
||||
|
||||
let parentBreadcrumbs = breadcrumbs.call(this, parentURL, { withCurrent: true });
|
||||
return [...parentBreadcrumbs, ...ret];
|
||||
if (!currentItem) {
|
||||
// Might have eleventyExcludeFromCollections, jump to parent
|
||||
let parentUrl = this.ctx.parentUrl;
|
||||
if (parentUrl) {
|
||||
url = parentUrl;
|
||||
}
|
||||
}
|
||||
|
||||
for (let item; (item = getCollectionItemFromUrl.call(this, url)); url = item.data.parentUrl) {
|
||||
ret.unshift(item);
|
||||
}
|
||||
|
||||
if (!withRoot && ret[0]?.page.url === '/') {
|
||||
// Remove root
|
||||
ret.shift();
|
||||
}
|
||||
|
||||
if (!withCurrent && ret.at(-1)?.page.url === currentUrl) {
|
||||
// Remove current page
|
||||
ret.pop();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -177,72 +178,196 @@ export function sort(arr, by = { 'data.order': 1, 'data.title': '' }) {
|
||||
});
|
||||
}
|
||||
|
||||
export function show(page) {
|
||||
return !(page.data.noAlpha && page.data.isAlpha) && !page.data.unlisted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group an 11ty collection (or any array of objects with a `data.tags` property) by certain tags.
|
||||
* @param {object[]} collection
|
||||
* @param { Object<string, string> | (string | Object<string, string>)[]} [tags] The tags to group by. If not provided/empty, defaults to grouping by all tags.
|
||||
* @returns { Object.<string, object[]> } An object with keys for each tag, and an array of items for each tag.
|
||||
* @param { Object<string, string> | string[]} [options] Options object or array of tags to group by.
|
||||
* @param {string[] | true} [options.tags] Tags to group by. If true, groups by all tags.
|
||||
* If not provided/empty, defaults to grouping by page hierarchy, with any pages with more than 1 children becoming groups.
|
||||
* @param {string[]} [options.groups] The groups to use if only a subset or a specific order is desired. Defaults to `options.tags`.
|
||||
* @param {string[]} [options.titles] Any title overrides for groups.
|
||||
* @param {string | false} [options.other="Other"] The title to use for the "Other" group. If `false`, the "Other" group is removed..
|
||||
* @returns { Object.<string, object[]> } An object of group ids to arrays of page objects.
|
||||
*/
|
||||
export function groupByTags(collection, tags) {
|
||||
export function groupPages(collection, options = {}, page) {
|
||||
if (!collection) {
|
||||
console.error(`Empty collection passed to groupByTags() to group by ${JSON.stringify(tags)}`);
|
||||
}
|
||||
if (!tags) {
|
||||
// Default to grouping by union of all tags
|
||||
tags = Array.from(new Set(collection.flatMap(item => item.data.tags)));
|
||||
} else if (Array.isArray(tags)) {
|
||||
// May contain objects of one-off tag -> label mappings
|
||||
tags = tags.map(tag => (typeof tag === 'object' ? Object.keys(tag)[0] : tag));
|
||||
} else if (typeof tags === 'object') {
|
||||
// tags is an object of tags to labels, so we just want the keys
|
||||
tags = Object.keys(tags);
|
||||
console.error(`Empty collection passed to groupPages() to group by ${JSON.stringify(options)}`);
|
||||
}
|
||||
|
||||
let ret = Object.fromEntries(tags.map(tag => [tag, []]));
|
||||
ret.other = [];
|
||||
if (Array.isArray(options)) {
|
||||
options = { tags: options };
|
||||
}
|
||||
|
||||
let { tags, groups, titles = {}, other = 'Other', filter = show } = options;
|
||||
|
||||
if (groups === undefined && Array.isArray(tags)) {
|
||||
groups = tags;
|
||||
}
|
||||
|
||||
let grouping;
|
||||
|
||||
if (tags) {
|
||||
grouping = {
|
||||
isGroup: item => undefined,
|
||||
getCandidateGroups: item => item.data.tags,
|
||||
getGroupMeta: group => ({}),
|
||||
};
|
||||
} else {
|
||||
grouping = {
|
||||
isGroup: item => (item.data.children.length >= 2 ? item.page.url : undefined),
|
||||
getCandidateGroups: item => {
|
||||
let parentUrl = item.data.parentUrl;
|
||||
if (page?.url === parentUrl) {
|
||||
return [];
|
||||
}
|
||||
return [parentUrl];
|
||||
},
|
||||
getGroupMeta: group => {
|
||||
let item = byUrl[group] || getCollectionItemFromUrl.call(this, group);
|
||||
return {
|
||||
title: item?.data.title,
|
||||
url: group,
|
||||
item,
|
||||
};
|
||||
},
|
||||
sortGroups: groups => sort(groups.map(url => byUrl[url]).filter(Boolean)).map(item => item.page.url),
|
||||
};
|
||||
}
|
||||
|
||||
let byUrl = {};
|
||||
let byParentUrl = {};
|
||||
|
||||
if (filter) {
|
||||
collection = collection.filter(filter);
|
||||
}
|
||||
|
||||
for (let item of collection) {
|
||||
let categorized = false;
|
||||
let url = item.page.url;
|
||||
let parentUrl = item.data.parentUrl;
|
||||
|
||||
for (let tag of tags) {
|
||||
if (item.data.tags.includes(tag)) {
|
||||
ret[tag].push(item);
|
||||
categorized = true;
|
||||
}
|
||||
}
|
||||
byUrl[url] = item;
|
||||
|
||||
if (!categorized) {
|
||||
ret.other.push(item);
|
||||
if (parentUrl) {
|
||||
byParentUrl[parentUrl] ??= [];
|
||||
byParentUrl[parentUrl].push(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty categories
|
||||
for (let category in ret) {
|
||||
if (ret[category].length === 0) {
|
||||
delete ret[category];
|
||||
let urlToGroups = {};
|
||||
|
||||
for (let item of collection) {
|
||||
let url = item.page.url;
|
||||
let parentUrl = item.data.parentUrl;
|
||||
|
||||
if (grouping.isGroup(item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let parentItem = byUrl[parentUrl];
|
||||
if (parentItem && !grouping.isGroup(parentItem)) {
|
||||
// Their parent is also here and is not a group
|
||||
continue;
|
||||
}
|
||||
|
||||
let candidateGroups = grouping.getCandidateGroups(item);
|
||||
|
||||
if (groups) {
|
||||
candidateGroups = candidateGroups.filter(group => groups.includes(group));
|
||||
}
|
||||
|
||||
urlToGroups[url] ??= [];
|
||||
|
||||
for (let group of candidateGroups) {
|
||||
urlToGroups[url].push(group);
|
||||
}
|
||||
}
|
||||
|
||||
let ret = {};
|
||||
|
||||
for (let url in urlToGroups) {
|
||||
let groups = urlToGroups[url];
|
||||
let item = byUrl[url];
|
||||
|
||||
if (groups.length === 0) {
|
||||
// Not filtered out but also not categorized
|
||||
groups = ['other'];
|
||||
}
|
||||
|
||||
for (let group of groups) {
|
||||
ret[group] ??= [];
|
||||
ret[group].push(item);
|
||||
|
||||
if (!ret[group].meta) {
|
||||
if (group === 'other') {
|
||||
ret[group].meta = { title: other };
|
||||
} else {
|
||||
ret[group].meta = grouping.getGroupMeta(group);
|
||||
ret[group].meta.title = titles[group] ?? ret[group].meta.title ?? capitalize(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (other === false) {
|
||||
delete ret.other;
|
||||
}
|
||||
|
||||
// Sort
|
||||
let sortedGroups = groups ?? grouping.sortGroups?.(Object.keys(ret));
|
||||
|
||||
if (sortedGroups) {
|
||||
ret = sortObject(ret, sortedGroups);
|
||||
} else {
|
||||
// At least make sure other is last
|
||||
if (ret.other) {
|
||||
let otherGroup = ret.other;
|
||||
delete ret.other;
|
||||
ret.other = otherGroup;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(ret, 'meta', {
|
||||
value: {
|
||||
groupCount: Object.keys(ret).length,
|
||||
},
|
||||
enumerable: false,
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort an object by its keys
|
||||
* @param {*} obj
|
||||
* @param {function | string[]} order
|
||||
*/
|
||||
function sortObject(obj, order) {
|
||||
let ret = {};
|
||||
let sortedKeys = Array.isArray(order) ? order : Object.keys(obj).sort(order);
|
||||
|
||||
for (let key of sortedKeys) {
|
||||
if (key in obj) {
|
||||
ret[key] = obj[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Add any keys that weren't in the order
|
||||
for (let key in obj) {
|
||||
if (!(key in ret)) {
|
||||
ret[key] = obj[key];
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function getCategoryTitle(category, categories) {
|
||||
let title;
|
||||
if (Array.isArray(categories)) {
|
||||
// Find relevant entry
|
||||
// [{id: "Title"}, id2, ...]
|
||||
title = categories.find(entry => typeof entry === 'object' && entry?.[category])?.[category];
|
||||
} else if (typeof categories === 'object') {
|
||||
// {id: "Title", id2: "Title 2", ...}
|
||||
title = categories[category];
|
||||
}
|
||||
|
||||
if (title) {
|
||||
return title;
|
||||
}
|
||||
|
||||
// Capitalized
|
||||
return category.charAt(0).toUpperCase() + category.slice(1);
|
||||
function capitalize(str) {
|
||||
str += '';
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
const IDENTITY = x => x;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { mkdir, writeFile } from 'fs/promises';
|
||||
import lunr from 'lunr';
|
||||
import { parse } from 'node-html-parser';
|
||||
import * as path from 'path';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
function collapseWhitespace(string) {
|
||||
@@ -52,8 +53,9 @@ export function searchPlugin(options = {}) {
|
||||
return content;
|
||||
});
|
||||
|
||||
eleventyConfig.on('eleventy.after', ({ dir }) => {
|
||||
const outputFilename = join(dir.output, 'search.json');
|
||||
eleventyConfig.on('eleventy.after', ({ directories }) => {
|
||||
const { output } = directories;
|
||||
const outputFilename = path.resolve(join(output, 'search.json'));
|
||||
const map = [];
|
||||
const searchIndex = lunr(async function () {
|
||||
let index = 0;
|
||||
|
||||
171
docs/assets/components/scoped.js
Normal file
171
docs/assets/components/scoped.js
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* Low-level utility to encapsulate a bit of HTML (mainly to apply certain stylesheets to it without them leaking to the rest of the page)
|
||||
* Usage: <wa-scoped><template><!-- your HTML here --></template></wa-scoped>
|
||||
*/
|
||||
import { discover } from '/dist/webawesome.js';
|
||||
|
||||
const imports = new Set();
|
||||
const fontFaceRules = new Set();
|
||||
|
||||
export default class WaScoped extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' });
|
||||
|
||||
this.observer = new MutationObserver(() => this.render());
|
||||
this.observer.observe(this, { childList: true, subtree: true, characterData: true });
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
this.ownerDocument.documentElement.addEventListener('wa-color-scheme-change', e =>
|
||||
this.#applyDarkMode(e.detail.dark),
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
this.observer.takeRecords();
|
||||
this.observer.disconnect();
|
||||
|
||||
this.shadowRoot.innerHTML = '';
|
||||
|
||||
// To avoid mutating this.childNodes while iterating over it
|
||||
let nodes = [];
|
||||
|
||||
for (let template of this.childNodes) {
|
||||
// Other solutions we can try if needed: <script type="text/html">, or comment nodes
|
||||
if (template instanceof HTMLTemplateElement) {
|
||||
if (template.content.childNodes.length > 0) {
|
||||
nodes.push(template.content.cloneNode(true));
|
||||
} else if (template.childNodes.length > 0) {
|
||||
// Fake template, suck its children out of the light DOM
|
||||
nodes.push(...template.childNodes);
|
||||
}
|
||||
} else {
|
||||
// Regular child, suck it out of the light DOM
|
||||
nodes.push(template);
|
||||
}
|
||||
}
|
||||
|
||||
this.shadowRoot.append(...nodes);
|
||||
|
||||
this.#fixStyles();
|
||||
this.#applyDarkMode();
|
||||
|
||||
discover(this.shadowRoot);
|
||||
|
||||
this.observer.observe(this, { childList: true, subtree: true, characterData: true });
|
||||
}
|
||||
|
||||
#applyDarkMode(isDark = getComputedStyle(this).colorScheme === 'dark') {
|
||||
// Hack to make dark mode work
|
||||
// NOTE If any child nodes actually have .wa-dark, this will override it
|
||||
for (let node of this.shadowRoot.children) {
|
||||
node.classList.toggle('wa-dark', isDark);
|
||||
}
|
||||
this.classList.toggle('wa-dark', isDark);
|
||||
}
|
||||
|
||||
/**
|
||||
* @font-face does not work in shadow DOM in Chrome & FF, as of March 2025 https://issues.chromium.org/issues/41085401
|
||||
* This works around this issue by traversing the shadow DOM CSS looking
|
||||
* for @font-face rules or CSS imports to known font providers and copies them to the main document
|
||||
*/
|
||||
async #fixStyles() {
|
||||
let styleElements = [...this.shadowRoot.querySelectorAll('link[rel="stylesheet"], style')];
|
||||
|
||||
let loadStates = styleElements.map(element => {
|
||||
try {
|
||||
if (element.sheet?.cssRules) {
|
||||
// Already loaded
|
||||
return Promise.resolve(element.sheet);
|
||||
}
|
||||
} catch (e) {
|
||||
// CORS
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
element.addEventListener('load', e => resolve(element.sheet));
|
||||
element.addEventListener('error', e => reject(null));
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.allSettled(loadStates);
|
||||
|
||||
let fontRules = findFontFaceRules(...this.shadowRoot.styleSheets);
|
||||
|
||||
if (!fontRules.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let doc = this.ownerDocument;
|
||||
// Why not adoptedStyleSheets? Can't have @import in those yet
|
||||
let id = `wa-scoped-hoisted-fonts`;
|
||||
let style = doc.head.querySelector('style#' + id);
|
||||
if (!style) {
|
||||
style = Object.assign(doc.createElement('style'), { id, textContent: ' ' });
|
||||
doc.head.append(style);
|
||||
}
|
||||
let sheet = style.sheet;
|
||||
|
||||
for (let rule of fontRules) {
|
||||
let cssText = rule.cssText;
|
||||
if (rule.type === CSSRule.FONT_FACE_RULE) {
|
||||
if (fontFaceRules.has(cssText)) {
|
||||
continue;
|
||||
}
|
||||
fontFaceRules.add(cssText);
|
||||
sheet.insertRule(cssText);
|
||||
} else if (rule.type === CSSRule.IMPORT_RULE) {
|
||||
if (imports.has(rule.href)) {
|
||||
continue;
|
||||
}
|
||||
imports.add(rule.href);
|
||||
sheet.insertRule(cssText, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static observedAttributes = [];
|
||||
}
|
||||
|
||||
customElements.define('wa-scoped', WaScoped);
|
||||
|
||||
export const WEB_FONT_HOSTS = [
|
||||
'fonts.googleapis.com',
|
||||
'fonts.gstatic.com',
|
||||
'use.typekit.net',
|
||||
'fonts.adobe.com',
|
||||
'kit.fontawesome.com',
|
||||
'pro.fontawesome.com',
|
||||
'cdn.materialdesignicons.com',
|
||||
];
|
||||
|
||||
function findFontFaceRules(...stylesheets) {
|
||||
let ret = [];
|
||||
|
||||
for (let sheet of stylesheets) {
|
||||
let rules;
|
||||
try {
|
||||
rules = sheet.cssRules;
|
||||
} catch (e) {
|
||||
// CORS
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let rule of rules) {
|
||||
if (rule.type === CSSRule.FONT_FACE_RULE) {
|
||||
ret.push(rule);
|
||||
} else if (rule.type === CSSRule.IMPORT_RULE) {
|
||||
if (WEB_FONT_HOSTS.some(host => rule.href.includes(host))) {
|
||||
ret.push(rule);
|
||||
} else if (rule.styleSheet) {
|
||||
ret.push(...findFontFaceRules(rule.styleSheet));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
document.addEventListener('click', event => {
|
||||
const toggle = event.target?.closest('.code-example-toggle');
|
||||
const pen = event.target?.closest('.code-example-pen');
|
||||
|
||||
// Toggle source
|
||||
if (toggle) {
|
||||
const codeExample = toggle.closest('.code-example');
|
||||
const isOpen = !codeExample.classList.contains('open');
|
||||
|
||||
toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
||||
codeExample.classList.toggle('open', isOpen);
|
||||
}
|
||||
|
||||
// Edit in CodePen
|
||||
if (pen) {
|
||||
const codeExample = pen.closest('.code-example');
|
||||
const code = codeExample.querySelector('code');
|
||||
const cdnUrl = document.documentElement.dataset.cdnUrl;
|
||||
const html =
|
||||
`<script type="module" src="${cdnUrl}webawesome.loader.js"></script>\n` +
|
||||
`<link rel="stylesheet" href="${cdnUrl}styles/themes/default.css">\n` +
|
||||
`<link rel="stylesheet" href="${cdnUrl}styles/webawesome.css">\n` +
|
||||
`<link rel="stylesheet" href="${cdnUrl}styles/utilities.css">\n\n` +
|
||||
`${code.textContent}`;
|
||||
const css = 'html > body {\n padding: 2rem !important;\n}';
|
||||
const js = '';
|
||||
|
||||
const form = document.createElement('form');
|
||||
form.action = 'https://codepen.io/pen/define';
|
||||
form.method = 'POST';
|
||||
form.target = '_blank';
|
||||
|
||||
const data = {
|
||||
title: '',
|
||||
description: '',
|
||||
tags: ['webawesome'],
|
||||
editors: '1000',
|
||||
head: '<meta name="viewport" content="width=device-width">',
|
||||
html_classes: '',
|
||||
css_external: '',
|
||||
js_external: '',
|
||||
js_module: true,
|
||||
js_pre_processor: 'none',
|
||||
html,
|
||||
css,
|
||||
js,
|
||||
};
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'data';
|
||||
input.value = JSON.stringify(data);
|
||||
form.append(input);
|
||||
|
||||
document.documentElement.append(form);
|
||||
form.submit();
|
||||
form.remove();
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
167
docs/assets/scripts/my.js
Normal file
167
docs/assets/scripts/my.js
Normal file
@@ -0,0 +1,167 @@
|
||||
const my = (globalThis.my = new EventTarget());
|
||||
export default my;
|
||||
|
||||
class PersistedArray extends Array {
|
||||
constructor(key) {
|
||||
super();
|
||||
this.key = key;
|
||||
|
||||
if (this.key) {
|
||||
this.fromLocalStorage();
|
||||
}
|
||||
|
||||
// Items were updated in another tab
|
||||
addEventListener('storage', event => {
|
||||
if (event.key === this.key || !event.key) {
|
||||
this.fromLocalStorage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update data from local storage
|
||||
*/
|
||||
fromLocalStorage() {
|
||||
// First, empty the array
|
||||
this.splice(0, this.length);
|
||||
|
||||
// Then, fill it with the data from local storage
|
||||
let saved = localStorage[this.key] ? JSON.parse(localStorage[this.key]) : null;
|
||||
|
||||
if (saved) {
|
||||
this.push(...saved);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data to local storage
|
||||
*/
|
||||
toLocalStorage() {
|
||||
if (this.length > 0) {
|
||||
localStorage[this.key] = JSON.stringify(this);
|
||||
} else {
|
||||
delete localStorage[this.key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SavedEntities extends EventTarget {
|
||||
constructor({ key, type, url }) {
|
||||
super();
|
||||
this.key = key;
|
||||
this.type = type;
|
||||
this.url = url ?? type + 's';
|
||||
this.saved = new PersistedArray(key);
|
||||
|
||||
let all = this;
|
||||
this.entityPrototype = {
|
||||
type: this.type,
|
||||
baseUrl: this.baseUrl,
|
||||
|
||||
get url() {
|
||||
return all.getURL(this);
|
||||
},
|
||||
|
||||
get parentUrl() {
|
||||
return all.getParentURL(this);
|
||||
},
|
||||
|
||||
delete() {
|
||||
all.delete(this);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getUid() {
|
||||
if (this.saved.length === 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
let uids = new Set(this.saved.map(p => p.uid));
|
||||
|
||||
// Find first available number
|
||||
for (let i = 1; i <= this.saved.length + 1; i++) {
|
||||
if (!uids.has(i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get baseUrl() {
|
||||
return `/docs/${this.url}/`;
|
||||
}
|
||||
|
||||
getURL(entity) {
|
||||
return this.getParentURL(entity) + entity.search;
|
||||
}
|
||||
|
||||
getParentURL(entity) {
|
||||
return this.baseUrl + entity.id + '/';
|
||||
}
|
||||
|
||||
getObject(entity) {
|
||||
let ret = Object.create(this.entityPrototype, Object.getOwnPropertyDescriptors(entity));
|
||||
// debugger;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an entity, either by updating its existing entry or creating a new one
|
||||
* @param {object} entity
|
||||
*/
|
||||
save(entity) {
|
||||
if (!entity.uid) {
|
||||
// First time saving
|
||||
entity.uid = this.getUid();
|
||||
}
|
||||
|
||||
let savedPalettes = this.saved;
|
||||
let existingIndex = entity.uid ? this.saved.findIndex(p => p.uid === entity.uid) : -1;
|
||||
let newIndex = existingIndex > -1 ? existingIndex : savedPalettes.length;
|
||||
|
||||
this.saved.splice(newIndex, 1, entity);
|
||||
|
||||
this.saved.toLocalStorage();
|
||||
|
||||
this.dispatchEvent(new CustomEvent('save', { detail: this.getObject(entity) }));
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
delete(entity) {
|
||||
let count = this.saved.length;
|
||||
|
||||
if (count === 0 || !entity?.uid) {
|
||||
// No stored entities or this entity has not been saved
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO improve UX of this
|
||||
if (!confirm(`Are you sure you want to delete ${this.type} “${entity.title}”?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let index; (index = this.saved.findIndex(p => p.uid === entity.uid)) > -1; ) {
|
||||
this.saved.splice(index, 1);
|
||||
}
|
||||
|
||||
if (this.saved.length === count) {
|
||||
// Nothing was removed
|
||||
return;
|
||||
}
|
||||
|
||||
this.saved.toLocalStorage();
|
||||
|
||||
this.dispatchEvent(new CustomEvent('delete', { detail: this.getObject(entity) }));
|
||||
}
|
||||
|
||||
dispatchEvent(event) {
|
||||
super.dispatchEvent(event);
|
||||
my.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
my.palettes = new SavedEntities({
|
||||
key: 'savedPalettes',
|
||||
type: 'palette',
|
||||
});
|
||||
@@ -1,254 +1,119 @@
|
||||
const sidebar = (globalThis.sidebar = {});
|
||||
import my from '/assets/scripts/my.js';
|
||||
|
||||
sidebar.palettes = {
|
||||
render() {
|
||||
if (this.saved.length === 0) {
|
||||
return;
|
||||
}
|
||||
const sidebar = {
|
||||
addChild(a, parentA) {
|
||||
let parentLi = parentA.closest('li');
|
||||
let ul = parentLi.querySelector(':scope > ul');
|
||||
ul ??= parentLi.appendChild(document.createElement('ul'));
|
||||
let li = document.createElement('li');
|
||||
li.append(a);
|
||||
ul.appendChild(li);
|
||||
|
||||
for (let palette of this.saved) {
|
||||
sidebar.palette.render(palette);
|
||||
}
|
||||
|
||||
sidebar.updateCurrent();
|
||||
},
|
||||
|
||||
updateSaved() {
|
||||
this.saved = localStorage.savedPalettes ? JSON.parse(localStorage.savedPalettes) : [];
|
||||
},
|
||||
|
||||
save(saved = this.saved) {
|
||||
this.saved = saved ?? [];
|
||||
|
||||
if (saved.length > 0) {
|
||||
localStorage.savedPalettes = JSON.stringify(saved);
|
||||
} else {
|
||||
delete localStorage.savedPalettes;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
sidebar.palettes.updateSaved();
|
||||
addEventListener('storage', event => sidebar.palettes.updateSaved());
|
||||
|
||||
sidebar.palette = {
|
||||
getUid() {
|
||||
let savedPalettes = sidebar.palettes.saved;
|
||||
let uids = new Set(savedPalettes.map(p => p.uid));
|
||||
|
||||
if (savedPalettes.length === 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Find first available number
|
||||
for (let i = 1; i <= savedPalettes.length + 1; i++) {
|
||||
if (!uids.has(i)) {
|
||||
return i;
|
||||
// If we are on the same page, update the current link
|
||||
let url = location.href.replace(/#.+$/, '');
|
||||
if (url.startsWith(a.href)) {
|
||||
// Remove existing current
|
||||
for (let current of document.querySelectorAll('#sidebar a.current')) {
|
||||
current.classList.remove('current');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
equals(p1, p2) {
|
||||
if (!p1 || !p2) {
|
||||
return false;
|
||||
a.classList.add('current');
|
||||
}
|
||||
|
||||
return p1.id === p2.id && p1.uid === p2.uid;
|
||||
return a;
|
||||
},
|
||||
|
||||
delete(palette) {
|
||||
let savedPalettes = sidebar.palettes.saved;
|
||||
let count = savedPalettes.length;
|
||||
if (count === 0) {
|
||||
removeLink(a) {
|
||||
if (!a || !a.isConnected) {
|
||||
// Link doesn't exist or is already removed
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO improve UX of this
|
||||
if (!confirm(`Are you sure you want to delete palette “${palette.title}”?`)) {
|
||||
return;
|
||||
let li = a?.closest('li');
|
||||
let ul = li?.closest('ul');
|
||||
let parentA = ul?.closest('li')?.querySelector(':scope > a');
|
||||
|
||||
li?.remove();
|
||||
if (ul?.children.length === 0) {
|
||||
ul.remove();
|
||||
}
|
||||
|
||||
savedPalettes = savedPalettes.filter(p => !sidebar.palette.equals(palette, p));
|
||||
|
||||
if (savedPalettes.length === count) {
|
||||
// Nothing was removed
|
||||
return;
|
||||
}
|
||||
|
||||
// Update UI
|
||||
let pathname = `/docs/palettes/${palette.id}/`;
|
||||
let url = pathname + palette.search;
|
||||
let uls = new Set();
|
||||
|
||||
for (let a of document.querySelectorAll(`#sidebar a[href="${url}"]`)) {
|
||||
let li = a.closest('li');
|
||||
let ul = li.closest('ul');
|
||||
uls.add(ul);
|
||||
li.remove();
|
||||
}
|
||||
|
||||
// Remove empty lists
|
||||
for (let ul of uls) {
|
||||
if (!ul.children.length) {
|
||||
ul.remove();
|
||||
}
|
||||
}
|
||||
|
||||
sidebar.updateCurrent();
|
||||
|
||||
sidebar.palettes.save(savedPalettes);
|
||||
|
||||
if (sidebar.palette.equals(globalThis.paletteApp?.saved, palette)) {
|
||||
paletteApp.postDelete();
|
||||
if (a.classList.contains('current')) {
|
||||
// If the deleted palette was the current one, the current one is now the parent
|
||||
parentA.classList.add('current');
|
||||
}
|
||||
},
|
||||
|
||||
getSaved(palette, savedPalettes = sidebar.palettes.saved) {
|
||||
return savedPalettes.find(p => sidebar.palette.equals(p, palette));
|
||||
findEntity(entity) {
|
||||
return document.querySelector(`#sidebar a[href^="${entity.baseUrl}"][data-uid="${entity.uid}"]`);
|
||||
},
|
||||
|
||||
render(palette) {
|
||||
// Find existing <a>
|
||||
let { title, id, search, uid } = palette;
|
||||
renderEntity(entity) {
|
||||
let { url, parentUrl } = entity;
|
||||
|
||||
for (let a of document.querySelectorAll(`#sidebar a[href^="/docs/palettes/${id}/"][data-uid="${uid}"]`)) {
|
||||
// Palette already in sidebar, just update it
|
||||
a.textContent = palette.title;
|
||||
a.href = `/docs/palettes/${id}/${search}`;
|
||||
return;
|
||||
}
|
||||
|
||||
let pathname = `/docs/palettes/${id}/`;
|
||||
let url = pathname + search;
|
||||
let parentA = document.querySelector(`a[href="${pathname}"]`);
|
||||
// Find parent
|
||||
let parentA = document.querySelector(`#sidebar a[href="${parentUrl}"]`);
|
||||
let parentLi = parentA?.closest('li');
|
||||
let a;
|
||||
|
||||
if (parentLi) {
|
||||
a = Object.assign(document.createElement('a'), { href: url, textContent: title });
|
||||
a.dataset.uid = uid;
|
||||
let badges = [...parentLi.querySelectorAll('wa-badge')].map(badge => badge.cloneNode(true));
|
||||
let ul = parentLi.querySelector('ul') ?? parentLi.appendChild(document.createElement('ul'));
|
||||
let li = document.createElement('li');
|
||||
let deleteButton = Object.assign(document.createElement('wa-icon-button'), {
|
||||
name: 'trash',
|
||||
label: 'Delete',
|
||||
className: 'delete',
|
||||
});
|
||||
|
||||
deleteButton.addEventListener('click', () => {
|
||||
let palette = { id, uid, title: a.textContent, search: a.search };
|
||||
sidebar.palette.delete(palette);
|
||||
});
|
||||
|
||||
li.append(a, ' ', ...badges, deleteButton);
|
||||
ul.appendChild(li);
|
||||
}
|
||||
},
|
||||
|
||||
save(palette, saved) {
|
||||
let savedPalettes = sidebar.palettes.saved;
|
||||
let existing = this.getSaved(saved ?? palette, savedPalettes);
|
||||
let oldValues;
|
||||
|
||||
if (existing) {
|
||||
// Rename
|
||||
oldValues = { ...existing };
|
||||
Object.assign(existing, palette);
|
||||
} else {
|
||||
savedPalettes.push(palette);
|
||||
if (!parentLi) {
|
||||
throw new Error(`Cannot find parent url ${parentUrl}`);
|
||||
}
|
||||
|
||||
this.render(palette, oldValues);
|
||||
sidebar.updateCurrent();
|
||||
// Find existing
|
||||
let a = this.findEntity(entity);
|
||||
let alreadyExisted = !!a;
|
||||
|
||||
sidebar.palettes.save(savedPalettes);
|
||||
},
|
||||
};
|
||||
a ??= document.createElement('a');
|
||||
|
||||
sidebar.updateCurrent = function () {
|
||||
// Find the sidebar link with the longest shared prefix with the current URL
|
||||
let pathParts = location.pathname.split('/').filter(Boolean);
|
||||
let prefixes = [];
|
||||
a.textContent = entity.title;
|
||||
a.href = url;
|
||||
|
||||
if (pathParts.length === 1) {
|
||||
// If at /docs/ we just use that, otherwise we want at least two parts (/docs/xxx/)
|
||||
prefixes.push('/' + pathParts[0] + '/');
|
||||
} else {
|
||||
for (let i = 2; i <= pathParts.length; i++) {
|
||||
prefixes.push('/' + pathParts.slice(0, i).join('/') + '/');
|
||||
}
|
||||
}
|
||||
if (!alreadyExisted) {
|
||||
a.dataset.uid = entity.uid;
|
||||
|
||||
// Last prefix includes the search too (if any)
|
||||
if (location.search) {
|
||||
let params = new URLSearchParams(location.search);
|
||||
params.sort();
|
||||
prefixes.push(prefixes.at(-1) + location.search);
|
||||
}
|
||||
a = sidebar.addChild(a, parentA);
|
||||
|
||||
// We want to start from the longest prefix
|
||||
prefixes.reverse();
|
||||
let candidates;
|
||||
let matchingPrefix;
|
||||
// This is mainly to port Pro badges
|
||||
let badges = Array.from(parentLi.querySelectorAll('wa-badge'), badge => badge.cloneNode(true));
|
||||
let append = [...badges];
|
||||
|
||||
for (let prefix of prefixes) {
|
||||
candidates = document.querySelectorAll(`#sidebar a[href^="${prefix}"]`);
|
||||
|
||||
if (candidates.length > 0) {
|
||||
matchingPrefix = prefix;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchingPrefix) {
|
||||
// Abort mission
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchingPrefix === pathParts.at(-1)) {
|
||||
// Full path matches, check search
|
||||
if (location.search) {
|
||||
candidates = [...candidates];
|
||||
|
||||
let searchParams = new URLSearchParams(location.search);
|
||||
|
||||
if (searchParams.has('uid')) {
|
||||
// Only consider candidates with the same uid
|
||||
candidates = candidates.filter(a => {
|
||||
let params = new URLSearchParams(a.search);
|
||||
return params.get('uid') === searchParams.get('uid');
|
||||
});
|
||||
} else {
|
||||
// Sort candidates based on how many params they have in common, in descending order
|
||||
candidates = candidates.sort((a, b) => {
|
||||
return countSharedSearchParams(searchParams, b.search) - countSharedSearchParams(searchParams, a.search);
|
||||
if (entity.delete) {
|
||||
let deleteButton = Object.assign(document.createElement('wa-icon-button'), {
|
||||
name: 'trash',
|
||||
label: 'Delete',
|
||||
className: 'delete',
|
||||
});
|
||||
deleteButton.addEventListener('click', () => entity.delete());
|
||||
append.push(deleteButton);
|
||||
}
|
||||
|
||||
if (append.length > 0) {
|
||||
a.closest('li').append(' ', ...append);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
if (candidates.length > 0) {
|
||||
for (let current of document.querySelectorAll('#sidebar a.current')) {
|
||||
current.classList.remove('current');
|
||||
render() {
|
||||
for (let type in my) {
|
||||
let controller = my[type];
|
||||
|
||||
if (!controller.saved) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let entity of controller.saved) {
|
||||
let object = controller.getObject(entity);
|
||||
this.renderEntity(object);
|
||||
}
|
||||
}
|
||||
|
||||
candidates[0].classList.add('current');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
sidebar.render = function () {
|
||||
this.palettes.render();
|
||||
};
|
||||
globalThis.sidebar = sidebar;
|
||||
|
||||
// Update sidebar when my saved stuff changes
|
||||
my.addEventListener('delete', e => sidebar.removeLink(sidebar.findEntity(e.detail)));
|
||||
my.addEventListener('save', e => sidebar.renderEntity(e.detail));
|
||||
|
||||
sidebar.render();
|
||||
window.addEventListener('turbo:render', () => sidebar.render());
|
||||
|
||||
function countSharedSearchParams(searchParams, search) {
|
||||
if (!search || search === '?') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let params = new URLSearchParams(search);
|
||||
return [...searchParams.keys()].filter(k => params.get(k) === searchParams.get(k)).length;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
// Helper for view transitions
|
||||
export function domChange(fn, { behavior = 'smooth' } = {}) {
|
||||
const canUseViewTransitions =
|
||||
document.startViewTransition && !window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
if (canUseViewTransitions && behavior === 'smooth') {
|
||||
document.startViewTransition(fn);
|
||||
} else {
|
||||
fn(true);
|
||||
}
|
||||
}
|
||||
import { domChange } from './util/dom-change.js';
|
||||
export { domChange };
|
||||
|
||||
export function nextFrame() {
|
||||
return new Promise(resolve => requestAnimationFrame(resolve));
|
||||
@@ -100,6 +91,7 @@ const colorScheme = new ThemeAspect({
|
||||
domChange(() => {
|
||||
let dark = this.computedValue === 'dark';
|
||||
document.documentElement.classList.toggle(`wa-dark`, dark);
|
||||
document.documentElement.dispatchEvent(new CustomEvent('wa-color-scheme-change', { detail: { dark } }));
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -114,6 +106,6 @@ document.addEventListener('keydown', event => {
|
||||
!event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))
|
||||
) {
|
||||
event.preventDefault();
|
||||
colorScheme.set(theming.colorScheme.resolvedValue === 'dark' ? 'light' : 'dark');
|
||||
colorScheme.set(colorScheme.get() === 'dark' ? 'light' : 'dark');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import 'https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.10/+esm';
|
||||
import { preventTurboFouce } from '/dist/webawesome.js';
|
||||
|
||||
if (!window.___turboScrollPositions___) {
|
||||
window.___turboScrollPositions___ = {};
|
||||
}
|
||||
@@ -70,3 +73,4 @@ function fixDSD(e) {
|
||||
window.addEventListener('turbo:before-cache', saveScrollPosition);
|
||||
window.addEventListener('turbo:before-render', restoreScrollPosition);
|
||||
window.addEventListener('turbo:render', restoreScrollPosition);
|
||||
preventTurboFouce();
|
||||
|
||||
@@ -13,56 +13,42 @@ export default class Permalink extends URLSearchParams {
|
||||
return Object.fromEntries(this.entries());
|
||||
}
|
||||
|
||||
#mappings = new WeakMap();
|
||||
|
||||
mapObject(obj, mapping = {}) {
|
||||
this.#mappings.set(obj, mapping);
|
||||
}
|
||||
|
||||
readFrom(obj) {
|
||||
let mapping = this.#mappings.get(obj) ?? {};
|
||||
let { keyFrom = IDENTITY, valueFrom = IDENTITY } = mapping;
|
||||
|
||||
for (let key in obj) {
|
||||
let value = obj[key];
|
||||
let mappedValue = valueFrom(value);
|
||||
let mappedKey = keyFrom(key);
|
||||
this.set(mappedKey, mappedValue);
|
||||
}
|
||||
}
|
||||
|
||||
writeTo(obj) {
|
||||
let mapping = this.#mappings.get(obj) ?? {};
|
||||
let { keyTo = IDENTITY, valueTo = IDENTITY, canExtend = false } = mapping;
|
||||
|
||||
for (let [key, value] of this) {
|
||||
let mappedKey = keyTo(key);
|
||||
let mappedValue = valueTo(value);
|
||||
|
||||
if (canExtend || mappedKey in obj) {
|
||||
obj[mappedKey] = mappedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set(key, value, defaultValue) {
|
||||
let oldValue = this.get(key);
|
||||
if (equals(value, defaultValue) || equals(value, '')) {
|
||||
value = null;
|
||||
}
|
||||
|
||||
if (!value || value == defaultValue) {
|
||||
value ??= null; // undefined -> null
|
||||
|
||||
let oldValue = Array.isArray(value) ? this.getAll(key) : this.get(key);
|
||||
let changed = !equals(value, oldValue);
|
||||
|
||||
if (!changed) {
|
||||
// Nothing to do here
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
super.delete(key);
|
||||
value = value.slice();
|
||||
|
||||
if (oldValue) {
|
||||
this.changed = true;
|
||||
for (let v of value) {
|
||||
if (v || v === 0) {
|
||||
if (typeof v === 'object') {
|
||||
super.append(key, JSON.stringify(v));
|
||||
} else {
|
||||
super.append(key, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (value === null) {
|
||||
super.delete(key);
|
||||
} else {
|
||||
super.set(key, value);
|
||||
|
||||
if (String(value) !== String(oldValue)) {
|
||||
this.changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.sort();
|
||||
this.changed ||= changed;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,3 +65,40 @@ export default class Permalink extends URLSearchParams {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function equals(value, oldValue) {
|
||||
if (Array.isArray(value) || Array.isArray(oldValue)) {
|
||||
value = toArray(value);
|
||||
oldValue = toArray(oldValue);
|
||||
|
||||
if (value.length !== oldValue.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return value.every((v, i) => equals(v, oldValue[i]));
|
||||
}
|
||||
|
||||
// (value ?? oldValue ?? true) returns true if they're both empty (null or undefined)
|
||||
[value, oldValue] = [value, oldValue].map(v => (!v && v !== false && v !== 0 ? null : v));
|
||||
return value === oldValue || String(value) === String(oldValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a value to an array. `undefined` and `null` values are converted to an empty array.
|
||||
* @param {*} value - The value to convert.
|
||||
* @returns {any[]} The converted array.
|
||||
*/
|
||||
function toArray(value) {
|
||||
value ??= [];
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// Don't convert "foo" into ["f", "o", "o"]
|
||||
if (typeof value !== 'string' && typeof value[Symbol.iterator] === 'function') {
|
||||
return Array.from(value);
|
||||
}
|
||||
|
||||
return [value];
|
||||
}
|
||||
|
||||
37
docs/assets/scripts/util/dom-change.js
Normal file
37
docs/assets/scripts/util/dom-change.js
Normal file
@@ -0,0 +1,37 @@
|
||||
let initialPageLoadComplete = document.readyState === 'complete';
|
||||
|
||||
if (!initialPageLoadComplete) {
|
||||
window.addEventListener('load', () => {
|
||||
initialPageLoadComplete = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for performing a DOM change using a view transition, wherever supported and reduced motion is not desired.
|
||||
* @param {function} fn - Function to perform the DOM change. If async, must resolve when the change is complete.
|
||||
* @param {object} [options] - Options for the transition
|
||||
* @param {'smooth' | 'instant'} [options.behavior] - Transition behavior. Defaults to 'smooth'. 'instant' will skip the transition.
|
||||
* @param {boolean} [options.ignoreInitialLoad] - If true, will skip the transition on initial page load. Defaults to true.
|
||||
*/
|
||||
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') {
|
||||
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(false);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
22
docs/assets/scripts/vue/directives/content.js
Normal file
22
docs/assets/scripts/vue/directives/content.js
Normal file
@@ -0,0 +1,22 @@
|
||||
// Like v-text, but doesn't complain if the element has content,
|
||||
// making it possible to use in a PE fashion, with the contents being the fallback
|
||||
export default function content(el, { value, arg }) {
|
||||
if (!el.dataset.fallback) {
|
||||
// Store the original content as a fallback the first time
|
||||
el.dataset.fallback = el.textContent;
|
||||
}
|
||||
|
||||
if (value === '') {
|
||||
value = el.dataset.fallback;
|
||||
} else {
|
||||
if (arg === 'number') {
|
||||
value = Number(value).toLocaleString(undefined, { maximumSignificantDigits: 2 });
|
||||
}
|
||||
}
|
||||
|
||||
if (arg === 'html') {
|
||||
el.innerHTML = value;
|
||||
} else {
|
||||
el.textContent = value;
|
||||
}
|
||||
}
|
||||
110
docs/assets/scripts/vue/mixins/saved.js
Normal file
110
docs/assets/scripts/vue/mixins/saved.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import my from '/assets/scripts/my.js';
|
||||
import Permalink from '/assets/scripts/tweak/permalink.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
uid: undefined,
|
||||
saved: null,
|
||||
unsavedChanges: false,
|
||||
permalink: new Permalink(),
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.permalink.has('uid')) {
|
||||
this.uid = Number(this.permalink.get('uid'));
|
||||
this.saved = this.controller.saved.find(p => p.uid === this.uid);
|
||||
}
|
||||
|
||||
this.controller.addEventListener('delete', ({ detail: entity }) => {
|
||||
if (entity.uid === this.saved?.uid) {
|
||||
this.postDelete();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$nextTick().then(() => {
|
||||
if (!location.search || this.saved) {
|
||||
this.unsavedChanges = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
computed: {
|
||||
controller() {
|
||||
return my[this.collection];
|
||||
},
|
||||
|
||||
title() {
|
||||
if (this.saved) {
|
||||
return this.saved.title;
|
||||
} else if (this.unsavedChanges) {
|
||||
return this.defaultTitle;
|
||||
} else {
|
||||
return this.originalTitle;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
saved: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.unsavedChanges = !this.saved;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
async save({ title } = {}) {
|
||||
let uid = this.uid;
|
||||
|
||||
this.saved ??= { uid: this.uid };
|
||||
this.saved.id = this.id;
|
||||
|
||||
if (title) {
|
||||
// Renaming
|
||||
this.saved.title = title;
|
||||
} else {
|
||||
this.saved.title ??= this.defaultTitle;
|
||||
}
|
||||
|
||||
this.saved.search = location.search;
|
||||
|
||||
this.saved = this.controller.save(this.saved);
|
||||
|
||||
if (uid !== this.saved.uid) {
|
||||
// UID changed (most likely from saving a new entity)
|
||||
this.uid = this.saved.uid;
|
||||
this.permalink.set('uid', this.uid);
|
||||
this.permalink.updateLocation();
|
||||
await this.$nextTick();
|
||||
this.save(); // Save again to update the search param to include the UID
|
||||
}
|
||||
|
||||
this.unsavedChanges = false;
|
||||
},
|
||||
|
||||
rename() {
|
||||
let newTitle = prompt('Title:', this.saved?.title ?? this.defaultTitle);
|
||||
|
||||
if (newTitle && newTitle !== this.saved?.title) {
|
||||
this.save({ title: newTitle });
|
||||
}
|
||||
},
|
||||
|
||||
// Cannot name this delete() because Vue complains
|
||||
deleteSaved() {
|
||||
this.controller.delete(this.saved);
|
||||
},
|
||||
|
||||
postDelete() {
|
||||
this.saved = null;
|
||||
this.permalink.delete('uid');
|
||||
this.uid = undefined;
|
||||
this.permalink.updateLocation();
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,87 +0,0 @@
|
||||
.code-example {
|
||||
border: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet);
|
||||
border-radius: var(--wa-border-radius-l);
|
||||
color: var(--wa-color-text-normal);
|
||||
margin-block-end: var(--wa-flow-spacing);
|
||||
}
|
||||
|
||||
.code-example-preview {
|
||||
padding: 2rem;
|
||||
border-bottom: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet);
|
||||
|
||||
> :first-child {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
margin-block-end: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.code-example-source {
|
||||
border-bottom: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet);
|
||||
}
|
||||
|
||||
.code-example:not(.open) .code-example-source {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.code-example.open .code-example-toggle wa-icon {
|
||||
rotate: 180deg;
|
||||
}
|
||||
|
||||
.code-example-source pre {
|
||||
position: relative;
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.code-example-source:not(:has(+ .code-example-buttons)) {
|
||||
border-bottom: none;
|
||||
|
||||
pre {
|
||||
border-bottom-right-radius: var(--wa-border-radius-l);
|
||||
border-bottom-left-radius: var(--wa-border-radius-l);
|
||||
}
|
||||
}
|
||||
|
||||
.code-example-buttons {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
||||
button {
|
||||
all: unset;
|
||||
flex: 1 0 auto;
|
||||
font-size: 0.875rem;
|
||||
color: var(--wa-color-text-quiet);
|
||||
border-left: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet);
|
||||
text-align: center;
|
||||
padding: 0.5rem;
|
||||
cursor: pointer;
|
||||
|
||||
&:first-of-type {
|
||||
border-left: none;
|
||||
border-bottom-left-radius: var(--wa-border-radius-l);
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom-right-radius: var(--wa-border-radius-l);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: var(--wa-focus-ring);
|
||||
}
|
||||
}
|
||||
|
||||
.code-example-pen {
|
||||
flex: 0 0 100px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
wa-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -2px;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
@import 'code-examples.css';
|
||||
@import 'code-highlighter.css';
|
||||
@import 'copy-code.css';
|
||||
@import 'outline.css';
|
||||
@import 'search.css';
|
||||
@import 'cera_typeface.css';
|
||||
@import 'theme-icons.css';
|
||||
|
||||
:root {
|
||||
--wa-brand-orange: #f36944;
|
||||
@@ -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;
|
||||
@@ -400,7 +412,6 @@ wa-page > main:has(> .index-grid) {
|
||||
|
||||
&::part(header) {
|
||||
background-color: var(--header-background, var(--wa-color-neutral-fill-quiet));
|
||||
border-bottom: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -596,13 +607,6 @@ table.colors {
|
||||
margin-block-end: var(--wa-flow-spacing);
|
||||
}
|
||||
|
||||
/** mobile */
|
||||
@media screen and (max-width: 768px) {
|
||||
wa-page .only-desktop {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/** desktop */
|
||||
@media screen and not (max-width: 768px) {
|
||||
/* Navigation sidebar */
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
|
||||
&::part(base) {
|
||||
margin-block: 10rem;
|
||||
&::part(dialog) {
|
||||
margin-block-start: 10vh;
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
&::part(body) {
|
||||
@@ -23,20 +24,20 @@
|
||||
@media screen and (max-width: 900px) {
|
||||
max-width: calc(100% - 2rem);
|
||||
|
||||
&::part(base) {
|
||||
&::part(dialog) {
|
||||
margin-block: 1rem;
|
||||
}
|
||||
|
||||
#site-search-container {
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#site-search-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: calc(100vh - 20rem);
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
max-height: calc(100dvh - 2rem);
|
||||
}
|
||||
max-height: calc(100vh - 18rem);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
|
||||
@@ -1,3 +1,61 @@
|
||||
wa-card:has(> .theme-icon-host, > [slot='header'] > .theme-icon-host) {
|
||||
&::part(header) {
|
||||
/* We want to add a background color, so any spacing needs to go on .theme-icon */
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
min-block-size: 0;
|
||||
}
|
||||
|
||||
[slot='header'] {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-icon-host,
|
||||
.palette-icon-host {
|
||||
flex: 1;
|
||||
border-radius: inherit;
|
||||
|
||||
&[slot='header'],
|
||||
[slot='header']:has(&) {
|
||||
flex: 1;
|
||||
border-radius: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.palette-icon {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--hues, 9), 1fr);
|
||||
gap: var(--wa-space-3xs);
|
||||
min-width: 20ch;
|
||||
min-height: 9ch;
|
||||
align-content: center;
|
||||
|
||||
.swatch {
|
||||
height: 0.7em;
|
||||
background: var(--color);
|
||||
border-radius: var(--wa-border-radius-s);
|
||||
|
||||
&[data-suffix=''] {
|
||||
height: 1.1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme-icon {
|
||||
min-width: 18ch;
|
||||
padding: var(--wa-space-xs) var(--wa-space-m);
|
||||
border-radius: inherit;
|
||||
box-sizing: border-box;
|
||||
|
||||
h2,
|
||||
h3,
|
||||
p {
|
||||
margin-block: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-color-icon {
|
||||
display: grid;
|
||||
gap: var(--wa-space-xs);
|
||||
@@ -25,10 +83,50 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--wa-space-xs);
|
||||
}
|
||||
|
||||
h3,
|
||||
p {
|
||||
margin-block: 0;
|
||||
padding: 0;
|
||||
.theme-overall-icon {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: var(--wa-space-xs);
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 7.5rem;
|
||||
box-sizing: border-box;
|
||||
background: var(--wa-color-surface-lowered);
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
gap: var(--wa-space-xs);
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.row-2 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
contain: inline-size;
|
||||
width: 100%;
|
||||
|
||||
wa-input {
|
||||
min-width: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.swatches {
|
||||
display: flex;
|
||||
gap: var(--wa-space-3xs);
|
||||
|
||||
> div {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
border-radius: var(--wa-border-radius-s);
|
||||
background: var(--wa-color-fill-loud);
|
||||
color: var(--wa-color-on-loud);
|
||||
|
||||
&.wa-brand {
|
||||
width: 2.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ Other elements can also be placed inside button groups:
|
||||
<wa-button-group label="Example Button Group">
|
||||
<wa-button>Button</wa-button>
|
||||
<button>Native Button</button>
|
||||
<wa-dropdown hoist>
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
@@ -185,7 +185,7 @@ Create a split button using a button and a dropdown. Use a [visually hidden](/do
|
||||
```html {.example}
|
||||
<wa-button-group label="Example Button Group">
|
||||
<wa-button variant="brand">Save</wa-button>
|
||||
<wa-dropdown placement="bottom-end" hoist>
|
||||
<wa-dropdown placement="bottom-end">
|
||||
<wa-button slot="trigger" variant="brand" caret>
|
||||
<span class="wa-visually-hidden">More options</span>
|
||||
</wa-button>
|
||||
|
||||
@@ -15,27 +15,17 @@ 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></wa-rating>
|
||||
<wa-rating label="Rating"></wa-rating>
|
||||
</div>
|
||||
</wa-card>
|
||||
|
||||
<style>
|
||||
.card-overview {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.card-overview small {
|
||||
color: var(--wa-color-text-quiet);
|
||||
}
|
||||
|
||||
.card-overview [slot='footer'] {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 300px;
|
||||
}
|
||||
</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>
|
||||
```
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Code Demo
|
||||
description: Code demos can be used to render code examples as inline live demos.
|
||||
tags: component
|
||||
noAlpha: true
|
||||
isPro: true
|
||||
---
|
||||
|
||||
```html {.example}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
---
|
||||
title: Image Comparer
|
||||
description: Compare visual differences between similar photos with a sliding panel.
|
||||
title: Comparer
|
||||
description: Compare visual differences between similar content with a sliding panel.
|
||||
tags: [imagery, niche]
|
||||
icon: image-comparer
|
||||
icon: comparer
|
||||
---
|
||||
|
||||
For best results, use images that share the same dimensions. The slider can be controlled by dragging or pressing the left and right arrow keys. (Tip: press shift + arrows to move the slider in larger intervals, or home + end to jump to the beginning or end.)
|
||||
This is especially useful for comparing images, but can be used for comparing any type of content (for an example of using it to compare entire UIs, check out our [theme pages](/docs/themes/default/)).
|
||||
For best results, use content that shares the same dimensions.
|
||||
The slider can be controlled by dragging or pressing the left and right arrow keys. (Tip: press shift + arrows to move the slider in larger intervals, or home + end to jump to the beginning or end.)
|
||||
|
||||
```html {.example}
|
||||
<wa-image-comparer>
|
||||
<wa-comparer>
|
||||
<img
|
||||
slot="before"
|
||||
src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80&sat=-100&bri=-5"
|
||||
@@ -19,7 +21,7 @@ For best results, use images that share the same dimensions. The slider can be c
|
||||
src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80"
|
||||
alt="Color version of kittens in a basket looking around."
|
||||
/>
|
||||
</wa-image-comparer>
|
||||
</wa-comparer>
|
||||
```
|
||||
|
||||
## Examples
|
||||
@@ -29,7 +31,7 @@ For best results, use images that share the same dimensions. The slider can be c
|
||||
Use the `position` attribute to set the initial position of the slider. This is a percentage from `0` to `100`.
|
||||
|
||||
```html {.example}
|
||||
<wa-image-comparer position="25">
|
||||
<wa-comparer position="25">
|
||||
<img
|
||||
slot="before"
|
||||
src="https://images.unsplash.com/photo-1520903074185-8eca362b3dce?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1200&q=80"
|
||||
@@ -40,5 +42,5 @@ Use the `position` attribute to set the initial position of the slider. This is
|
||||
src="https://images.unsplash.com/photo-1520640023173-50a135e35804?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80"
|
||||
alt="A person sitting on a yellow curb tying shoelaces on a boot."
|
||||
/>
|
||||
</wa-image-comparer>
|
||||
</wa-comparer>
|
||||
```
|
||||
@@ -77,6 +77,31 @@ The details component automatically adapts to right-to-left languages:
|
||||
</wa-details>
|
||||
```
|
||||
|
||||
### Appearance
|
||||
|
||||
Use the `appearance` attribute to change the element’s 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.
|
||||
|
||||
@@ -88,10 +88,10 @@ You can add the special `data-dialog="close"` attribute to a button inside the d
|
||||
|
||||
### Custom Width
|
||||
|
||||
Just use the CSS `width` property to set the dialog's width.
|
||||
Just use the `--width` custom property to set the dialog's width.
|
||||
|
||||
```html {.example}
|
||||
<wa-dialog label="Dialog" with-header with-footer class="dialog-width" style="width: 50vw;">
|
||||
<wa-dialog label="Dialog" with-header with-footer class="dialog-width" style="--width: 50vw;">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
<wa-button slot="footer" variant="brand" data-dialog="close">Close</wa-button>
|
||||
</wa-dialog>
|
||||
|
||||
@@ -180,38 +180,3 @@ To create a submenu, nest an `<wa-menu slot="submenu">` element in a [menu item]
|
||||
:::warning
|
||||
As a UX best practice, avoid using more than one level of submenu when possible.
|
||||
:::
|
||||
|
||||
### Hoisting
|
||||
|
||||
Dropdown panels will be clipped if they're inside a container that has `overflow: auto|hidden`. The `hoist` attribute forces the panel to use a fixed positioning strategy, allowing it to break out of the container. In this case, the panel will be positioned relative to its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.
|
||||
|
||||
```html {.example}
|
||||
<div class="dropdown-hoist">
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>No Hoist</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
<wa-menu-item>Item 2</wa-menu-item>
|
||||
<wa-menu-item>Item 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
|
||||
<wa-dropdown hoist>
|
||||
<wa-button slot="trigger" caret>Hoist</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
<wa-menu-item>Item 2</wa-menu-item>
|
||||
<wa-menu-item>Item 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.dropdown-hoist {
|
||||
position: relative;
|
||||
border: solid 2px var(--wa-color-surface-border);
|
||||
padding: var(--wa-space-m);
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
@@ -17,11 +17,94 @@ Not sure which icon to use? [Find the perfect icon over at Font Awesome!](https:
|
||||
|
||||
The default icon library is Font Awesome Free, which comes with two icon families: `classic` and `brands`. Use the `family` attribute to set the icon family.
|
||||
|
||||
Many Font Awesome Pro icon families have variants such as `thin`, `light`, `regular`, and `solid`. Font Awesome Pro users can [provide their kit code](/docs/installation/#using-font-awesome-kit-codes) to unlock additional families, including `sharp` and `duotone`. For these icon families, use the `variant` attribute to set the variant.
|
||||
Many Font Awesome Pro icon families have variants such as `thin`, `light`, `regular`, and `solid`. Font Awesome Pro users can [provide their kit code](/docs/#using-font-awesome-kit-codes) to unlock additional families, including `sharp`, `duotone`, and `sharp-duotone`. For these icon families, use the `variant` attribute to set the variant.
|
||||
|
||||
```html {.example}
|
||||
<wa-icon family="brands" name="font-awesome"></wa-icon>
|
||||
<wa-icon family="brands" name="web-awesome"></wa-icon>
|
||||
<wa-icon family="brands" name="font-awesome"></wa-icon>
|
||||
<wa-icon family="brands" name="web-awesome"></wa-icon>
|
||||
<wa-icon family="classic" variant="light" name="sparkles"></wa-icon>
|
||||
<wa-icon family="sharp" variant="solid" name="fire"></wa-icon>
|
||||
<wa-icon family="duotone" variant="regular" name="cake-slice"></wa-icon>
|
||||
```
|
||||
|
||||
### Setting defaults via CSS
|
||||
|
||||
You can use certain CSS custom properties to set icon defaults, not just on the icon itself, but any ancestor.
|
||||
This can be useful when you want certain parameters to vary based on context, e.g. icons inside callouts or all icons for a given theme.
|
||||
|
||||
:::warning
|
||||
These CSS properties are intended to set **defaults**, and thus only make a difference when the corresponding attributes are not set.
|
||||
In future versions of Web Awesome, we may change this behavior to allow CSS properties to override attributes if `!important` is used.
|
||||
:::
|
||||
|
||||
For example, here is how you can use CSS custom properties to set a default icon for each type of callout:
|
||||
|
||||
```html {.example}
|
||||
<wa-callout>
|
||||
<!-- Look ma, no attributes! -->
|
||||
<wa-icon slot="icon"></wa-icon>
|
||||
This is a normal callout.
|
||||
</wa-callout>
|
||||
|
||||
<wa-callout variant="danger">
|
||||
<wa-icon slot="icon" name="dumpster-fire" variant="solid"></wa-icon>
|
||||
This is a callout with an explicit icon, which overrides these defaults.
|
||||
</wa-callout>
|
||||
|
||||
<wa-callout variant="warning">
|
||||
<!-- Look ma, no attributes! -->
|
||||
<wa-icon slot="icon"></wa-icon>
|
||||
Here be dragons.
|
||||
</wa-callout>
|
||||
|
||||
<wa-callout variant="danger">
|
||||
<!-- Look ma, no attributes! -->
|
||||
<wa-icon slot="icon"></wa-icon>
|
||||
Here be more dragons.
|
||||
</wa-callout>
|
||||
|
||||
<wa-callout variant="success">
|
||||
<!-- Look ma, no attributes! -->
|
||||
<wa-icon slot="icon"></wa-icon>
|
||||
Success!
|
||||
</wa-callout>
|
||||
|
||||
<style>
|
||||
wa-callout {
|
||||
--wa-icon-variant: regular;
|
||||
--wa-icon-name: info-circle;
|
||||
|
||||
&[variant="warning"] {
|
||||
--wa-icon-name: triangle-exclamation;
|
||||
}
|
||||
|
||||
&[variant="danger"] {
|
||||
--wa-icon-name: circle-exclamation;
|
||||
}
|
||||
|
||||
&[variant="success"] {
|
||||
--wa-icon-name: circle-check;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
You can even set icons dynamically, as a response to user interaction or media queries.
|
||||
For example, here's how we can change the icon on hover:
|
||||
|
||||
```html {.example}
|
||||
<wa-button class="github" href="https://github.com/webawesome/webawesome"><wa-icon slot="prefix" fixed-width></wa-icon> GitHub Repo</wa-button>
|
||||
<style>
|
||||
.github {
|
||||
--wa-icon-name: github;
|
||||
--wa-icon-family: brands;
|
||||
|
||||
&:hover {
|
||||
--wa-icon-name: arrow-up-right-from-square;
|
||||
--wa-icon-family: classic;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### Colors
|
||||
@@ -103,7 +186,8 @@ Custom icons can be loaded individually with the `src` attribute. Only SVGs on a
|
||||
|
||||
You can register additional icons to use with the `<wa-icon>` component through icon libraries. Icon files can exist locally or on a CORS-enabled endpoint (e.g. a CDN). There is no limit to how many icon libraries you can register and there is no cost associated with registering them, as individual icons are only requested when they're used.
|
||||
|
||||
Web Awesome ships with two built-in icon libraries, `default` and `system`. The [default icon library](#customizing-the-default-library) is provided courtesy of [Font Awesome](https://fontawesome.com/). The [system icon library](#customizing-the-system-library) contains only a small subset of icons that are used internally by Web Awesome components.
|
||||
Web Awesome ships with one built-in icon library: `default`.
|
||||
The [default icon library](#customizing-the-default-library) is provided courtesy of [Font Awesome](https://fontawesome.com/).
|
||||
|
||||
To register an additional icon library, use the `registerIconLibrary()` function that's exported from `dist/webawesome.js`. At a minimum, you must provide a name and a resolver function. The resolver function translates an icon name to a URL where the corresponding SVG file exists. Refer to the examples below to better understand how it works.
|
||||
|
||||
@@ -115,8 +199,9 @@ Here's an example that registers an icon library located in the `/assets/icons`
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary('my-icons', {
|
||||
resolver: (name, family, variant) => `/assets/icons/${name}.svg`,
|
||||
registerIconLibrary({
|
||||
name: 'my-icons',
|
||||
getUrl: (name, family, variant) => `/assets/icons/${name}.svg`,
|
||||
mutator: svg => svg.setAttribute('fill', 'currentColor')
|
||||
});
|
||||
</script>
|
||||
@@ -131,6 +216,289 @@ To display an icon, set the `library` and `name` attributes of an `<wa-icon>` el
|
||||
|
||||
If an icon is used before registration occurs, it will be empty initially but shown when registered.
|
||||
|
||||
### Customizing the Default Icon Library
|
||||
|
||||
The default icon library contains over 2,000 icons courtesy of [Font Awesome](https://fontawesome.com/).
|
||||
These are the icons that display when you use `<wa-icon>` without the `library` attribute.
|
||||
If you prefer to have these icons resolve to a different icon library, simply register it using the `default` name:
|
||||
|
||||
For example, this will change the default icon library to use [Bootstrap Icons](https://icons.getbootstrap.com/) loaded from the jsDelivr CDN.
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary('default', {
|
||||
name: 'default',
|
||||
getUrl: (name, family) => {
|
||||
const suffix = family === 'filled' ? '-fill' : '';
|
||||
return `https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/icons/${name}${suffix}.svg`
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
You can also register an existing library as the default:
|
||||
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
let myIcons = registerIconLibrary({
|
||||
name: 'my-icons',
|
||||
getUrl: (name, family, variant) => `/assets/icons/${name}.svg`,
|
||||
mutator: svg => svg.setAttribute('fill', 'currentColor')
|
||||
});
|
||||
|
||||
// Alias of my-icons to default
|
||||
registerIconLibrary('default', myIcons);
|
||||
</script>
|
||||
```
|
||||
|
||||
This allows you to have multiple libraries that co-exist, only one of which is the default.
|
||||
|
||||
### Customizing system icons
|
||||
|
||||
Web Awesome components use a number of icons internally.
|
||||
For example, the checkmark icon in `<wa-checkbox>` or the chevron used in `<wa-details>`.
|
||||
These icons have a `system:` prefix in their name, e.g. `system:check` or `system:chevron-down`.
|
||||
|
||||
To specify how these map to your icon library, you can define a `system()` function that maps Web Awesome’s system icons to your library’s icons.
|
||||
The `system()` function receives the icon name, family and variant as arguments and returns an object with the new `name`, `family`, and `variant`.
|
||||
For example, here is how to define system icon mappings for the Bootstrap Icons library:
|
||||
|
||||
```js
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
let system = {
|
||||
filled: ['circle', 'pause', 'play', 'star', 'user'],
|
||||
nameMap: {
|
||||
'check': 'check-lg',
|
||||
'circle': 'circle',
|
||||
'circle-xmark': 'x-circle',
|
||||
'eye-dropper': 'eyedropper',
|
||||
'eye': 'eye',
|
||||
'eye-slash': 'eye-slash',
|
||||
'grip-vertical': 'grip-vertical',
|
||||
'indeterminate': 'dash-lg',
|
||||
'minus': 'dash-lg',
|
||||
'pause': 'pause',
|
||||
'play': 'play',
|
||||
'user': 'person',
|
||||
'xmark': 'x-lg',
|
||||
}
|
||||
};
|
||||
|
||||
registerIconLibrary({
|
||||
name: 'bootstrap-icons',
|
||||
system(name, family) {
|
||||
return {
|
||||
// Transform names where different
|
||||
name: system.nameMap[name] || name,
|
||||
|
||||
// Default to non-filled as many of system's "solid" icons are not filled in Bootstrap Icons
|
||||
family: system.filled.includes(name) ? 'filled' : ''
|
||||
};
|
||||
},
|
||||
getUrl: (name, family) => {
|
||||
const suffix = family === 'filled' ? '-fill' : '';
|
||||
return `https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/icons/${name}${suffix}.svg`
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
You can also specify a `library` key to resolve a system icon through a different library, e.g. WA’s default icon library.
|
||||
For example, the code above will try to resolve any system icons through your library and only fall back to WA's default library if they fail to load.
|
||||
This maximizes the odds that icons from your library are used, but can also be unpredictable.
|
||||
To resolve any system icon you have not vetted via the Web Awesome default library, you can use the `library` key:
|
||||
|
||||
```js
|
||||
let system = {
|
||||
filled: ['circle', 'pause', 'play', 'star', 'user'],
|
||||
nameMap: {
|
||||
'chevron-down': 'chevron-down',
|
||||
'chevron-left': 'chevron-left',
|
||||
'chevron-right': 'chevron-right',
|
||||
'check': 'check-lg',
|
||||
'circle': 'circle',
|
||||
'eye-dropper': 'eyedropper',
|
||||
'grip-vertical': 'grip-vertical',
|
||||
'indeterminate': 'dash-lg',
|
||||
'pause': 'pause',
|
||||
'play': 'play',
|
||||
'circle-xmark': 'x-circle',
|
||||
'grip-vertical': 'grip-vertical',
|
||||
'eye-slash': 'eye-slash',
|
||||
'eye': 'eye',
|
||||
'user': 'person',
|
||||
'xmark': 'x-lg',
|
||||
}
|
||||
};
|
||||
|
||||
registerIconLibrary({
|
||||
name: 'bootstrap-icons',
|
||||
system(name, family) {
|
||||
if (!system.nameMap[name]) {
|
||||
// If the icon is not known, use the default library
|
||||
return { library: 'wa', name, family, variant };
|
||||
}
|
||||
|
||||
return {
|
||||
name: system.nameMap[name],
|
||||
family: system.filled.includes(name) ? 'filled' : ''
|
||||
};
|
||||
},
|
||||
getUrl: (name, family) => {
|
||||
const suffix = family === 'filled' ? '-fill' : '';
|
||||
return `https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/icons/${name}${suffix}.svg`
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### Index of All System Icons { #system-icons-index }
|
||||
|
||||
These are all system icons used currently by Web Awesome components (`system:` prefix omitted for readability):
|
||||
|
||||
| Name | Variant | Icon |
|
||||
| --- | --- | --- |
|
||||
{%- for icon in systemIcons %}
|
||||
| `{{ icon.name }}` | `{{ icon.variant }}` | <wa-icon library="wa" name="system:{{ icon.name }}" family="classic" variant="{{ icon.variant }}"></wa-icon> |
|
||||
{%- endfor %}
|
||||
|
||||
### Inlined icons { #inlined }
|
||||
|
||||
Inlined icons are SVG icons that are loaded directly from code rather than from HTTP requests, making them load faster in your application.
|
||||
Inlining critical icons improves performance by eliminating HTTP requests, reducing load times, and ensuring they are immediately available.
|
||||
As an example, all of [Web Awesome’s default system icons](#system-icons-index) are inlined, and we strongly advise you inline the corresponding icons in your custom icon libraries as well.
|
||||
|
||||
#### How to Use Inlined Icons
|
||||
|
||||
The `inlined` property allows you to provide icon SVG markup directly in your code.
|
||||
This creates a mapping of `(name, family, variant)` to SVG markup.
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary({
|
||||
name: 'default',
|
||||
getUrl: name => `/path/to/custom/icons/${name}.svg`,
|
||||
inlined: {
|
||||
check: '<svg xmlns="http://www.w3.org/2000/svg">...</svg>',
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
The structure of the `inlined` property is as follows:
|
||||
- For simple libraries (no families or variants): `{ name: svg }`
|
||||
- For libraries with families (no variants): `{ family: { name: svg } }`
|
||||
- For complex libraries (with both family and variant): `{ family: { variant: { name: svg } } }`
|
||||
|
||||
#### Adding More Inlined Icons
|
||||
|
||||
To add additional icons to an existing library, use the `inline()` method:
|
||||
|
||||
```js
|
||||
import { getIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
let defaultLibrary = getIconLibrary('default');
|
||||
defaultLibrary.inline({
|
||||
classic: { // family
|
||||
regular: { // variant
|
||||
'circle-info': '<svg xmlns="http://www.w3.org/2000/svg">...</svg>',
|
||||
'triangle-exclamation': '<svg xmlns="http://www.w3.org/2000/svg">...</svg>'
|
||||
// ...
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### Best Practice
|
||||
|
||||
When using custom icon libraries with Web Awesome components, always inline system icons to ensure optimal performance.
|
||||
|
||||
### Customize icon markup, for SVG sprites and more
|
||||
|
||||
By default, icon markup is produced by fetching the URL returned by the `getUrl()` function.
|
||||
You can provide a `getMarkup()` function to customize this.
|
||||
|
||||
A common use case for that is SVG sprites, often used to improve performance by avoiding multiple trips for each SVG.
|
||||
The browser will load the sprite sheet once and then you reference the particular SVG within the sprite sheet using its id:
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary({
|
||||
name: 'sprite',
|
||||
getUrl: name => `/assets/images/sprite.svg#${name}`,
|
||||
getMarkup: url => `<svg fill="currentColor"><use href="${url}"></use></svg>`
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
As always, make sure to benchmark these changes. When using HTTP/2, it may in fact be more bandwidth-friendly to use multiple small requests instead of 1 large sprite sheet.
|
||||
|
||||
:::warning
|
||||
When using sprite sheets, the `wa-load` and `wa-error` events will not fire.
|
||||
|
||||
For security reasons, browsers may apply the same-origin policy on `<use>` elements located in the `<wa-icon>` shadow DOM and may refuse to load a cross-origin URL. There is currently no defined way to set a cross-origin policy for `<use>` elements. For this reason, sprite sheets should only be used if you're self-hosting them.
|
||||
:::
|
||||
|
||||
### Fallbacks
|
||||
|
||||
By default, if an icon fails to load, WA will retry using the WA default icon library, and if that also fails, a blank icon will be displayed.
|
||||
You can provide a `fallback()` function to customize this behavior.
|
||||
Some examples for common use cases follow.
|
||||
|
||||
If you never want icons outside your library to display:
|
||||
|
||||
```js
|
||||
registerIconLibrary({
|
||||
name: 'my-icons',
|
||||
getUrl: (name, family, variant) => `/assets/icons/${name}.svg`,
|
||||
fallback: (name, family, variant) => {
|
||||
// Don't show anything if an icon is not found
|
||||
return null;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
If you want to display a certain designated "missing icon" icon:
|
||||
|
||||
```js
|
||||
registerIconLibrary({
|
||||
name: 'my-icons',
|
||||
getUrl: (name, family, variant) => `/assets/icons/${name}.svg`,
|
||||
inlined: {
|
||||
// Make sure the missing icon never fails by inlining it:
|
||||
'missing-icon': '<svg xmlns="http://www.w3.org/2000/svg">...</svg>'
|
||||
}
|
||||
fallback: (name, family, variant) => {
|
||||
return {name: 'missing-icon'};
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
If you want to retry with the default family and variant:
|
||||
|
||||
```js
|
||||
registerIconLibrary({
|
||||
name: 'my-icons',
|
||||
getUrl: (name, family, variant) => `/assets/icons/${name}.svg`,
|
||||
fallback: (name, family, variant) => {
|
||||
if (family !== 'classic' || variant !== 'solid') {
|
||||
return { name, family: 'classic', variant: 'solid' };
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## Icon Library Examples { #icon-libraries }
|
||||
|
||||
The following examples demonstrate how to register a number of popular, open source icon libraries via CDN. Feel free to adapt the code as you see fit to use your own origin or naming conventions.
|
||||
|
||||
### Bootstrap Icons
|
||||
@@ -143,8 +511,42 @@ Icons in this library are licensed under the [MIT License](https://github.com/tw
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary('default', {
|
||||
resolver: (name, family) => {
|
||||
let system = {
|
||||
filled: ['circle', 'pause', 'play', 'star', 'user'],
|
||||
nameMap: {
|
||||
'chevron-down': 'chevron-down',
|
||||
'chevron-left': 'chevron-left',
|
||||
'chevron-right': 'chevron-right',
|
||||
'check': 'check-lg',
|
||||
'circle': 'circle',
|
||||
'eye-dropper': 'eyedropper',
|
||||
'grip-vertical': 'grip-vertical',
|
||||
'indeterminate': 'dash-lg',
|
||||
'pause': 'pause',
|
||||
'play': 'play',
|
||||
'circle-xmark': 'x-circle',
|
||||
'grip-vertical': 'grip-vertical',
|
||||
'eye-slash': 'eye-slash',
|
||||
'eye': 'eye',
|
||||
'user': 'person',
|
||||
'xmark': 'x-lg',
|
||||
}
|
||||
};
|
||||
|
||||
registerIconLibrary({
|
||||
name: 'default',
|
||||
system(name, family) {
|
||||
if (!system.nameMap[name]) {
|
||||
// If the icon is not known, use the default library
|
||||
return { library: 'wa', name: name, family, variant };
|
||||
}
|
||||
|
||||
return {
|
||||
name: system.nameMap[name],
|
||||
family: system.filled.includes(name) ? 'filled' : ''
|
||||
};
|
||||
},
|
||||
getUrl: (name, family) => {
|
||||
const suffix = family === 'filled' ? '-fill' : '';
|
||||
return `https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/icons/${name}${suffix}.svg`
|
||||
}
|
||||
@@ -162,8 +564,9 @@ Icons in this library are licensed under the [Creative Commons 4.0 License](http
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary('boxicons', {
|
||||
resolver: name => {
|
||||
registerIconLibrary({
|
||||
name: 'boxicons',
|
||||
getUrl: name => {
|
||||
let folder = 'regular';
|
||||
if (name.substring(0, 4) === 'bxs-') folder = 'solid';
|
||||
if (name.substring(0, 4) === 'bxl-') folder = 'logos';
|
||||
@@ -216,8 +619,9 @@ Icons in this library are licensed under the [MIT License](https://github.com/lu
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary('lucide', {
|
||||
resolver: name => `https://cdn.jsdelivr.net/npm/lucide-static@0.16.29/icons/${name}.svg`
|
||||
registerIconLibrary({
|
||||
name: 'lucide',
|
||||
getUrl: name => `https://cdn.jsdelivr.net/npm/lucide-static@0.16.29/icons/${name}.svg`
|
||||
});
|
||||
</script>
|
||||
```
|
||||
@@ -232,8 +636,9 @@ Icons in this library are licensed under the [MIT License](https://github.com/ta
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary('heroicons', {
|
||||
resolver: name => `https://cdn.jsdelivr.net/npm/heroicons@2.0.1/24/outline/${name}.svg`
|
||||
registerIconLibrary({
|
||||
name: 'heroicons',
|
||||
getUrl: name => `https://cdn.jsdelivr.net/npm/heroicons@2.0.1/24/outline/${name}.svg`
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -257,8 +662,9 @@ Icons in this library are licensed under the [MIT License](https://github.com/lu
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary('iconoir', {
|
||||
resolver: name => `https://cdn.jsdelivr.net/gh/lucaburgio/iconoir@latest/icons/${name}.svg`
|
||||
registerIconLibrary({
|
||||
name: 'iconoir',
|
||||
getUrl: name => `https://cdn.jsdelivr.net/gh/lucaburgio/iconoir@latest/icons/${name}.svg`
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -282,8 +688,9 @@ Icons in this library are licensed under the [MIT License](https://github.com/io
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary('ionicons', {
|
||||
resolver: name => `https://cdn.jsdelivr.net/npm/ionicons@5.1.2/dist/ionicons/svg/${name}.svg`,
|
||||
registerIconLibrary({
|
||||
name: 'ionicons',
|
||||
getUrl: name => `https://cdn.jsdelivr.net/npm/ionicons@5.1.2/dist/ionicons/svg/${name}.svg`,
|
||||
mutator: svg => {
|
||||
svg.setAttribute('fill', 'currentColor');
|
||||
svg.setAttribute('stroke', 'currentColor');
|
||||
@@ -327,8 +734,9 @@ Icons in this library are licensed under the [MIT License](https://github.com/mi
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary('jam', {
|
||||
resolver: name => `https://cdn.jsdelivr.net/npm/jam-icons@2.0.0/svg/${name}.svg`,
|
||||
registerIconLibrary({
|
||||
name: 'jam',
|
||||
getUrl: name => `https://cdn.jsdelivr.net/npm/jam-icons@2.0.0/svg/${name}.svg`,
|
||||
mutator: svg => svg.setAttribute('fill', 'currentColor')
|
||||
});
|
||||
</script>
|
||||
@@ -360,8 +768,9 @@ Icons in this library are licensed under the [Apache 2.0 License](https://github
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary('material', {
|
||||
resolver: name => {
|
||||
registerIconLibrary({
|
||||
name: 'material',
|
||||
getUrl: name => {
|
||||
const match = name.match(/^(.*?)(_(round|sharp))?$/);
|
||||
return `https://cdn.jsdelivr.net/npm/@material-icons/svg@1.0.5/svg/${match[1]}/${match[3] || 'outline'}.svg`;
|
||||
},
|
||||
@@ -403,8 +812,9 @@ Icons in this library are licensed under the [Apache 2.0 License](https://github
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary('remixicon', {
|
||||
resolver: name => {
|
||||
registerIconLibrary({
|
||||
name: 'remixicon',
|
||||
getUrl: name => {
|
||||
const match = name.match(/^(.*?)\/(.*?)?$/);
|
||||
match[1] = match[1].charAt(0).toUpperCase() + match[1].slice(1);
|
||||
return `https://cdn.jsdelivr.net/npm/remixicon@2.5.0/icons/${match[1]}/${match[2]}.svg`;
|
||||
@@ -440,8 +850,9 @@ Icons in this library are licensed under the [MIT License](https://github.com/ta
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary('tabler', {
|
||||
resolver: name => `https://cdn.jsdelivr.net/npm/@tabler/icons@1.68.0/icons/${name}.svg`,
|
||||
registerIconLibrary({
|
||||
name: 'tabler',
|
||||
getUrl: name => `https://cdn.jsdelivr.net/npm/@tabler/icons@1.68.0/icons/${name}.svg`,
|
||||
mutator: svg => {
|
||||
svg.style.fill = 'none';
|
||||
svg.setAttribute('stroke', 'currentColor');
|
||||
@@ -476,8 +887,9 @@ Icons in this library are licensed under the [Apache 2.0 License](https://github
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary('unicons', {
|
||||
resolver: name => {
|
||||
registerIconLibrary({
|
||||
name: 'unicons',
|
||||
getUrl: name => {
|
||||
const match = name.match(/^(.*?)(-s)?$/);
|
||||
return `https://cdn.jsdelivr.net/npm/@iconscout/unicons@3.0.3/svg/${match[2] === '-s' ? 'solid' : 'line'}/${
|
||||
match[1]
|
||||
@@ -504,61 +916,6 @@ Icons in this library are licensed under the [Apache 2.0 License](https://github
|
||||
</div>
|
||||
```
|
||||
|
||||
### Customizing the Default Library
|
||||
|
||||
The default icon library contains over 2,000 icons courtesy of [Font Awesome](https://fontawesome.com/). These are the icons that display when you use `<wa-icon>` without the `library` attribute. If you prefer to have these icons resolve elsewhere or to a different icon library, register an icon library using the `default` name and a custom resolver.
|
||||
|
||||
For example, this will change the default icon library to use [Bootstrap Icons](https://icons.getbootstrap.com/) loaded from the jsDelivr CDN.
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary('default', {
|
||||
resolver: (name, family) => {
|
||||
const suffix = family === 'filled' ? '-fill' : '';
|
||||
return `https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/icons/${name}${suffix}.svg`
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
#### Customize the default library to use SVG sprites
|
||||
|
||||
To improve performance you can use a SVG sprites to avoid multiple trips for each SVG. The browser will load the sprite sheet once and then you reference the particular SVG within the sprite sheet using hash selector.
|
||||
|
||||
As always, make sure to benchmark these changes. When using HTTP/2, it may in fact be more bandwidth-friendly to use multiple small requests instead of 1 large sprite sheet.
|
||||
|
||||
:::warning
|
||||
When using sprite sheets, the `wa-load` and `wa-error` events will not fire.
|
||||
|
||||
For security reasons, browsers may apply the same-origin policy on `<use>` elements located in the `<wa-icon>` shadow DOM and may refuse to load a cross-origin URL. There is currently no defined way to set a cross-origin policy for `<use>` elements. For this reason, sprite sheets should only be used if you're self-hosting them.
|
||||
:::
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary('sprite', {
|
||||
resolver: name => `/assets/images/sprite.svg#${name}`,
|
||||
mutator: svg => svg.setAttribute('fill', 'currentColor'),
|
||||
spriteSheet: true
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### Customizing the System Library
|
||||
|
||||
The system library contains only the icons used internally by Web Awesome components. Unlike the default icon library, the system library does not rely on physical assets. Instead, its icons are hard-coded as data URIs into the resolver to ensure their availability.
|
||||
|
||||
If you want to change the icons Web Awesome uses internally, you can register an icon library using the `system` name and a custom resolver. If you choose to do this, it's your responsibility to provide all of the icons that are required by components. You can reference `src/components/library.system.ts` for a complete list of system icons used by Web Awesome.
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
registerIconLibrary('system', {
|
||||
resolver: name => `/path/to/custom/icons/${name}.svg`
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -2,13 +2,10 @@
|
||||
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: []
|
||||
categories:
|
||||
tags: [actions, feedback, imagery, inputs, navigation, organization, helpers]
|
||||
titles:
|
||||
feedback: 'Feedback & Status'
|
||||
helpers: 'Utilities'
|
||||
---
|
||||
|
||||
@@ -885,4 +885,4 @@ If you don’t want to use [native styles](/docs/native/), you can include this
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="{% cdnUrl 'styles/components/page.css' %}" />
|
||||
```
|
||||
```
|
||||
@@ -468,75 +468,20 @@ Use the `sync` attribute to make the popup the same width or height as the ancho
|
||||
</script>
|
||||
```
|
||||
|
||||
### Positioning Strategy
|
||||
|
||||
By default, the popup is positioned using an absolute positioning strategy. However, if your anchor is fixed or exists within a container that has `overflow: auto|hidden`, the popup risks being clipped. To work around this, you can use a fixed positioning strategy by setting the `strategy` attribute to `fixed`.
|
||||
|
||||
The fixed positioning strategy reduces jumpiness when the anchor is fixed and allows the popup to break out containers that clip. When using this strategy, it's important to note that the content will be positioned relative to its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.
|
||||
|
||||
In this example, you can see how the popup breaks out of the overflow container when it's fixed. The fixed positioning strategy tends to be less performant than absolute, so avoid using it unnecessarily.
|
||||
|
||||
Toggle the switch and scroll the container to see the difference.
|
||||
|
||||
```html {.example}
|
||||
<div class="popup-strategy">
|
||||
<div class="overflow">
|
||||
<wa-popup placement="top" strategy="fixed" active>
|
||||
<span slot="anchor"></span>
|
||||
<div class="box"></div>
|
||||
</wa-popup>
|
||||
</div>
|
||||
|
||||
<wa-switch checked>Fixed</wa-switch>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.popup-strategy .overflow {
|
||||
position: relative;
|
||||
height: 300px;
|
||||
border: solid 2px var(--wa-color-surface-border);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.popup-strategy span[slot='anchor'] {
|
||||
display: inline-block;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border: dashed 2px var(--wa-color-neutral-fill-loud);
|
||||
margin: 150px 50px;
|
||||
}
|
||||
|
||||
.popup-strategy .box {
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
background: var(--wa-color-brand-fill-loud);
|
||||
border-radius: var(--wa-border-radius-m);
|
||||
}
|
||||
|
||||
.popup-strategy wa-switch {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const container = document.querySelector('.popup-strategy');
|
||||
const popup = container.querySelector('wa-popup');
|
||||
const fixed = container.querySelector('wa-switch');
|
||||
|
||||
fixed.addEventListener('change', () => (popup.strategy = fixed.checked ? 'fixed' : 'absolute'));
|
||||
</script>
|
||||
```
|
||||
|
||||
### Flip
|
||||
|
||||
When the popup doesn't have enough room in its preferred placement, it can automatically flip to keep it in view. To enable this, use the `flip` attribute. By default, the popup will flip to the opposite placement, but you can configure preferred fallback placements using `flip-fallback-placement` and `flip-fallback-strategy`. Additional options are available to control the flip behavior's boundary and padding.
|
||||
When the popup doesn't have enough room in its preferred placement, it can automatically flip to keep it in view and visually connected to its anchor.
|
||||
To enable this, use the `flip` attribute. By default, the popup will flip to the opposite placement, but you can configure preferred fallback placements using `flip-fallback-placement` and `flip-fallback-strategy`. Additional options are available to control the flip behavior's boundary and padding.
|
||||
|
||||
By default, flip takes effect when the popup would overflow the viewport.
|
||||
You can use `boundary="scroll"` to make the popup resize when it overflows its nearest scrollable container instead.
|
||||
|
||||
Scroll the container to see how the popup flips to prevent clipping.
|
||||
|
||||
```html {.example}
|
||||
<div class="popup-flip">
|
||||
<div class="overflow">
|
||||
<wa-popup placement="top" flip active>
|
||||
<wa-popup placement="top" flip active boundary="scroll">
|
||||
<span slot="anchor"></span>
|
||||
<div class="box"></div>
|
||||
</wa-popup>
|
||||
@@ -592,7 +537,7 @@ Scroll the container to see how the popup changes it's fallback placement to pre
|
||||
```html {.example}
|
||||
<div class="popup-flip-fallbacks">
|
||||
<div class="overflow">
|
||||
<wa-popup placement="top" flip flip-fallback-placements="right bottom" flip-fallback-strategy="initial" active>
|
||||
<wa-popup placement="top" flip flip-fallback-placements="right bottom" flip-fallback-strategy="initial" active boundary="scroll">
|
||||
<span slot="anchor"></span>
|
||||
<div class="box"></div>
|
||||
</wa-popup>
|
||||
@@ -626,14 +571,18 @@ Scroll the container to see how the popup changes it's fallback placement to pre
|
||||
|
||||
### Shift
|
||||
|
||||
When a popup is longer than its anchor, it risks being clipped by an overflowing container. In this case, use the `shift` attribute to shift the popup along its axis and back into view. You can customize the shift behavior using `shiftBoundary` and `shift-padding`.
|
||||
When a popup is longer than its anchor, it risks overflowing.
|
||||
In this case, use the `shift` attribute to shift the popup along its axis and back into view. You can customize the shift behavior using `shiftBoundary` and `shift-padding`.
|
||||
|
||||
By default, auto-size takes effect when the popup would overflow the viewport.
|
||||
You can use `boundary="scroll"` to make the popup resize when it overflows its nearest scrollable container instead.
|
||||
|
||||
Toggle the switch to see the difference.
|
||||
|
||||
```html {.example}
|
||||
<div class="popup-shift">
|
||||
<div class="overflow">
|
||||
<wa-popup placement="top" shift shift-padding="10" active>
|
||||
<wa-popup placement="top" shift shift-padding="10" active boundary="scroll">
|
||||
<span slot="anchor"></span>
|
||||
<div class="box"></div>
|
||||
</wa-popup>
|
||||
@@ -676,7 +625,11 @@ Toggle the switch to see the difference.
|
||||
|
||||
### Auto-size
|
||||
|
||||
Use the `auto-size` attribute to tell the popup to resize when necessary to prevent it from getting clipped. Possible values are `horizontal`, `vertical`, and `both`. You can use `autoSizeBoundary` and `auto-size-padding` to customize the behavior of this option. Auto-size works well with `flip`, but if you're using `auto-size-padding` make sure `flip-padding` is the same value.
|
||||
Use the `auto-size` attribute to tell the popup to resize when necessary to prevent it from overflowing.
|
||||
Possible values are `horizontal`, `vertical`, and `both`. You can use `autoSizeBoundary` and `auto-size-padding` to customize the behavior of this option. Auto-size works well with `flip`, but if you're using `auto-size-padding` make sure `flip-padding` is the same value.
|
||||
|
||||
By default, auto-size takes effect when the popup would overflow the viewport.
|
||||
You can use `boundary="scroll"` to make the popup resize when it overflows its nearest scrollable container instead.
|
||||
|
||||
When using `auto-size`, one or both of `--auto-size-available-width` and `--auto-size-available-height` will be applied to the host element. These values determine the available space the popover has before clipping will occur. Since they cascade, you can use them to set a max-width/height on your popup's content and easily control its overflow.
|
||||
|
||||
@@ -685,7 +638,7 @@ Scroll the container to see the popup resize as its available space changes.
|
||||
```html {.example}
|
||||
<div class="popup-auto-size">
|
||||
<div class="overflow">
|
||||
<wa-popup placement="top" auto-size="both" auto-size-padding="10" active>
|
||||
<wa-popup placement="top" auto-size="both" auto-size-padding="10" active boundary="scroll">
|
||||
<span slot="anchor"></span>
|
||||
<div class="box"></div>
|
||||
</wa-popup>
|
||||
|
||||
@@ -425,11 +425,3 @@ This can be hard to conceptualize, so heres a fairly large example showing how l
|
||||
</script>
|
||||
```
|
||||
|
||||
<script type="module">
|
||||
//
|
||||
// TODO - remove once we switch to the Popover API
|
||||
//
|
||||
customElements.whenDefined('wa-select').then(() => {
|
||||
document.querySelectorAll('wa-code-demo [slot="preview"] wa-select').forEach(select => select.hoist = true);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -129,7 +129,7 @@ Set a matching width and height to make a circle, square, or rounded avatar skel
|
||||
|
||||
<style>
|
||||
.skeleton-avatars wa-skeleton {
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
margin-right: 0.5rem;
|
||||
|
||||
@@ -152,25 +152,3 @@ Use the `--max-width` custom property to change the width the tooltip can grow t
|
||||
<wa-button id="wrapping-tooltip">Hover me</wa-button>
|
||||
```
|
||||
|
||||
### Hoisting
|
||||
|
||||
Tooltips will be clipped if they're inside a container that has `overflow: auto|hidden|scroll`. The `hoist` attribute forces the tooltip to use a fixed positioning strategy, allowing it to break out of the container. In this case, the tooltip will be positioned relative to its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.
|
||||
|
||||
```html {.example}
|
||||
<div class="tooltip-hoist">
|
||||
<wa-tooltip for="no-hoist">This is a tooltip</wa-tooltip>
|
||||
<wa-button id="no-hoist">No Hoist</wa-button>
|
||||
|
||||
<wa-tooltip for="hoist" hoist>This is a tooltip</wa-tooltip>
|
||||
<wa-button id="hoist">Hoist</wa-button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.tooltip-hoist {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: solid 2px var(--wa-color-surface-border);
|
||||
padding: var(--wa-space-m);
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Viewport Demo
|
||||
description: Viewport demos can be used to display an iframe as a resizable, zoomable preview.
|
||||
tags: component
|
||||
noAlpha: true
|
||||
isPro: true
|
||||
---
|
||||
|
||||
```html {.example}
|
||||
|
||||
@@ -10,7 +10,7 @@ You can customize the look and feel of Web Awesome at a high level with themes.
|
||||
|
||||
Web Awesome uses [themes](/docs/themes) to apply a cohesive look and feel across the entire library. Themes are built with a collection of predefined CSS custom properties, which we call [design tokens](/docs/tokens), and there are many premade themes you can choose from.
|
||||
|
||||
To use a theme, simply add a link to the theme's stylesheet to the `<head>` of your page. For example, you can replace the link to `default.css` in the [installation code](/docs/installation/#quick-start-autoloading-via-cdn) with this snippet to use the *Awesome* theme:
|
||||
To use a theme, simply add a link to the theme's stylesheet to the `<head>` of your page. For example, you can replace the link to `default.css` in the [installation code](/docs/#quick-start-autoloading-via-cdn) with this snippet to use the *Awesome* theme:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="{% cdnUrl 'styles/themes/awesome.css' %}" />
|
||||
@@ -197,7 +197,7 @@ For example, we can give all outlined callouts a thick left border, regardless o
|
||||
|
||||
<style>
|
||||
wa-callout:is(
|
||||
[appearance~="outlined"],
|
||||
[appearance~="outlined"],
|
||||
.wa-outlined
|
||||
) {
|
||||
border-left-width: var(--wa-panel-border-radius);
|
||||
|
||||
@@ -1,10 +1,108 @@
|
||||
/**
|
||||
* Global data for all pages
|
||||
*/
|
||||
import { sort } from '../_utils/filters.js';
|
||||
|
||||
export default {
|
||||
eleventyComputed: {
|
||||
children(data) {
|
||||
let mainTag = data.tags?.[0];
|
||||
let collection = data.collections[mainTag] ?? [];
|
||||
/**
|
||||
* Default parent slug. Can be overridden by explicitly setting parent in the data.
|
||||
* It can be either the URL slug of a page in the same directory or a parent directory.
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
parent(data) {
|
||||
let { parent, page } = data;
|
||||
|
||||
return collection.filter(item => item.data.parent === data.page.fileSlug);
|
||||
if (parent) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
return page.url.split('/').filter(Boolean).at(-2);
|
||||
},
|
||||
|
||||
/**
|
||||
* URL of parent page
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
parentUrl(data) {
|
||||
let { parent, page } = data;
|
||||
return getParentUrl(page.url, parent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Collection item of parent page
|
||||
* @returns {object | undefined} Parent page item
|
||||
*/
|
||||
parentItem(data) {
|
||||
let { parentUrl } = data;
|
||||
return data.collections.all.find(item => item.url === parentUrl);
|
||||
},
|
||||
|
||||
/**
|
||||
* Child pages of current page
|
||||
* @returns {object[]} Array of child pages
|
||||
*/
|
||||
children(data) {
|
||||
let { collections, page, parentOf } = data;
|
||||
|
||||
if (parentOf) {
|
||||
return collections[parentOf];
|
||||
}
|
||||
|
||||
let collection = collections.all ?? [];
|
||||
let url = page.url;
|
||||
|
||||
let ret = collection.filter(item => {
|
||||
return item.data.parentUrl === url;
|
||||
});
|
||||
|
||||
sort(ret);
|
||||
|
||||
return ret;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve a parent slug against a page URL
|
||||
* @param {string} url - The URL of the page
|
||||
* @param {string} parent - The slug of the parent page
|
||||
* @returns {string} The resolved URL of the parent page
|
||||
*/
|
||||
function getParentUrl(url, parent) {
|
||||
if (!parent) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let parts = url.split('/').filter(Boolean);
|
||||
let ancestorIndex = parts.findLastIndex(part => part === parent);
|
||||
let retParts = parts.slice();
|
||||
|
||||
if (ancestorIndex > -1) {
|
||||
// parent is an ancestor
|
||||
retParts.splice(ancestorIndex + 1);
|
||||
} else {
|
||||
// parent is a sibling in the same directory
|
||||
retParts.splice(-1, 1, parent);
|
||||
}
|
||||
|
||||
let ret = retParts.join('/');
|
||||
|
||||
if (url.startsWith('/')) {
|
||||
// If the current page starts with a slash, make sure the parent does too
|
||||
// This is pretty much always the case with 11ty page URLs
|
||||
ret = '/' + ret;
|
||||
}
|
||||
|
||||
if (!retParts.at(-1)?.includes('.') && !ret.endsWith('/')) {
|
||||
// If no extension, make sure to end with a slash
|
||||
ret += '/';
|
||||
}
|
||||
|
||||
if (ret === '/docs/') {
|
||||
// We don't want anyone's parent to be "Installation"!
|
||||
ret = '/';
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
---
|
||||
title: Clamped brand tokens
|
||||
title: Clamped Color Tokens
|
||||
layout: block
|
||||
---
|
||||
|
||||
{% set tints = ['40-max', '50-max', '60-max', '40-min', '50-min', '60-min'] %}
|
||||
|
||||
{% for hue in hues %}
|
||||
<link href="/dist/styles/brand/{{ hue }}.css" rel="stylesheet">
|
||||
{% endfor %}
|
||||
{% set tints = ['max-50', 'max-60', 'max-70', 'min-50', 'min-60', 'min-70'] %}
|
||||
{% set hues = ['red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'indigo', 'purple', 'pink', 'gray'] %}
|
||||
|
||||
<table class="colors">
|
||||
<thead>
|
||||
@@ -20,18 +17,18 @@ layout: block
|
||||
</tr>
|
||||
</thead>
|
||||
{% for hue in hues -%}
|
||||
<tr class="wa-brand-{{ hue }}">
|
||||
<tr class="wa-color-{{ hue }}">
|
||||
<th>{{ hue | capitalize }}</th>
|
||||
<td class="core-column">
|
||||
<div class="color swatch" style="background-color: var(--wa-color-brand); color: var(--wa-color-brand-on);">
|
||||
<div class="color swatch" style="background-color: var(--wa-color-{{ hue }}); color: var(--wa-color-{{ hue }}-on); --key: var(--wa-color-{{ hue }}-key);">
|
||||
{{ palettes[paletteId][hue].maxChromaTint }}
|
||||
<wa-copy-button value="--wa-color-brand" copy-label="--wa-color-brand"></wa-copy-button>
|
||||
<wa-copy-button value="--wa-color-{{ hue }}" copy-label="--wa-color-{{ hue }}"></wa-copy-button>
|
||||
</div>
|
||||
</td>
|
||||
{% for tint in tints -%}
|
||||
<td>
|
||||
<div class="color swatch" style="background-color: var(--wa-color-brand-{{ tint }})">
|
||||
<wa-copy-button value="--wa-color-brand-{{ tint }}" copy-label="--wa-color-brand-{{ tint }}"></wa-copy-button>
|
||||
<div class="color swatch" style="background-color: var(--wa-color-{{ hue }}-{{ tint }})">
|
||||
<wa-copy-button value="--wa-color-{{ hue }}-{{ tint }}" copy-label="--wa-color-{{ hue }}-{{ tint }}"></wa-copy-button>
|
||||
</div>
|
||||
</td>
|
||||
{%- endfor -%}
|
||||
@@ -41,7 +38,7 @@ layout: block
|
||||
|
||||
<style>
|
||||
.core-column .color.swatch::before {
|
||||
counter-reset: key var(--wa-color-brand-key);
|
||||
counter-reset: key var(--key);
|
||||
content: counter(key);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -1412,7 +1412,8 @@ hasOutline: false
|
||||
import { registerIconLibrary } from '/dist/webawesome.js';
|
||||
|
||||
// Ensure regular icons are always available for the knobs
|
||||
registerIconLibrary('fa-classic-regular', {
|
||||
registerIconLibrary({
|
||||
name: 'fa-classic-regular',
|
||||
resolver: name => `https://ka-f.fontawesome.com/releases/v6.5.1/svgs/regular/${name}.svg`
|
||||
});
|
||||
|
||||
@@ -1453,36 +1454,47 @@ hasOutline: false
|
||||
break;
|
||||
case 'premium':
|
||||
iconFamily.value = 'custom';
|
||||
registerIconLibrary('default', {
|
||||
registerIconLibrary({
|
||||
name: 'default',
|
||||
resolver: name => `/assets/icons/chunk/${name}.svg`,
|
||||
mutator: svg => {[...svg.querySelectorAll('[fill="black"]')].map(el => el.setAttribute('fill', 'currentColor'));}
|
||||
});
|
||||
registerIconLibrary('system', {
|
||||
registerIconLibrary({
|
||||
name: 'system',
|
||||
resolver: name => `/assets/icons/chunk/${name}.svg`,
|
||||
mutator: svg => {[...svg.querySelectorAll('[fill="black"]')].map(el => el.setAttribute('fill', 'currentColor'));}
|
||||
});
|
||||
break;
|
||||
case 'playful':
|
||||
iconFamily.value = 'custom';
|
||||
registerIconLibrary('default', {
|
||||
registerIconLibrary({
|
||||
name: 'default',
|
||||
resolver: name => `/assets/icons/jelly/${name}.svg`,
|
||||
mutator: svg => {[...svg.querySelectorAll('[fill="black"]')].map(el => el.setAttribute('fill', 'currentColor'));}
|
||||
});
|
||||
registerIconLibrary('system', {
|
||||
registerIconLibrary({
|
||||
name: 'system',
|
||||
name: 'system',
|
||||
name: 'system',
|
||||
resolver: name => `/assets/icons/jelly/${name}.svg`,
|
||||
mutator: svg => {[...svg.querySelectorAll('[fill="black"]')].map(el => el.setAttribute('fill', 'currentColor'));}
|
||||
});
|
||||
break;
|
||||
case 'brutalist':
|
||||
caregisterIconLibrary({
|
||||
registerIconLibrary({
|
||||
registerIconLibrary({
|
||||
name: 'default',
|
||||
iconFamily.value = 'custom';
|
||||
registerIconLibrary('default', {
|
||||
registerIconLibrary({
|
||||
name: 'default',
|
||||
resolver: name => `/assets/icons/utility/${name}.svg`,
|
||||
mutator: svg => {
|
||||
[...svg.querySelectorAll('[fill="black"]')].map(el => el.setAttribute('fill', 'currentColor'));
|
||||
[...svg.querySelectorAll('[stroke="black"]')].map(el => el.setAttribute('stroke', 'currentColor'));
|
||||
}
|
||||
});
|
||||
registerIconLibrary('system', {
|
||||
registerIconLibrary({
|
||||
name: 'system',
|
||||
resolver: name => `/assets/icons/utility/${name}.svg`,
|
||||
mutator: svg => {
|
||||
[...svg.querySelectorAll('[fill="black"]')].map(el => el.setAttribute('fill', 'currentColor'));
|
||||
@@ -1497,10 +1509,12 @@ hasOutline: false
|
||||
break;
|
||||
case 'classic':
|
||||
iconFamily.value = 'custom';
|
||||
registerIconLibrary('default', {
|
||||
registerIconLibrary({
|
||||
name: 'default',
|
||||
resolver: name => `/assets/icons/bootstrap/${name}.svg`,
|
||||
});
|
||||
registerIconLibrary('system', {
|
||||
registerIconLibrary({
|
||||
name: 'system',
|
||||
resolver: name => `/assets/icons/bootstrap/${name}.svg`,
|
||||
});
|
||||
break;
|
||||
@@ -1531,7 +1545,8 @@ hasOutline: false
|
||||
iconLibrary = 'sharp-solid';
|
||||
}
|
||||
// Ensures sharp-solid variations are available for ratings, etc.
|
||||
registerIconLibrary('always-solid', {
|
||||
registerIconLibrary({
|
||||
name: 'always-solid',
|
||||
resolver: name => `https://ka-f.fontawesome.com/releases/v6.5.1/svgs/sharp-solid/${name}.svg`
|
||||
});
|
||||
solidifyRatingStars();
|
||||
@@ -1557,15 +1572,18 @@ hasOutline: false
|
||||
iconLibrary = 'solid';
|
||||
}
|
||||
// Ensures solid variations are available for radios, ratings, etc.
|
||||
registerIconLibrary('always-solid', {
|
||||
registerIconLibrary({
|
||||
name: 'always-solid',
|
||||
resolver: name => `https://ka-f.fontawesome.com/releases/v6.5.1/svgs/solid/${name}.svg`
|
||||
});
|
||||
solidifyRatingStars();
|
||||
}
|
||||
registerIconLibrary('default', {
|
||||
registerIconLibrary({
|
||||
name: 'default',
|
||||
resolver: name => `https://ka-f.fontawesome.com/releases/v6.5.1/svgs/${iconLibrary}/${name}.svg`
|
||||
});
|
||||
registerIconLibrary('system', {
|
||||
registerIconLibrary({
|
||||
name: 'system',
|
||||
resolver: name => `https://ka-f.fontawesome.com/releases/v6.5.1/svgs/${iconLibrary}/${name}.svg`
|
||||
});
|
||||
};
|
||||
|
||||
@@ -32,15 +32,11 @@ To get everything included in Web Awesome, add the following code to the `<head>
|
||||
|
||||
This snippet includes three parts:
|
||||
1. **The default theme**, a stylesheet that gives a cohesive look to Web Awesome components with both light and dark modes
|
||||
2. **Web Awesome styles**, an optional stylesheet that [styles native HTML elements](/docs/native) and includes [utility classes](/docs/utilities) you can use in your project
|
||||
2. **Web Awesome styles**, an optional stylesheet that [styles native HTML elements](/docs/native) and includes [utility classes](/docs/utilities) you can use in your project
|
||||
3. **The autoloader**, a lightweight script watches the DOM for unregistered Web Awesome elements and lazy loads them for you — even if they're added dynamically
|
||||
|
||||
Now you can [start using Web Awesome!](/docs/usage)
|
||||
|
||||
:::info
|
||||
While convenient, autoloading may lead to a [Flash of Undefined Custom Elements](https://www.abeautifulsite.net/posts/flash-of-undefined-custom-elements/). The linked article describes some ways to alleviate it.
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Using Font Awesome Kit Codes
|
||||
@@ -62,7 +58,7 @@ Font Awesome users can set their kit code to unlock Font Awesome Pro icons. You
|
||||
|
||||
## Advanced Setup
|
||||
|
||||
The autoloader is the easiest way to use Web Awesome, but different projects (or your own preferences!) may require different installation methods.
|
||||
The autoloader is the easiest way to use Web Awesome, but different projects (or your own preferences!) may require different installation methods.
|
||||
|
||||
### Installing via npm
|
||||
|
||||
@@ -126,4 +122,4 @@ Most of the magic behind assets is handled internally by Web Awesome, but if you
|
||||
// Get the path to an asset, e.g. /path/to/assets/file.ext
|
||||
const assetPath = getBasePath('file.ext');
|
||||
</script>
|
||||
```
|
||||
```
|
||||
@@ -2,6 +2,7 @@
|
||||
title: Layout
|
||||
description: Layout components and utility classes help you organize content that can adapt to any device or screen size. See the [installation instructions](#installation) to use Web Awesome's layout tools in your project.
|
||||
layout: overview
|
||||
parentOf: layout
|
||||
categories: ["components", "utilities"]
|
||||
override:tags: []
|
||||
---
|
||||
@@ -9,7 +10,7 @@ override:tags: []
|
||||
{% markdown %}
|
||||
## Installation
|
||||
|
||||
Layout components are included in Web Awesome's [autoloader](/docs/installation/#quick-start-autoloading-via-cdn). You can also import them individually via [cherry picking](/docs/installation/#cherry-picking).
|
||||
Layout components are included in Web Awesome's [autoloader](/docs/#quick-start-autoloading-via-cdn). You can also import them individually via [cherry picking](/docs/#cherry-picking).
|
||||
|
||||
Layout utilities are bundled with all [style utilities](/docs/utilities). You can import all Web Awesome page styles (including [native styles](/docs/native/)) by including the following stylesheet in your project:
|
||||
|
||||
@@ -22,4 +23,4 @@ Or, you can choose to import _only_ the utilities:
|
||||
```html
|
||||
<link rel="stylesheet" href="{% cdnUrl 'styles/utilities.css' %}" />
|
||||
```
|
||||
{% endmarkdown %}
|
||||
{% endmarkdown %}
|
||||
|
||||
@@ -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;">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -11,8 +11,8 @@ file: styles/native/radio.css
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-cluster">
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="a" value="1" checked> Option 1</label>
|
||||
<label><input type="radio" name="a" value="2"> Option 2</label>
|
||||
</div>
|
||||
```
|
||||
|
||||
@@ -24,9 +24,9 @@ To set the initial value and checked state, use the `checked` attribute on the c
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-cluster">
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3" checked> Option 3</label>
|
||||
<label><input type="radio" name="b" value="1" checked> Option 1</label>
|
||||
<label><input type="radio" name="b" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="b" value="3"> Option 3</label>
|
||||
</div>
|
||||
```
|
||||
|
||||
@@ -36,9 +36,9 @@ Use the `disabled` attribute to disable a radio.
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-cluster">
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2" disabled> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3"> Option 3</label>
|
||||
<label><input type="radio" name="c" value="1" checked> Option 1</label>
|
||||
<label><input type="radio" name="c" value="2" disabled> Option 2</label>
|
||||
<label><input type="radio" name="c" value="3"> Option 3</label>
|
||||
</div>
|
||||
```
|
||||
|
||||
@@ -49,26 +49,26 @@ Use the [size utilities](/docs/utilities/size) to change the radios' size.
|
||||
```html {.example}
|
||||
<fieldset class="wa-size-s wa-cluster">
|
||||
<legend>Small</legend>
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3"> Option 3</label>
|
||||
<label><input type="radio" name="d" value="1" checked> Option 1</label>
|
||||
<label><input type="radio" name="d" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="d" value="3"> Option 3</label>
|
||||
</fieldset>
|
||||
|
||||
<br />
|
||||
<fieldset class="wa-size-m wa-cluster">
|
||||
<legend>Medium</legend>
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3"> Option 3</label>
|
||||
<label><input type="radio" name="e" value="1" checked> Option 1</label>
|
||||
<label><input type="radio" name="e" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="e" value="3"> Option 3</label>
|
||||
</fieldset>
|
||||
|
||||
<br />
|
||||
|
||||
<fieldset class="wa-size-l wa-cluster">
|
||||
<legend>Large</legend>
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3"> Option 3</label>
|
||||
<label><input type="radio" name="f" value="1" checked> Option 1</label>
|
||||
<label><input type="radio" name="f" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="f" value="3"> Option 3</label>
|
||||
</fieldset>
|
||||
```
|
||||
|
||||
@@ -78,16 +78,16 @@ You can wrap native radios in a flex container to give them a horizontal or vert
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-cluster">
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3" checked> Option 3</label>
|
||||
<label><input type="radio" name="g" value="1" checked> Option 1</label>
|
||||
<label><input type="radio" name="g" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="g" value="3"> Option 3</label>
|
||||
</div>
|
||||
```
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-stack">
|
||||
<label><input type="radio" name="radio" value="1"> Option 1</label>
|
||||
<label><input type="radio" name="radio" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="radio" value="3" checked> Option 3</label>
|
||||
<label><input type="radio" name="h" value="1" checked> Option 1</label>
|
||||
<label><input type="radio" name="h" value="2"> Option 2</label>
|
||||
<label><input type="radio" name="h" value="3"> Option 3</label>
|
||||
</div>
|
||||
```
|
||||
|
||||
@@ -5,6 +5,6 @@ layout: overview
|
||||
override:tags: []
|
||||
forTag: palette
|
||||
categories:
|
||||
tags: [other, pro]
|
||||
other: Free
|
||||
pro: Pro
|
||||
---
|
||||
|
||||
@@ -6,6 +6,8 @@ import { cssImport, cssLiteral, cssRule } from '../../assets/scripts/tweak/code.
|
||||
import { maxGrayChroma, moreHue, selectors, urls } from '../../assets/scripts/tweak/data.js';
|
||||
import { subtractAngles } from '../../assets/scripts/tweak/util.js';
|
||||
import Prism from '/assets/scripts/prism.js';
|
||||
import content from '/assets/scripts/vue/directives/content.js';
|
||||
import savedMixin from '/assets/scripts/vue/mixins/saved.js';
|
||||
|
||||
await Promise.all(['wa-slider'].map(tag => customElements.whenDefined(tag)));
|
||||
|
||||
@@ -38,24 +40,25 @@ for (let palette in allPalettes) {
|
||||
const percentFormatter = value => value.toLocaleString(undefined, { style: 'percent' });
|
||||
|
||||
let paletteAppSpec = {
|
||||
mixins: [savedMixin],
|
||||
|
||||
data() {
|
||||
let appRoot = document.querySelector('#palette-app');
|
||||
let paletteId = appRoot.dataset.paletteId;
|
||||
let palette = allPalettes[paletteId];
|
||||
let id = appRoot.dataset.paletteId;
|
||||
let palette = allPalettes[id];
|
||||
|
||||
return {
|
||||
uid: undefined,
|
||||
paletteId,
|
||||
paletteTitle: palette.title,
|
||||
id,
|
||||
originalTitle: palette.title,
|
||||
originalColors: palette.colors,
|
||||
permalink: new Permalink(),
|
||||
hueRanges,
|
||||
hueShifts: Object.fromEntries(hues.map(hue => [hue, 0])),
|
||||
chromaScale: 1,
|
||||
grayChroma: undefined,
|
||||
grayColor: undefined,
|
||||
tweaking: {},
|
||||
saved: null,
|
||||
type: 'palette',
|
||||
collection: 'palettes',
|
||||
};
|
||||
},
|
||||
|
||||
@@ -63,20 +66,16 @@ let paletteAppSpec = {
|
||||
// Non-reactive variables to expose
|
||||
Object.assign(this, { moreHue });
|
||||
|
||||
// Read URL params and apply them. This facilitates permalinks.
|
||||
this.permalink.mapObject(this.hueShifts, {
|
||||
keyTo: key => key.replace(/-shift$/, ''),
|
||||
keyFrom: key => key + '-shift',
|
||||
valueFrom: value => (!value ? '' : Number(value)),
|
||||
valueTo: value => (!value ? 0 : Number(value)),
|
||||
});
|
||||
|
||||
this.grayChroma = this.originalGrayChroma;
|
||||
this.grayColor = this.originalGrayColor;
|
||||
|
||||
if (location.search) {
|
||||
// Update from URL
|
||||
this.permalink.writeTo(this.hueShifts);
|
||||
// Read URL params and apply them. This facilitates permalinks.
|
||||
for (let hue in this.hueShifts) {
|
||||
if (this.permalink.has(hue + '-shift')) {
|
||||
this.hueShifts[hue] = Number(this.permalink.get(hue + '-shift'));
|
||||
}
|
||||
}
|
||||
|
||||
for (let param of ['chroma-scale', 'gray-color', 'gray-chroma']) {
|
||||
if (this.permalink.has(param)) {
|
||||
@@ -91,12 +90,6 @@ let paletteAppSpec = {
|
||||
this[prop] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.permalink.has('uid')) {
|
||||
this.uid = Number(this.permalink.get('uid'));
|
||||
}
|
||||
|
||||
this.saved = sidebar.palette.getSaved(this.getPalette());
|
||||
}
|
||||
},
|
||||
|
||||
@@ -107,6 +100,11 @@ let paletteAppSpec = {
|
||||
},
|
||||
|
||||
computed: {
|
||||
/** Default palette title for saving */
|
||||
defaultTitle() {
|
||||
return this.originalTitle + ' (tweaked)';
|
||||
},
|
||||
|
||||
tweaks() {
|
||||
return {
|
||||
hueShifts: this.hueShifts,
|
||||
@@ -123,7 +121,7 @@ let paletteAppSpec = {
|
||||
code() {
|
||||
let ret = {};
|
||||
for (let language of ['html', 'css']) {
|
||||
let code = getPaletteCode(this.paletteId, this.colors, this.tweaked, { language, cdnUrl });
|
||||
let code = getPaletteCode(this.id, this.colors, this.tweaked, { language, cdnUrl });
|
||||
ret[language] = {
|
||||
raw: code,
|
||||
highlighted: Prism.highlight(code, Prism.languages[language], language),
|
||||
@@ -284,7 +282,9 @@ let paletteAppSpec = {
|
||||
hueShifts: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.permalink.readFrom(this.hueShifts);
|
||||
for (let hue in this.hueShifts) {
|
||||
this.permalink.set(hue + '-shift', this.hueShifts[hue], 0);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -308,69 +308,12 @@ let paletteAppSpec = {
|
||||
// Update page URL
|
||||
this.permalink.updateLocation();
|
||||
|
||||
if (this.saved) {
|
||||
this.save({ silent: true });
|
||||
}
|
||||
this.unsavedChanges = true;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
getPalette() {
|
||||
return { id: this.paletteId, uid: this.uid, search: location.search };
|
||||
},
|
||||
|
||||
save({ silent } = {}) {
|
||||
let title = silent
|
||||
? (this.saved?.title ?? this.paletteTitle)
|
||||
: prompt('Palette title:', `${this.paletteTitle} (tweaked)`);
|
||||
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
|
||||
let uid = this.uid;
|
||||
|
||||
if (!uid) {
|
||||
// First time saving
|
||||
this.uid = uid = sidebar.palette.getUid();
|
||||
|
||||
this.permalink.set('uid', uid);
|
||||
this.permalink.updateLocation();
|
||||
}
|
||||
|
||||
let palette = { ...this.getPalette(), uid, title };
|
||||
|
||||
sidebar.palette.save(palette, this.saved);
|
||||
this.saved = palette;
|
||||
},
|
||||
|
||||
rename() {
|
||||
if (!this.saved) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newTitle = prompt('New title:', this.saved.title);
|
||||
|
||||
if (!newTitle) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.saved.title = newTitle;
|
||||
sidebar.palette.save(this.saved);
|
||||
},
|
||||
|
||||
deleteSaved() {
|
||||
sidebar.palette.delete(this.saved);
|
||||
},
|
||||
|
||||
postDelete() {
|
||||
this.saved = null;
|
||||
this.permalink.delete('uid');
|
||||
this.uid = undefined;
|
||||
this.permalink.updateLocation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a specific tweak or all tweaks
|
||||
* @param {string} [param] - The tweak to remove. If not provided, all tweaks are removed.
|
||||
@@ -399,28 +342,7 @@ let paletteAppSpec = {
|
||||
},
|
||||
|
||||
directives: {
|
||||
// Like v-text, but doesn't complain if the element has content,
|
||||
// making it possible to use in a PE fashion, with the contents being the fallback
|
||||
content(el, { value, arg }) {
|
||||
if (!el.dataset.fallback) {
|
||||
// Store the original content as a fallback the first time
|
||||
el.dataset.fallback = el.textContent;
|
||||
}
|
||||
|
||||
if (value === '') {
|
||||
value = el.dataset.fallback;
|
||||
} else {
|
||||
if (arg === 'number') {
|
||||
value = Number(value).toLocaleString(undefined, { maximumSignificantDigits: 2 });
|
||||
}
|
||||
}
|
||||
|
||||
if (arg === 'html') {
|
||||
el.innerHTML = value;
|
||||
} else {
|
||||
el.textContent = value;
|
||||
}
|
||||
},
|
||||
content,
|
||||
},
|
||||
|
||||
compilerOptions: {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
70
docs/docs/patterns/app/action-panel.md
Normal file
70
docs/docs/patterns/app/action-panel.md
Normal file
@@ -0,0 +1,70 @@
|
||||
---
|
||||
title: Action Panel
|
||||
description: 'Help users complete tasks efficiently with quick access to key actions.'
|
||||
icon: action-panel
|
||||
isPro: true
|
||||
---
|
||||
|
||||
## Simple
|
||||
|
||||
```html {.example}
|
||||
<wa-card style="max-width: 60ch; margin: auto">
|
||||
<div class="wa-stack wa-align-items-start">
|
||||
<h3 class="wa-heading-m">New Dashboard</h3>
|
||||
<p>Arrange your data into a single view to monitor trends and track performance.</p>
|
||||
<wa-button variant="brand" size="small">Build Dashboard</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
```
|
||||
|
||||
## With Flanked Button
|
||||
|
||||
```html {.example}
|
||||
<wa-card style="max-width: 60ch; margin: auto">
|
||||
<div class="wa-flank:end">
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<h3 class="wa-heading-m">Query with SQL Runner</h3>
|
||||
<p>Access your database to run ad hoc queries.</p>
|
||||
</div>
|
||||
<wa-button variant="brand" size="small">New Query</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
```
|
||||
|
||||
## With Switch
|
||||
|
||||
```html {.example}
|
||||
<wa-card style="max-width: 70ch; margin: auto">
|
||||
<div class="wa-stack">
|
||||
<div class="wa-flank:end">
|
||||
<h3 id="auto-renew-label" class="wa-heading-m">Auto-renew</h3>
|
||||
<wa-switch size="large" aria-labelledby="auto-renew-label"></wa-switch>
|
||||
</div>
|
||||
<p class="wa-body-s">Automatically renew your subscription using your preferred payment method. We'll send you a reminder 30 days before we draft your account.</p>
|
||||
</div>
|
||||
</wa-card>
|
||||
```
|
||||
|
||||
## Avatar and Quick actions
|
||||
|
||||
```html {.example}
|
||||
<wa-card style="margin: 0 auto; max-width: 45ch;">
|
||||
<div class="wa-flank">
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1532202802379-df93d543bac3?q=80&w=2574&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="profile-image"></wa-avatar>
|
||||
<div class="wa-split">
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<span class="wa-heading-s">Super Dog</span>
|
||||
<div class="wa-caption-m wa-cluster wa-gap-xs">
|
||||
<span>Online</span>
|
||||
<wa-icon name="circle" style="color: var(--wa-color-green); font-size: 10px;"></wa-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wa-cluster" style="font-size: var(--wa-font-size-l);">
|
||||
<wa-icon-button name="microphone" label="audio-input"></wa-icon-button>
|
||||
<wa-icon-button name="headphones" label="audio-output"></wa-icon-button>
|
||||
<wa-icon-button name="gear" label="settings"></wa-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
```
|
||||
336
docs/docs/patterns/app/activity-log.md
Normal file
336
docs/docs/patterns/app/activity-log.md
Normal file
@@ -0,0 +1,336 @@
|
||||
---
|
||||
title: Activity Log
|
||||
description: 'Track and organize recent user actions or events.'
|
||||
|
||||
---
|
||||
|
||||
## Simple
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-stack" style="max-width: 60ch; margin: auto">
|
||||
<article class="wa-flank:end wa-align-items-baseline" style="--flank-size: 10ch">
|
||||
<div class="wa-grid">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="french-fries" fixed-width></wa-icon>
|
||||
<span>Fast food</span>
|
||||
</div>
|
||||
<wa-relative-time sync></wa-relative-time>
|
||||
</div>
|
||||
<wa-tag variant="danger">- $5.00</wa-tag>
|
||||
</article>
|
||||
<wa-divider></wa-divider>
|
||||
<article class="wa-flank:end wa-align-items-baseline" style="--flank-size: 10ch">
|
||||
<div class="wa-grid">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="piggy-bank" fixed-width></wa-icon>
|
||||
<span>Refund</span>
|
||||
</div>
|
||||
<wa-relative-time date="2025-03-26T09:00:00-04:00"></wa-relative-time>
|
||||
</div>
|
||||
<wa-tag variant="success">+ $48.99</wa-tag>
|
||||
</article>
|
||||
<wa-divider></wa-divider>
|
||||
<article class="wa-flank:end wa-align-items-baseline" style="--flank-size: 10ch">
|
||||
<div class="wa-grid">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="carrot" fixed-width></wa-icon>
|
||||
<span>Groceries</span>
|
||||
</div>
|
||||
<wa-relative-time date="2025-03-24T09:00:00-04:00"></wa-relative-time>
|
||||
</div>
|
||||
<wa-tag variant="danger">- $115.37</wa-tag>
|
||||
</article>
|
||||
<wa-divider></wa-divider>
|
||||
<article class="wa-flank:end wa-align-items-baseline" style="--flank-size: 10ch">
|
||||
<div class="wa-grid">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="shirt" fixed-width></wa-icon>
|
||||
<span>Clothing</span>
|
||||
</div>
|
||||
<wa-relative-time date="2025-03-15T09:00:00-04:00"></wa-relative-time>
|
||||
</div>
|
||||
<wa-tag variant="danger">- $220.99</wa-tag>
|
||||
</article>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Timeline with Icons
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-stack wa-gap-3xs" style="max-width: 60ch; margin: auto">
|
||||
<article class="wa-flank" style="flex-wrap: nowrap">
|
||||
<wa-avatar style="--size: 2rem">
|
||||
<wa-icon slot="icon" name="acorn"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-flank:end wa-gap-xs">
|
||||
<span>Buried by <strong>squirrel</strong></span>
|
||||
<wa-format-date date="2025-04-01" month="short" day="numeric"></wa-format-date>
|
||||
</div>
|
||||
</article>
|
||||
<wa-divider vertical style="height: 1em; margin-left: 1rem"></wa-divider>
|
||||
<article class="wa-flank" style="flex-wrap: nowrap">
|
||||
<wa-avatar style="--size: 2rem">
|
||||
<wa-icon slot="icon" name="seedling"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-flank:end wa-gap-xs">
|
||||
<span>Germinated in <strong>nutrient-rich soil</strong></span>
|
||||
<wa-format-date date="2025-05-29" month="short" day="numeric"></wa-format-date>
|
||||
</div>
|
||||
</article>
|
||||
<wa-divider vertical style="height: 1em; margin-left: 1rem"></wa-divider>
|
||||
<article class="wa-flank" style="flex-wrap: nowrap">
|
||||
<wa-avatar style="--size: 2rem">
|
||||
<wa-icon slot="icon" name="tree-deciduous"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-flank:end wa-gap-xs">
|
||||
<span>Matured by <strong>water</strong> and <strong>sunlight</strong></span>
|
||||
<wa-format-date date="2025-09-15" month="short" day="numeric"></wa-format-date>
|
||||
</div>
|
||||
</article>
|
||||
<wa-divider vertical style="height: 1em; margin-left: 1rem"></wa-divider>
|
||||
<article class="wa-flank" style="flex-wrap: nowrap">
|
||||
<wa-avatar style="--size: 2rem">
|
||||
<wa-icon slot="icon" name="crate-apple"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-flank:end wa-gap-xs">
|
||||
<span>Fruit harvested by <strong>you</strong></span>
|
||||
<wa-format-date date="2025-10-18" month="short" day="numeric"></wa-format-date>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
```
|
||||
|
||||
## With Expandable Details
|
||||
|
||||
```html {.example}
|
||||
<wa-card style="max-width: 70ch; margin: auto">
|
||||
<h3 class="wa-heading-m">Monthly Activity</h3>
|
||||
<div class="wa-stack">
|
||||
<wa-details>
|
||||
<span class="wa-heading-m" slot="summary">
|
||||
February
|
||||
</span>
|
||||
<div class="wa-stack">
|
||||
<article class="wa-flank">
|
||||
<wa-icon style="font-size: var(--wa-font-size-l)" name="envelope" fixed-width></wa-icon>
|
||||
<div class="wa-split">
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<span class="wa-heading-s">Email blasts</span>
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<a href="#">Nick Burkhart</a><span>sent to</span><a href="#">likely customers</a>
|
||||
</div>
|
||||
</div>
|
||||
<wa-format-date date="2025-02-28" month="short" day="numeric" class="wa-caption-m"></wa-format-date>
|
||||
</div>
|
||||
</article>
|
||||
<wa-divider></wa-divider>
|
||||
<article class="wa-flank">
|
||||
<wa-icon style="font-size: var(--wa-font-size-l)" name="phone" fixed-width></wa-icon>
|
||||
<div class="wa-split">
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<span class="wa-heading-s">Spoke with the Pope</span>
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<a href="#">Artur Fleck</a><span>for 1 hour</span>
|
||||
</div>
|
||||
</div>
|
||||
<wa-format-date date="2025-02-23" month="short" day="numeric" class="wa-caption-m"></wa-format-date>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</wa-details>
|
||||
<wa-details>
|
||||
<span class="wa-heading-m" slot="summary">
|
||||
March
|
||||
</span>
|
||||
<div class="wa-stack">
|
||||
<article class="wa-flank">
|
||||
<wa-icon style="font-size: var(--wa-font-size-l)" name="video" fixed-width></wa-icon>
|
||||
<div class="wa-split">
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<span class="wa-heading-s">Zoom Call with Northeast office</span>
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<a href="#">Axel Foley</a><span>for 47 minutes</span>
|
||||
</div>
|
||||
</div>
|
||||
<wa-format-date date="2025-03-15" month="short" day="numeric" class="wa-caption-m"></wa-format-date>
|
||||
</div>
|
||||
</article>
|
||||
<wa-divider></wa-divider>
|
||||
<article class="wa-flank">
|
||||
<wa-icon style="font-size: var(--wa-font-size-l)" name="calendar" fixed-width></wa-icon>
|
||||
<div class="wa-split">
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<span class="wa-heading-s">Scheduled birthday party</span>
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<a href="#">John Blaze</a><span>in</span><a href="#">Social Events</a>
|
||||
</div>
|
||||
</div>
|
||||
<wa-format-date date="2025-03-03" month="short" day="numeric" class="wa-caption-m"></wa-format-date>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</wa-details>
|
||||
<wa-details>
|
||||
<span class="wa-heading-m" slot="summary">
|
||||
April
|
||||
</span>
|
||||
<div class="wa-stack">
|
||||
<article class="wa-flank">
|
||||
<wa-icon style="font-size: var(--wa-font-size-l)" family="brands" name="intercom" fixed-width></wa-icon>
|
||||
<div class="wa-split">
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<span class="wa-heading-s">Got new lead</span>
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<a href="#">Jack Carter</a><span>on Intercom switchboard</span>
|
||||
</div>
|
||||
</div>
|
||||
<wa-format-date date="2025-04-18" month="short" day="numeric" class="wa-caption-m"></wa-format-date>
|
||||
</div>
|
||||
</article>
|
||||
<wa-divider></wa-divider>
|
||||
<article class="wa-flank">
|
||||
<wa-icon style="font-size: var(--wa-font-size-l)" name="list-check" fixed-width></wa-icon>
|
||||
<div class="wa-split">
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<span class="wa-heading-s">Completed Todo</span>
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<a href="#">Huey Freeman</a><span>marked complete on</span><a href="#">Daily Tasks</a>
|
||||
</div>
|
||||
</div>
|
||||
<wa-format-date date="2025-04-02" month="short" day="numeric" class="wa-caption-m"></wa-format-date>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</wa-details>
|
||||
</div>
|
||||
</wa-card>
|
||||
```
|
||||
## Card Separated
|
||||
```html {.example}
|
||||
<div class="wa-stack" style="max-width: 45ch; margin: 0 auto;">
|
||||
<div class="wa-stack">
|
||||
<wa-card>
|
||||
<div class="wa-flank">
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1559188286-a173792c8340?q=80&w=2906&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="profile image"></wa-avatar>
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<div class="wa-split">
|
||||
<span class="wa-heading-s">Isaiah Hamilton</span>
|
||||
<wa-relative-time class="wa-caption-s" date="2025-01-15T09:17:00-04:00"></wa-relative-time>
|
||||
</div>
|
||||
<p>Who's on first?</p>
|
||||
<a href="#" class="wa-cluster wa-gap-2xs">
|
||||
<wa-icon name="reply" family="sharp" variant="regular"></wa-icon>
|
||||
<span>Reply</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
<div class="wa-flank wa-gap-xl">
|
||||
<wa-divider vertical style="height: auto; align-self: stretch"></wa-divider>
|
||||
<ul class="wa-stack">
|
||||
<li class="wa-stack wa-gap-2xs">
|
||||
<wa-card>
|
||||
<div class="wa-flank">
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1645288059073-af3e9eb62a29?q=80&w=2936&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="profile image"></wa-avatar>
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<div class="wa-split">
|
||||
<span class="wa-heading-s">Melvin Hurst</span>
|
||||
<wa-relative-time class="wa-caption-s" date="2025-02-15T09:17:00-04:00"></wa-relative-time>
|
||||
</div>
|
||||
<p>What's on second?</p>
|
||||
<a href="#" class="wa-cluster wa-gap-2xs">
|
||||
<wa-icon name="reply" family="sharp" variant="regular"></wa-icon>
|
||||
<span>Reply</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
</li>
|
||||
<li class="wa-stack wa-gap-2xs">
|
||||
<wa-card>
|
||||
<div class="wa-flank wa-align-items-start">
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1674044494331-8db2ecf18d46?q=80&w=3019&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="profile image"></wa-avatar>
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<div class="wa-split">
|
||||
<span class="wa-heading-s">Vanessa Wright</span>
|
||||
</div>
|
||||
<wa-textarea size="small" aria-label="Add Your Comment"></wa-textarea>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Divider Separated
|
||||
```html {.example}
|
||||
<wa-card with-header style="max-width: 54ch; margin: 0 auto;">
|
||||
<div slot="header" class="wa-split">
|
||||
<div>
|
||||
<span>Notifications</span>
|
||||
<wa-badge appearance="filled" variant="success" pill>2</wa-badge>
|
||||
</div>
|
||||
<wa-icon name="close"></wa-icon>
|
||||
</div>
|
||||
<div class="wa-stack">
|
||||
<article>
|
||||
<div class="wa-flank wa-align-items-start">
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1614807547811-4174d3582092?q=80&w=2932&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="profile image"></wa-avatar>
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<div class="wa-split">
|
||||
<span><strong>Happy</strong> commented in <a href="#">Reporting Dashboard</a></span>
|
||||
<wa-icon name="circle" style="color: var(--wa-color-green);"></wa-icon>
|
||||
</div>
|
||||
<div class="wa-split">
|
||||
<span class="wa-caption-m">Friday 3:12PM</span>
|
||||
<wa-relative-time class="wa-caption-m" date="2025-02-15T09:17:00-04:00"></wa-relative-time>
|
||||
</div>
|
||||
<wa-callout variant="neutral">
|
||||
Really love this approach. I think this is the best solution for the sync issue.
|
||||
</wa-callout>
|
||||
</div>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
</article>
|
||||
<article>
|
||||
<div class="wa-flank wa-align-items-start">
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1613428800237-c86372070fab?q=80&w=3017&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="profile image"></wa-avatar>
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<div class="wa-split">
|
||||
<span><strong>Charlotte</strong> followed you</span>
|
||||
<wa-icon name="circle" style="color: var(--wa-color-green);"></wa-icon>
|
||||
</div>
|
||||
<div class="wa-split">
|
||||
<span class="wa-caption-m">Friday 3:04PM</span>
|
||||
<wa-relative-time class="wa-caption-m" date="2025-02-15T09:17:00-04:00"></wa-relative-time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
</article>
|
||||
<article>
|
||||
<div class="wa-flank wa-align-items-start">
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1645288059073-af3e9eb62a29?q=80&w=2936&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="Profile image"></wa-avatar>
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<div class="wa-split">
|
||||
<span><strong>Tavitian</strong> invited you to <a href="#">Homepage Redesign</a></span>
|
||||
</div>
|
||||
<div class="wa-split">
|
||||
<span class="wa-caption-m">Friday 2:22PM</span>
|
||||
<wa-relative-time class="wa-caption-m" date="2025-02-15T09:17:00-04:00"></wa-relative-time>
|
||||
</div>
|
||||
<div class="wa-cluster wa-gap-xs">
|
||||
<wa-button appearance="outlined" size="small">Decline</wa-button>
|
||||
<wa-button variant="brand" size="small">Accept</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
</article>
|
||||
</div>
|
||||
</wa-card>
|
||||
```
|
||||
3
docs/docs/patterns/app/app.json
Normal file
3
docs/docs/patterns/app/app.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"tags": ["app"]
|
||||
}
|
||||
165
docs/docs/patterns/app/comments.md
Normal file
165
docs/docs/patterns/app/comments.md
Normal file
@@ -0,0 +1,165 @@
|
||||
---
|
||||
title: Comments
|
||||
description: 'Enable users to engage in discussions, provide feedback, or record their thoughts.'
|
||||
isPro: true
|
||||
---
|
||||
|
||||
## Card with Header & Footer
|
||||
|
||||
```html {.example}
|
||||
<form style="max-width: 60ch; margin: auto">
|
||||
<wa-card>
|
||||
<div slot="header" id="comment-area-label">
|
||||
<span class="wa-heading-s">Leave a Comment</span>
|
||||
</div>
|
||||
<wa-textarea aria-labelledby="comment-area-label"></wa-textarea>
|
||||
<div slot="footer" class="wa-cluster" style="justify-content: flex-end">
|
||||
<wa-button appearance="filled" size="small">
|
||||
<wa-icon slot="prefix" name="paperclip" variant="solid"></wa-icon>
|
||||
Attach a file
|
||||
</wa-button>
|
||||
<wa-button variant="brand" size="small">Comment</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
</form>
|
||||
```
|
||||
|
||||
## Card with Thread
|
||||
|
||||
```html {.example}
|
||||
<wa-card style="max-width: 60ch; margin: auto">
|
||||
<div class="wa-stack">
|
||||
<h3 class="wa-heading-m">Comments</h3>
|
||||
<wa-textarea aria-label="Comment"></wa-textarea>
|
||||
<wa-button variant="brand">Add Comment</wa-button>
|
||||
<wa-divider></wa-divider>
|
||||
<ul class="wa-stack">
|
||||
<li class="wa-stack wa-gap-2xs">
|
||||
<div class="wa-flank">
|
||||
<wa-avatar initials="RF" label="User avatar"></wa-avatar>
|
||||
<div class="wa-cluster">
|
||||
<strong>Robert Fox</strong>
|
||||
<span class="wa-caption-m">commented <wa-relative-time date="2025-03-31T09:17:00-04:00"></wa-relative-time></span>
|
||||
</div>
|
||||
</div>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras convallis mollis nunc, vel tempor sem faucibus nec. Suspendisse potenti. Pellentesque lobortis pulvinar nulla non tempor. Interdum et malesuada fames ac ante ipsum primis in faucibus.</p>
|
||||
</li>
|
||||
<div class="wa-flank wa-gap-xl">
|
||||
<wa-divider vertical style="height: auto; align-self: stretch"></wa-divider>
|
||||
<ul class="wa-stack">
|
||||
<li class="wa-stack wa-gap-2xs">
|
||||
<div class="wa-flank">
|
||||
<wa-avatar initials="VF" label="User avatar"></wa-avatar>
|
||||
<div class="wa-cluster">
|
||||
<strong>Virginia Woolf</strong>
|
||||
<span class="wa-caption-m">commented <wa-relative-time date="2025-03-31T12:32:00-04:00"></wa-relative-time></span>
|
||||
</div>
|
||||
</div>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras convallis mollis nunc, vel tempor sem faucibus nec.</p>
|
||||
</li>
|
||||
<li class="wa-stack wa-gap-2xs">
|
||||
<div class="wa-flank">
|
||||
<wa-avatar initials="CV" label="User avatar"></wa-avatar>
|
||||
<div class="wa-cluster">
|
||||
<strong>Clarissa Vaughan</strong>
|
||||
<span class="wa-caption-m">commented <wa-relative-time></wa-relative-time></span>
|
||||
</div>
|
||||
</div>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras convallis mollis nunc, vel tempor sem faucibus nec.</p>
|
||||
</li>
|
||||
<li class="wa-cluster">
|
||||
<wa-icon name="reply"></wa-icon>
|
||||
<a href="">Leave a reply</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</wa-card>
|
||||
```
|
||||
|
||||
## With Avatar & Additional Actions
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-align-items-start wa-flank">
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="User avatar"></wa-avatar>
|
||||
<div class="wa-stack wa-gap-s">
|
||||
<wa-textarea placeholder="Add to the conversation..." aria-label="Add comment"></wa-textarea>
|
||||
<div class="wa-split">
|
||||
<div class="wa-cluster wa-gap-s">
|
||||
<wa-icon-button name="paperclip" label="Attach File" id="attach-button"></wa-icon-button>
|
||||
<wa-tooltip for="attach-button">Attach File</wa-tooltip>
|
||||
<wa-icon-button name="face-smile" label="Add Sticker" id="sticker-button"></wa-icon-button>
|
||||
<wa-tooltip for="sticker-button">Add Sticker</wa-tooltip>
|
||||
</div>
|
||||
<wa-button variant="brand">Comment</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Rich Card with Multiple Actions
|
||||
|
||||
```html {.example}
|
||||
<wa-card with-header with-footer style="max-width: 60ch; margin: auto">
|
||||
<div slot="header">
|
||||
<h3 class="wa-heading-s">I watched...</h3>
|
||||
</div>
|
||||
<div class="wa-stack">
|
||||
<div class="wa-flank" style="--flank-size: 3rem">
|
||||
<div class="wa-frame:portrait wa-border-radius-s">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1607675742178-f616ae75044b?q=80&w=3435&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
|
||||
alt="the cover image for the film"
|
||||
/>
|
||||
</div>
|
||||
<span class="wa-heading-l">Heretic</span>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
<dl class="wa-split">
|
||||
<dt>Date</dt>
|
||||
<dd>
|
||||
<wa-format-date date="2025-03-13T00:00:00.000-04:00" weekday="long" month="long" day="numeric" year="numeric" class="wa-caption-m"></wa-format-date>
|
||||
</dd>
|
||||
</dl>
|
||||
<wa-divider></wa-divider>
|
||||
<div class="wa-split">
|
||||
<wa-rating label="Rating"></wa-rating>
|
||||
<wa-checkbox>Loved it!</wa-checkbox>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-textarea placeholder="Add review..." aria-label="Add review"></wa-textarea>
|
||||
</div>
|
||||
<div slot="footer" class="wa-grid">
|
||||
<wa-button appearance="outlined">Cancel</wa-button>
|
||||
<wa-button variant="brand">Save</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
```
|
||||
## With Preview Pane
|
||||
```html{.example}
|
||||
<div style="max-width: 60ch; margin: 0 auto;">
|
||||
<wa-card class="wa-border-radius-square" with-footer>
|
||||
<h3 class="wa-heading-m">Add a comment</h3>
|
||||
<wa-tab-group>
|
||||
<wa-tab panel="write">Write</wa-tab>
|
||||
<wa-tab panel="preview">Preview</wa-tab>
|
||||
<wa-tab-panel name="write">
|
||||
<div class="wa-stack">
|
||||
<div class="wa-cluster wa-gap-xs" style="justify-content: flex-end;">
|
||||
<wa-icon-button name="link" label="add link"></wa-icon-button>
|
||||
<wa-icon-button name="at" label="mention collaborator"></wa-icon-button>
|
||||
<wa-icon-button name="hashtag" label="change heading"></wa-icon-button>
|
||||
</div>
|
||||
<wa-textarea aria-label="Add a comment"></wa-textarea>
|
||||
</div>
|
||||
</wa-tab-panel>
|
||||
<wa-tab-panel name="preview">Your content will render here.</wa-tab-panel>
|
||||
</wa-tab-group>
|
||||
<div slot="footer" class="wa-cluster" style="justify-content: flex-end;">
|
||||
<wa-button appearance="outlined" size="small">Post</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
|
||||
</div>
|
||||
```
|
||||
174
docs/docs/patterns/app/data-display.md
Normal file
174
docs/docs/patterns/app/data-display.md
Normal file
@@ -0,0 +1,174 @@
|
||||
---
|
||||
title: Data Display
|
||||
description: 'Convey insights, metrics, and aggregate data at a glance.'
|
||||
isPro: true
|
||||
---
|
||||
|
||||
|
||||
## Simple
|
||||
```html {.example}
|
||||
<wa-card>
|
||||
<div class="wa-grid wa-gap-3xl" style="--min-column-size: 24ch;">
|
||||
<div class="wa-stack">
|
||||
<div class="wa-split">
|
||||
<div class="wa-cluster wa-gap-xs">
|
||||
<wa-icon name="sack-dollar"></wa-icon>
|
||||
<span>Incomes</span>
|
||||
</div>
|
||||
<div class="wa-cluster wa-gap-xs" style="color: var(--wa-color-green);">
|
||||
<wa-icon name="arrow-trend-up"></wa-icon>
|
||||
<wa-format-number class="wa-heading-s" type="percent" value=".475"></wa-format-number>
|
||||
</div>
|
||||
</div>
|
||||
<wa-format-number class="wa-heading-xl" type="currency" currency="USD" value="175000000" lang="en-US"></wa-format-number>
|
||||
|
||||
</div>
|
||||
<div class="wa-stack">
|
||||
<div class="wa-split">
|
||||
<div class="wa-cluster wa-gap-xs">
|
||||
<wa-icon name="credit-card"></wa-icon>
|
||||
<span>Expenses</span>
|
||||
</div>
|
||||
<div class="wa-cluster wa-gap-xs" style="color: var(--wa-color-red);">
|
||||
<wa-icon name="arrow-trend-down"></wa-icon>
|
||||
<wa-format-number class="wa-heading-s" type="percent" value=".27"></wa-format-number>
|
||||
</div>
|
||||
</div>
|
||||
<wa-format-number class="wa-heading-xl" class="wa-heading-xl" type="currency" currency="USD" value="289472" lang="en-US"></wa-format-number>
|
||||
</div>
|
||||
<div class="wa-stack">
|
||||
<div class="wa-split">
|
||||
<div class="wa-cluster wa-gap-xs">
|
||||
<wa-icon name="seedling"></wa-icon>
|
||||
<span>Investments</span>
|
||||
</div>
|
||||
<div class="wa-cluster wa-gap-xs" style="color: var(--wa-color-green);">
|
||||
<wa-icon name="arrow-trend-up"></wa-icon>
|
||||
<wa-format-number class="wa-heading-s" type="percent" value=".14"></wa-format-number>
|
||||
</div>
|
||||
</div>
|
||||
<wa-format-number class="wa-heading-xl" class="wa-heading-xl" type="currency" currency="USD" value="569213" lang="en-US"></wa-format-number>
|
||||
</div>
|
||||
<div class="wa-stack">
|
||||
<div class="wa-split">
|
||||
<div class="wa-cluster wa-gap-xs">
|
||||
<wa-icon name="landmark"></wa-icon>
|
||||
<span>Mortgages & Loans</span>
|
||||
</div>
|
||||
</div>
|
||||
<wa-format-number class="wa-heading-xl" class="wa-heading-xl" type="currency" currency="USD" value="23904" lang="en-US"></wa-format-number>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
```
|
||||
|
||||
## Cards with Avatars
|
||||
```html {.example}
|
||||
<div class="wa-grid" style="--min-column-size: 30ch">
|
||||
<wa-card>
|
||||
<div class="wa-flank wa-align-items-start">
|
||||
<wa-avatar shape="rounded">
|
||||
<wa-icon slot="icon" name="user-group"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<h3 class="wa-caption-m">Total Subscribers</h3>
|
||||
<div class="wa-cluster wa-gap-xs">
|
||||
<span class="wa-heading-l">81,779</span>
|
||||
<wa-badge variant="success" appearance="filled outlined" pill>
|
||||
<wa-icon fixed-width name="arrow-up" label="Up"></wa-icon>
|
||||
212
|
||||
</wa-badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card>
|
||||
<div class="wa-flank wa-align-items-start">
|
||||
<wa-avatar shape="rounded">
|
||||
<wa-icon slot="icon" name="envelope-open"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<h3 class="wa-caption-m">Open Rate</h3>
|
||||
<div class="wa-cluster wa-gap-xs">
|
||||
<span class="wa-heading-l">61.58%</span>
|
||||
<wa-badge variant="success" appearance="filled outlined" pill>
|
||||
<wa-icon fixed-width name="arrow-up" label="Up"></wa-icon>
|
||||
4.5%
|
||||
</wa-badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card>
|
||||
<div class="wa-flank wa-align-items-start">
|
||||
<wa-avatar shape="rounded">
|
||||
<wa-icon slot="icon" name="arrow-pointer"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<h3 class="wa-caption-m">Click Rate</h3>
|
||||
<div class="wa-cluster wa-gap-xs">
|
||||
<span class="wa-heading-l">25.74%</span>
|
||||
<wa-badge variant="danger" appearance="filled outlined" pill>
|
||||
<wa-icon fixed-width name="arrow-down" label="Down"></wa-icon>
|
||||
2.1%
|
||||
</wa-badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Condensed Card
|
||||
|
||||
```html {.example}
|
||||
<wa-card style="max-width: 50ch; margin: auto">
|
||||
<div slot="header" class="wa-split">
|
||||
<h3 class="wa-heading-m"><span style="color: var(--wa-color-text-quiet)">query</span> getUser</h3>
|
||||
<wa-icon-button id="go-to-query-button" name="chevron-right" label="Go to Query"></wa-icon-button>
|
||||
<wa-tooltip for="go-to-query-button">Go to Query</wa-tooltip>
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-xl">
|
||||
<div class="wa-split wa-align-items-stretch">
|
||||
<article class="wa-stack wa-align-items-start wa-gap-xs">
|
||||
<h4 class="wa-caption-l">Cache Hit Rate</h4>
|
||||
<div class="wa-cluster wa-heading-2xl">
|
||||
<wa-progress-ring value="12.3" style="--size: 1em; --track-width: 0.125em"></wa-progress-ring>
|
||||
<span>12.3%</span>
|
||||
</div>
|
||||
<wa-badge appearance="filled outlined" variant="danger"><wa-icon name="arrow-down"></wa-icon> down from 19.6%</wa-badge>
|
||||
</article>
|
||||
<article class="wa-stack wa-gap-xs wa-align-items-end">
|
||||
<h4 class="wa-caption-l">Max CHR</h4>
|
||||
<span class="wa-heading-2xl">72.6%</span>
|
||||
<wa-badge appearance="filled outlined" variant="success"><wa-icon name="sparkles"></wa-icon> CHR Impact +5.4%</wa-badge>
|
||||
</article>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
<article class="wa-stack wa-gap-xl">
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<h4 class="wa-caption-l">Cacheable Bandwidth</h4>
|
||||
<div class="wa-split">
|
||||
<span class="wa-heading-2xl">90.5 GB</span>
|
||||
<span class="wa-caption-xl">69.9%</span>
|
||||
</div>
|
||||
<wa-progress-bar value="30.1" label="Cached and non-cacheable bandwidth"></wa-progress-bar>
|
||||
</div>
|
||||
<dl class="wa-stack wa-caption-m">
|
||||
<div class="wa-cluster">
|
||||
<dt>Cached</dt>
|
||||
<dd>12.8 GB (9.8%)</dd>
|
||||
</div>
|
||||
<div class="wa-cluster">
|
||||
<dt>Non-Cacheable</dt>
|
||||
<dd>26.3 GB (20.3%)</dd>
|
||||
</div>
|
||||
<div class="wa-cluster">
|
||||
<dt>Total</dt>
|
||||
<dd>129.6 GB</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</article>
|
||||
</div>
|
||||
</wa-card>
|
||||
```
|
||||
253
docs/docs/patterns/app/description-list.md
Normal file
253
docs/docs/patterns/app/description-list.md
Normal file
@@ -0,0 +1,253 @@
|
||||
---
|
||||
title: Description List
|
||||
description: 'Help users digest detailed information in a structured, easy-to-scan format.'
|
||||
isPro: true
|
||||
---
|
||||
|
||||
## Left Aligned
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-stack">
|
||||
<h3 class="wa-heading-m">Applicant Info</h3>
|
||||
<p class="wa-caption-m">Details about the applicant and attachments.</p>
|
||||
<wa-divider></wa-divider>
|
||||
<dl class="wa-stack wa-gap-2xl">
|
||||
<div class="wa-flank" style="--flank-size: 20ch;">
|
||||
<dt>Full name</dt>
|
||||
<dd>Bucky Barnes</dd>
|
||||
</div>
|
||||
<div class="wa-flank" style="--flank-size: 20ch;">
|
||||
<dt>Application for</dt>
|
||||
<dd>Machine Learning Engineer</dd>
|
||||
</div>
|
||||
<div class="wa-flank" style="--flank-size: 20ch;">
|
||||
<dt>Email address</dt>
|
||||
<dd>winter_soldier@example.com</dd>
|
||||
</div>
|
||||
<div class="wa-flank" style="--flank-size: 20ch;">
|
||||
<dt>Salary expectation</dt>
|
||||
<dd>$240,000</dd>
|
||||
</div>
|
||||
<div class="wa-flank wa-align-items-start" style="--flank-size: 20ch;">
|
||||
<dt>About</dt>
|
||||
<dd>After being lost in action and brainwashed into becoming Hydra's ruthless assassin, my journey is one of redemption, healing, and reclaiming my true self. Though burdened with the weight of the past, I remain a fierce warrior, loyal to those I love, and I'm always striving to atone for those dark days as the Winter Soldier.
|
||||
</dd>
|
||||
</div>
|
||||
<div class="wa-flank wa-align-items-start" style="--flank-size: 20ch;">
|
||||
<dt>Attachments</dt>
|
||||
<dd>
|
||||
<wa-card>
|
||||
<div class="wa-stack">
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="paperclip"></wa-icon>
|
||||
<div class="wa-split">
|
||||
<span class="wa-caption-m wa-cluster">
|
||||
<span>bb_resume.pdf</span>
|
||||
<span>2.4mb</span>
|
||||
</span>
|
||||
<wa-button appearance="plain" variant="brand" size="small">Download</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="paperclip"></wa-icon>
|
||||
<div class="wa-split">
|
||||
<span class="wa-caption-m wa-cluster">
|
||||
<span>bb_cover_letter.pdf</span>
|
||||
<span>2.4mb</span>
|
||||
</span>
|
||||
<wa-button appearance="plain" variant="brand" size="small">Download</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Two Column
|
||||
|
||||
```html{.example}
|
||||
<div class="wa-stack">
|
||||
<h2 class="wa-heading-m">Applicant Info</h2>
|
||||
<p class="wa-caption-m">Details about the applicant and attachments.</p>
|
||||
<wa-divider></wa-divider>
|
||||
<dl class="wa-grid wa-gap-2xl" style="--min-column-size: 40ch;">
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<dt>Full name</dt>
|
||||
<dd>Bucky Barnes</dd>
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<dt>Application for</dt>
|
||||
<dd>Machine Learning Engineer</dd>
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<dt>Email address</dt>
|
||||
<dd>winter_soldier@example.com</dd>
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<dt>Salary expectation</dt>
|
||||
<dd>$240,000</dd>
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-xs wa-span-grid">
|
||||
<dt>About</dt>
|
||||
<dd>After being lost in action and brainwashed into becoming Hydra's ruthless assassin, my journey is one of redemption, healing, and reclaiming my true self. Though burdened with the weight of the past, I remain a fierce warrior, loyal to those I love, and I'm always striving to atone for those dark days as the Winter Soldier.
|
||||
</dd>
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-xs wa-span-grid">
|
||||
<dt>Attachments</dt>
|
||||
<dd>
|
||||
<wa-card>
|
||||
<div>
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="paperclip"></wa-icon>
|
||||
<div class="wa-split">
|
||||
<span class="wa-caption-m wa-cluster">
|
||||
<span>bb_resume.pdf</span>
|
||||
<span>2.4mb</span>
|
||||
</span>
|
||||
<wa-button appearance="plain" variant="brand" size="small">Download</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="paperclip"></wa-icon>
|
||||
<div class="wa-split">
|
||||
<span class="wa-caption-m wa-cluster">
|
||||
<span>bb_cover_letter.pdf</span>
|
||||
<span>2.4mb</span>
|
||||
</span>
|
||||
<wa-button appearance="plain" variant="brand" size="small">Download</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Left Aligned with Actions
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-stack">
|
||||
<h3 class="wa-heading-m">Applicant Info</h3>
|
||||
<p class="wa-caption-m">Details about the applicant and attachments.</p>
|
||||
<wa-divider></wa-divider>
|
||||
<dl class="wa-stack wa-gap-2xl">
|
||||
<div class="wa-flank" style="--flank-size: 20ch;">
|
||||
<dt>Full name</dt>
|
||||
<div class="wa-flank:end">
|
||||
<dd>Bucky Barnes</dd>
|
||||
<wa-button appearance="plain" variant="brand" size="small">Edit</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wa-flank" style="--flank-size: 20ch;">
|
||||
<dt>Application for</dt>
|
||||
<div class="wa-flank:end">
|
||||
<dd>Machine Learning Engineer</dd>
|
||||
<wa-button appearance="plain" variant="brand" size="small">Edit</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wa-flank" style="--flank-size: 20ch;">
|
||||
<dt>Email address</dt>
|
||||
<div class="wa-flank:end">
|
||||
<dd>winter_soldier@example.com</dd>
|
||||
<wa-button appearance="plain" variant="brand" size="small">Edit</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wa-flank" style="--flank-size: 20ch;">
|
||||
<dt>Salary expectation</dt>
|
||||
<div class="wa-flank:end">
|
||||
<dd>$240,000</dd>
|
||||
<wa-button appearance="plain" variant="brand" size="small">Edit</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wa-flank wa-align-items-start" style="--flank-size: 20ch;">
|
||||
<dt>About</dt>
|
||||
<div class="wa-flank:end">
|
||||
<dd>After being lost in action and brainwashed into becoming Hydra's ruthless assassin, my journey is one of redemption, healing, and reclaiming my true self. Though burdened with the weight of the past, I remain a fierce warrior, loyal to those I love, and I'm always striving to atone for those dark days as the Winter Soldier.</dd>
|
||||
<wa-button appearance="plain" variant="brand" size="small">Edit</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wa-flank" style="--flank-size: 20ch;">
|
||||
<dt>Attachments</dt>
|
||||
<dd>
|
||||
<wa-card>
|
||||
<div class="wa-stack">
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="paperclip"></wa-icon>
|
||||
<div class="wa-split">
|
||||
<span class="wa-caption-m wa-cluster">
|
||||
<span>bb_resume.pdf</span>
|
||||
<span>2.4mb</span>
|
||||
</span>
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<wa-button appearance="plain" variant="brand" size="small">Download</wa-button>
|
||||
<wa-divider vertical style="height: 1em"></wa-divider>
|
||||
<wa-button appearance="plain" variant="danger" size="small">Delete</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="paperclip"></wa-icon>
|
||||
<div class="wa-split">
|
||||
<span class="wa-caption-m wa-cluster">
|
||||
<span>bb_cover_letter.pdf</span>
|
||||
<span>2.4mb</span>
|
||||
</span>
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<wa-button appearance="plain" variant="brand" size="small">Download</wa-button>
|
||||
<wa-divider vertical style="height: 1em"></wa-divider>
|
||||
<wa-button appearance="plain" variant="danger" size="small">Delete</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Condensed
|
||||
|
||||
```html{.example}
|
||||
<wa-card appearance="filled" style="max-width: 45ch; margin: auto">
|
||||
<div class="wa-stack">
|
||||
<div class="wa-split wa-align-items-start">
|
||||
<dl class="wa-stack wa-gap-2xs">
|
||||
<dt class="wa-heading-s">Amount</dt>
|
||||
<dd class="wa-heading-l">$5,610.00</dd>
|
||||
</dl>
|
||||
<wa-badge appearance="filled outlined" variant="success">Paid</wa-badge>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
<dl class="wa-stack">
|
||||
<div class="wa-flank wa-align-items-stretch">
|
||||
<dt><wa-icon name="user" label="Name" fixed-width></wa-icon></dt>
|
||||
<dd>Sam Wilson</dd>
|
||||
</div>
|
||||
<div class="wa-flank wa-align-items-stretch">
|
||||
<dt><wa-icon name="calendar-days" label="Date" fixed-width></wa-icon></dt>
|
||||
<dd><wa-format-date date="2025-03-15"></wa-format-date></dd>
|
||||
</div>
|
||||
<div class="wa-flank wa-align-items-stretch">
|
||||
<dt><wa-icon family="brands" name="cc-visa" label="Credit Card" fixed-width></wa-icon></dt>
|
||||
<dd>Paid with Visa 1234</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<a href="" class="wa-cluster wa-gap-2xs">
|
||||
<span>Download Receipt</span>
|
||||
<wa-icon name="arrow-right"></wa-icon>
|
||||
</a>
|
||||
</div>
|
||||
</wa-card>
|
||||
```
|
||||
207
docs/docs/patterns/app/empty-state.md
Normal file
207
docs/docs/patterns/app/empty-state.md
Normal file
@@ -0,0 +1,207 @@
|
||||
---
|
||||
title: Empty State
|
||||
description: 'Guide users with helpful prompts and visuals when no content is available.'
|
||||
isPro: true
|
||||
---
|
||||
|
||||
## Simple
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-stack wa-align-items-center">
|
||||
<wa-icon name="backpack" class="wa-caption-l" style="font-size: var(--wa-font-size-3xl)"></wa-icon>
|
||||
<span class="wa-heading-m">No Kits</span>
|
||||
<p class="wa-caption-l">Manage all of your project's icons in a kit.</p>
|
||||
<wa-button>
|
||||
<wa-icon slot="prefix" name="plus"></wa-icon>
|
||||
Add Kit
|
||||
</wa-button>
|
||||
</div>
|
||||
```
|
||||
|
||||
## With Interactive Placeholder
|
||||
|
||||
```html {.example}
|
||||
<a href="" class="wa-stack wa-align-items-center wa-placeholder wa-link-plain" style="max-width: 60ch; margin: auto">
|
||||
<wa-icon name="ufo-beam" class="wa-caption-l" family="sharp" style="font-size: var(--wa-font-size-3xl)"></wa-icon>
|
||||
<p class="wa-heading-m">No Custom Icons</p>
|
||||
<p style="text-align: center">Add your own icon or logo to get started.</p>
|
||||
</a>
|
||||
```
|
||||
|
||||
## With Templates
|
||||
|
||||
```html {.example}
|
||||
<wa-card with-header with-footer style="max-width: 70ch; margin: auto">
|
||||
<div slot="header" class="wa-stack wa-gap-xs">
|
||||
<h2 class="wa-heading-m">Projects</h2>
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-xl">
|
||||
<p class="wa-caption-m">You haven’t created a project yet. Get started by selecting a template or start with a blank canvas.</p>
|
||||
<div class="wa-grid wa-gap-xl" style="--min-column-size: 30ch;">
|
||||
<a href="" class="wa-flank wa-align-items-start wa-link-plain">
|
||||
<wa-avatar shape="rounded" style="--background-color: var(--wa-color-yellow-90);color: var(--wa-color-yellow-40);">
|
||||
<wa-icon slot="icon" name="note-sticky"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<span class="wa-align-items-center wa-cluster wa-gap-xs wa-heading-s">
|
||||
Create a Quick Note <wa-icon name="arrow-right"></wa-icon>
|
||||
</span>
|
||||
<p class="wa-caption-m">
|
||||
Jot down any idea. Will it make sense later? Who knows.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="" class="wa-flank wa-align-items-start wa-link-plain">
|
||||
<wa-avatar shape="rounded" style="--background-color: var(--wa-color-red-90);color: var(--wa-color-red-40);">
|
||||
<wa-icon slot="icon" name="list-check"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<span class="wa-align-items-center wa-cluster wa-gap-xs wa-heading-s">
|
||||
Create a Checklist <wa-icon name="arrow-right"></wa-icon>
|
||||
</span>
|
||||
<p class="wa-caption-m">
|
||||
The ultimate tool for looking busy.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href=""class="wa-flank wa-align-items-start wa-link-plain">
|
||||
<wa-avatar shape="rounded" style="--background-color: var(--wa-color-purple-90);color: var(--wa-color-purple-40);">
|
||||
<wa-icon slot="icon" name="table-cells"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<span class="wa-align-items-center wa-cluster wa-gap-xs wa-heading-s">
|
||||
Create a Spreadsheet <wa-icon name="arrow-right"></wa-icon>
|
||||
</span>
|
||||
<p class="wa-caption-m">
|
||||
Endless rows and columns of tiny, soul-crushing numbers.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="" class="wa-flank wa-align-items-start wa-link-plain">
|
||||
<wa-avatar shape="rounded" style="--background-color: var(--wa-color-orange-90);color: var(--wa-color-orange-40);">
|
||||
<wa-icon slot="icon" name="presentation-screen"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<span class="wa-align-items-center wa-cluster wa-gap-xs wa-heading-s">
|
||||
Create a Slideshow <wa-icon name="arrow-right"></wa-icon>
|
||||
</span>
|
||||
<p class="wa-caption-m">
|
||||
Dramatic transitions make everything seem more official.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="" class="wa-flank wa-align-items-start wa-link-plain">
|
||||
<wa-avatar shape="rounded" style="--background-color: var(--wa-color-green-90);color: var(--wa-color-green-40);">
|
||||
<wa-icon slot="icon" name="pen-field"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<span class="wa-align-items-center wa-cluster wa-gap-xs wa-heading-s">
|
||||
Create a Form <wa-icon name="arrow-right"></wa-icon>
|
||||
</span>
|
||||
<p class="wa-caption-m">
|
||||
Collect the deepest personal details and darkest secrets.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="" class="wa-flank wa-align-items-start wa-link-plain">
|
||||
<wa-avatar shape="rounded" style="--background-color: var(--wa-color-blue-90);color: var(--wa-color-blue-40);">
|
||||
<wa-icon slot="icon" name="image"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<span class="wa-align-items-center wa-cluster wa-gap-xs wa-heading-s">
|
||||
Create a Photo Album <wa-icon name="arrow-right"></wa-icon>
|
||||
</span>
|
||||
<p class="wa-caption-m">
|
||||
Curate your best memories or most basic food pictures.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<a href="" class="wa-cluster wa-gap-xs">
|
||||
<span>Or start with a blank canvas</span>
|
||||
<wa-icon name="arrow-right"></wa-icon>
|
||||
</a>
|
||||
</div>
|
||||
</wa-card>
|
||||
```
|
||||
## Add people
|
||||
```html {.example}
|
||||
<wa-card style="max-width: 60ch; margin: 0 auto;">
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<h3 class="wa-heading-m">Add team members</h3>
|
||||
<p>This project is awful lonely. Invite some team members to liven up the joint.</p>
|
||||
<div class="wa-flank:end wa-gap-xs">
|
||||
<wa-input></wa-input><wa-button>Invite</wa-button>
|
||||
</div>
|
||||
<div class="wa-stack">
|
||||
<em class="wa-caption-l">Team members previously added to projects</em>
|
||||
<wa-divider></wa-divider>
|
||||
<section>
|
||||
<div class="wa-flank">
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1614807547811-4174d3582092?q=80&w=2932&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="profile image"></wa-avatar>
|
||||
<div class="wa-split">
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<span class="wa-heading-s">Earl Upton</span>
|
||||
<span class="wa-caption-m">DevOps</span>
|
||||
</div>
|
||||
<wa-button appearance="plain">
|
||||
<wa-icon name="user-plus" slot="prefix"></wa-icon>
|
||||
Invite
|
||||
</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
</section>
|
||||
<section>
|
||||
<div class="wa-flank">
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1613428800237-c86372070fab?q=80&w=3017&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" lable="profile image"></wa-avatar>
|
||||
<div class="wa-split">
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<span class="wa-heading-s">Steamboat Willie</span>
|
||||
<span class="wa-caption-m">Captain</span>
|
||||
</div>
|
||||
<wa-button appearance="plain">
|
||||
<wa-icon name="user-plus" slot="prefix"></wa-icon>
|
||||
Invite
|
||||
</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
</section>
|
||||
<section>
|
||||
<div class="wa-flank">
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1678582967399-bf558533f5eb?q=80&w=3029&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="profile image"></wa-avatar>
|
||||
<div class="wa-split">
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<span class="wa-heading-s">Melissa Eckers</span>
|
||||
<span class="wa-caption-m">Cloud Engineer</span>
|
||||
</div>
|
||||
<wa-button appearance="plain">
|
||||
<wa-icon name="user-plus" slot="prefix"></wa-icon>
|
||||
Invite
|
||||
</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
</section><section>
|
||||
<div class="wa-flank">
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1559188286-a173792c8340?q=80&w=2906&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="profile image"></wa-avatar>
|
||||
<div class="wa-split">
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<span class="wa-heading-s">Devin Shears</span>
|
||||
<span class="wa-caption-m">UX Writer</span>
|
||||
</div>
|
||||
<wa-button appearance="plain">
|
||||
<wa-icon name="user-plus" slot="prefix"></wa-icon>
|
||||
Invite
|
||||
</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
```
|
||||
119
docs/docs/patterns/app/faq.md
Normal file
119
docs/docs/patterns/app/faq.md
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
title: FAQ
|
||||
description: 'Empower users to learn more with a structured list of questions and answers.'
|
||||
isPro: true
|
||||
---
|
||||
|
||||
## With Flanked Heading & Description
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-flank wa-align-items-start wa-gap-2xl" style="--flank-size: 35ch">
|
||||
<div>
|
||||
<h2>Frequently Asked Questions</h2>
|
||||
<p>Can’t find an answer? Reach out to your local <a href="">Operator</a>, or contact <a href="">the Oracle</a>, and enjoy a cookie. 🍪</p>
|
||||
</div>
|
||||
<dl class="wa-stack wa-gap-2xl">
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<dt>Is Zion actually real, or just another Matrix?</dt>
|
||||
<dd>Ah, the question that keeps redpills up at night. Sure, we escaped the first Matrix, but who’s to say Zion isn’t just another layer of the simulation?</dd>
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<dt>Why do the Agents always wear suits?</dt>
|
||||
<dd>Because nothing says "unstoppable digital enforcer" like a generic business professional aesthetic. Also, intimidation. You ever try fighting someone in sunglasses and a tie? It’s terrifying.</dd>
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<dt>Can I go back into the Matrix once I’m out?</dt>
|
||||
<dd>Technically, yes—via hacking in. Emotionally? That depends on how well you handle the knowledge that nothing around you is real.</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
```
|
||||
|
||||
## With Expandable Answers
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-stack">
|
||||
<h2>Frequently Asked Questions</h2>
|
||||
<wa-details appearance="plain">
|
||||
<h3 slot="summary" class="wa-heading-m" style="margin: 0">Is Zion actually real, or just another Matrix?</h3>
|
||||
Ah, the question that keeps redpills up at night. Sure, we escaped the first Matrix, but who’s to say Zion isn’t just another layer of the simulation?
|
||||
</wa-details>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-details appearance="plain">
|
||||
<h3 slot="summary" class="wa-heading-m" style="margin: 0">Why do the Agents always wear suits?</h3>
|
||||
Because nothing says "unstoppable digital enforcer" like a generic business professional aesthetic. Also, intimidation. You ever try fighting someone in sunglasses and a tie? It’s terrifying.
|
||||
</wa-details>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-details appearance="plain">
|
||||
<h3 slot="summary" class="wa-heading-m" style="margin: 0">Can I go back into the Matrix once I’m out?</h3>
|
||||
Technically, yes—via hacking in. Emotionally? That depends on how well you handle the knowledge that nothing around you is real.
|
||||
</wa-details>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Two Column
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-stack wa-gap-2xl">
|
||||
<h2>Frequently Asked Questions</h2>
|
||||
<dl class="wa-stack wa-gap-2xl">
|
||||
<div class="wa-grid wa-gap-xs">
|
||||
<dt class="wa-heading-m">Is Zion actually real, or just another Matrix?</dt>
|
||||
<dd>Ah, the question that keeps redpills up at night. Sure, we escaped the first Matrix, but who’s to say Zion isn’t just another layer of the simulation?</dd>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
<div class="wa-grid wa-gap-xs">
|
||||
<dt class="wa-heading-m">Why do the Agents always wear suits?</dt>
|
||||
<dd>Because nothing says "unstoppable digital enforcer" like a generic business professional aesthetic. Also, intimidation. You ever try fighting someone in sunglasses and a tie? It’s terrifying.</dd>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
<div class="wa-grid wa-gap-xs">
|
||||
<dt class="wa-heading-m">Can I go back into the Matrix once I’m out?</dt>
|
||||
<dd>Technically, yes—via hacking in. Emotionally? That depends on how well you handle the knowledge that nothing around you is real.</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Multiple Columns
|
||||
```html{.example}
|
||||
<div>
|
||||
|
||||
<h2>Frequently Asked Questions</h2>
|
||||
|
||||
|
||||
<dl class="wa-grid wa-gap-m" style="--min-column-size: 30ch;">
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<dt class="wa-heading-m">How often do you update your courses?</dt>
|
||||
<dd>A course is updated once there is a fundamental shift in the language or library’s underlying API. You can check our <a href="#">workshop</a> list to see if a new version of a given course is on the schedule. You may also write to us as <a href="#">support@frontendmasters.com</a> with suggestions for updates.</dd>
|
||||
</div>
|
||||
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<dt class="wa-heading-m">Do you offer certificates of completion?</dt>
|
||||
<dd>You can download certificates of completion from the <a href="#">Completed Courses</a> list in your Learning Library. Click the diploma icon next to the course to download the certificate in light or dark mode. A link to your Public Profile is included on each certificate if you’ve created one. Public Profiles showcase your learning journey and are a fantastic way to share progress with friends, co-workers, or employers. Public Profiles are available to members with an active Frontend Masters subscription who have watched ten or more hours of content. Visit the <a href="#">Public Profile</a> section in My Account to get started.</dd>
|
||||
</div>
|
||||
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<dt class="wa-heading-m">Do you offer a free trial?</dt>
|
||||
<dd>
|
||||
<p>We offer a free trial to first-time subscribers. You can find more about the trial here.</p>
|
||||
<p>We also have the following opportunities to learn for free:</p>
|
||||
<ul>
|
||||
<li>The online bootcamp is a free, two-week curriculum to get you started with web development.</li>
|
||||
<li>You can create a free account to gain access to five full courses for free.</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<dt class="wa-heading-m">Do you have discounts for students?</dt>
|
||||
<dd>We are part of the <a href="#">GitHub Student Developer Pack</a>, allowing students six months of free access to the entire platform.</dd>
|
||||
</div>
|
||||
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<dt class="wa-heading-m">How do I cancel my plan?</dt>
|
||||
<dd>You can cancel your Frontend Masters subscription by visiting the <a href="#">Subscription tab</a> in your My Account area.</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
```
|
||||
525
docs/docs/patterns/app/grid-list.md
Normal file
525
docs/docs/patterns/app/grid-list.md
Normal file
@@ -0,0 +1,525 @@
|
||||
---
|
||||
title: Grid List
|
||||
description: 'Improve browsing and selection by organizing data in a structured grid layout.'
|
||||
isPro: true
|
||||
---
|
||||
|
||||
## Cards with Footer Actions
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-grid" style="--min-column-size: 30ch;">
|
||||
<wa-card with-footer>
|
||||
<div class="wa-flank:end">
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<div class="wa-cluster wa-gap-xs">
|
||||
<h3 class="wa-heading-s">Barklia Woofington</h3 class="wa-heading-s">
|
||||
<wa-badge pill>Admin</wa-badge>
|
||||
</div>
|
||||
<span class="wa-caption-m">Canine Executive Officer</span>
|
||||
</div>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1593270379182-fe1b1f6d67e5?q=80&w=2175&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="Avatar of black and white Border Collie"></wa-avatar>
|
||||
</div>
|
||||
<div slot="footer" class="wa-grid wa-gap-xs" style="--min-column-size: 10ch;">
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="at"></wa-icon>
|
||||
Email
|
||||
</wa-button>
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="phone"></wa-icon>
|
||||
Phone
|
||||
</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card with-footer>
|
||||
<div class="wa-flank:end">
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<div class="wa-cluster wa-gap-xs">
|
||||
<h3 class="wa-heading-s">Maggie Pawsworth</h3 class="wa-heading-s">
|
||||
<wa-badge pill>Admin</wa-badge>
|
||||
</div>
|
||||
<span class="wa-caption-m">Canine Fetch Officer</span>
|
||||
</div>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1514479425649-0981aca9fe41?q=80&w=3474&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="Avatar of black collie mix"></wa-avatar>
|
||||
</div>
|
||||
<div slot="footer" class="wa-grid wa-gap-xs" style="--min-column-size: 10ch;">
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="at"></wa-icon>
|
||||
Email
|
||||
</wa-button>
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="phone"></wa-icon>
|
||||
Phone
|
||||
</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card with-footer>
|
||||
<div class="wa-flank:end">
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<h3 class="wa-heading-s">Rex Tailwag</h3 class="wa-heading-s">
|
||||
<span class="wa-caption-m">Head of Security</span>
|
||||
</div>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1610968755695-d7fcb5fd4b92?q=80&w=2848&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="Avatar of black and tan German Shepherd"></wa-avatar>
|
||||
</div>
|
||||
<div slot="footer" class="wa-grid wa-gap-xs" style="--min-column-size: 10ch;">
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="at"></wa-icon>
|
||||
Email
|
||||
</wa-button>
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="phone"></wa-icon>
|
||||
Phone
|
||||
</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card with-footer>
|
||||
<div class="wa-flank:end">
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<h3 class="wa-heading-s">Luna Sniffington</h3 class="wa-heading-s">
|
||||
<span class="wa-caption-m">Hound Relations</span>
|
||||
</div>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1526440847959-4e38e7f00b04?q=80&w=3540&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="Avatar of black and tan Yorkshire Terrier"></wa-avatar>
|
||||
</div>
|
||||
<div slot="footer" class="wa-grid wa-gap-xs" style="--min-column-size: 10ch;">
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="at"></wa-icon>
|
||||
Email
|
||||
</wa-button>
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="phone"></wa-icon>
|
||||
Phone
|
||||
</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card with-footer>
|
||||
<div class="wa-flank:end">
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<h3 class="wa-heading-s">Charlie Drooler</h3 class="wa-heading-s">
|
||||
<span class="wa-caption-m">Head of Sales</span>
|
||||
</div>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1554692844-6627ca340264?q=80&w=3540&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="Avatar of tan and white corgi"></wa-avatar>
|
||||
</div>
|
||||
<div slot="footer" class="wa-grid wa-gap-xs" style="--min-column-size: 10ch;">
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="at"></wa-icon>
|
||||
Email
|
||||
</wa-button>
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="phone"></wa-icon>
|
||||
Phone
|
||||
</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card with-footer>
|
||||
<div class="wa-flank:end">
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<h3 class="wa-heading-s">Daisy Zoomley</h3 class="wa-heading-s">
|
||||
<span class="wa-caption-m">IT Support</span>
|
||||
</div>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1544378062-0b74cc8b4713?q=80&w=3648&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="Avatar of gray Weimaraner"></wa-avatar>
|
||||
</div>
|
||||
<div slot="footer" class="wa-grid wa-gap-xs" style="--min-column-size: 10ch;">
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="at"></wa-icon>
|
||||
Email
|
||||
</wa-button>
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="phone"></wa-icon>
|
||||
Phone
|
||||
</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Cards with Footer Actions & Large Image
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-grid" style="--min-column-size: 29ch;">
|
||||
<wa-card with-footer>
|
||||
<div class="wa-stack wa-align-items-center wa-gap-xs">
|
||||
<div class="wa-frame wa-border-radius-circle">
|
||||
<img src="https://images.unsplash.com/photo-1522075469751-3a6694fb2f61?q=80&w=2680&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
<h2 class="wa-heading-m">Scott Summers</h2>
|
||||
<p class="wa-caption-l">DevOps</p>
|
||||
</div>
|
||||
<div slot="footer" class="wa-grid wa-gap-xs" style="--min-column-size: 10ch;">
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="at"></wa-icon>
|
||||
Email
|
||||
</wa-button>
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="phone"></wa-icon>
|
||||
Phone
|
||||
</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card with-footer>
|
||||
<div class="wa-stack wa-align-items-center wa-gap-xs">
|
||||
<div class="wa-frame wa-border-radius-circle">
|
||||
<img src="https://images.unsplash.com/photo-1559188286-a173792c8340?q=80&w=2906&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
<h2 class="wa-heading-m">Kaitlin Moore</h2>
|
||||
<p class="wa-caption-l">Systems Engineer</p>
|
||||
</div>
|
||||
<div slot="footer" class="wa-grid wa-gap-xs" style="--min-column-size: 10ch;">
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="at"></wa-icon>
|
||||
Email
|
||||
</wa-button>
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="phone"></wa-icon>
|
||||
Phone
|
||||
</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card with-footer>
|
||||
<div class="wa-stack wa-align-items-center wa-gap-xs">
|
||||
<div class="wa-frame wa-border-radius-circle">
|
||||
<img src="https://images.unsplash.com/photo-1613428800237-c86372070fab?q=80&w=3017&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
<h2 class="wa-heading-m">Nessa Riley</h2>
|
||||
<p class="wa-caption-l">Cloud Engineer</p>
|
||||
</div>
|
||||
<div slot="footer" class="wa-grid wa-gap-xs" style="--min-column-size: 10ch;">
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="at"></wa-icon>
|
||||
Email
|
||||
</wa-button>
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="phone"></wa-icon>
|
||||
Phone
|
||||
</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card with-footer>
|
||||
<div class="wa-stack wa-align-items-center wa-gap-xs">
|
||||
<div class="wa-frame wa-border-radius-circle">
|
||||
<img src="https://images.unsplash.com/photo-1645288059073-af3e9eb62a29?q=80&w=2936&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
<h2 class="wa-heading-m">Veronica Staley</h2>
|
||||
<p class="wa-caption-l">Machine Learning Engineer</p>
|
||||
</div>
|
||||
<div slot="footer" class="wa-grid wa-gap-xs" style="--min-column-size: 10ch;">
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="at"></wa-icon>
|
||||
Email
|
||||
</wa-button>
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="phone"></wa-icon>
|
||||
Phone
|
||||
</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
</div>
|
||||
```
|
||||
## with Images
|
||||
```html {.example}
|
||||
<div class="wa-grid">
|
||||
<article class="wa-stack">
|
||||
<div class="wa-frame wa-border-radius-l">
|
||||
<img src="https://images.unsplash.com/photo-1522075469751-3a6694fb2f61?q=80&w=2680&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-3xs">
|
||||
<span>Jeff Hanks</span>
|
||||
<span>Product Designer</span>
|
||||
</div>
|
||||
<div class="wa-cluster wa-gap-3xs">
|
||||
<wa-icon-button name="bluesky" family="brands" label="link to Blusky profile"></wa-icon-button>
|
||||
<wa-icon-button name="dribbble" family="brands" label="link to Dribbble profile"></wa-icon-button>
|
||||
</div>
|
||||
</article>
|
||||
<article class="wa-stack">
|
||||
<div class="wa-frame wa-border-radius-l">
|
||||
<img src="https://images.unsplash.com/photo-1674044494331-8db2ecf18d46?q=80&w=3019&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-3xs">
|
||||
<span>Allen Bryant</span>
|
||||
<span>Staff Engineer</span>
|
||||
</div>
|
||||
<div class="wa-cluster wa-gap-3xs">
|
||||
<wa-icon-button name="bluesky" family="brands" label="link to Blusky profile"></wa-icon-button>
|
||||
<wa-icon-button name="dribbble" family="brands" label="link to Dribbble profile"></wa-icon-button>
|
||||
</div>
|
||||
</article>
|
||||
<article class="wa-stack">
|
||||
<div class="wa-frame wa-border-radius-l">
|
||||
<img src="https://images.unsplash.com/photo-1645288059073-af3e9eb62a29?q=80&w=2936&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-3xs">
|
||||
<span>Mariah Greene</span>
|
||||
<span>DevOps</span>
|
||||
</div>
|
||||
<div class="wa-cluster wa-gap-3xs">
|
||||
<wa-icon-button name="bluesky" family="brands" label="link to Blusky profile"></wa-icon-button>
|
||||
<wa-icon-button name="dribbble" family="brands" label="link to Dribbble profile"></wa-icon-button>
|
||||
</div>
|
||||
</article>
|
||||
<article class="wa-stack">
|
||||
<div class="wa-frame wa-border-radius-l">
|
||||
<img src="https://images.unsplash.com/photo-1613428800237-c86372070fab?q=80&w=3017&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-3xs">
|
||||
<span>Beverly Winslow</span>
|
||||
<span>Design Systems Lead</span>
|
||||
</div>
|
||||
<div class="wa-cluster wa-gap-3xs">
|
||||
<wa-icon-button name="bluesky" family="brands" label="link to Blusky profile"></wa-icon-button>
|
||||
<wa-icon-button name="dribbble" family="brands" label="link to Dribbble profile"></wa-icon-button>
|
||||
</div>
|
||||
</article>
|
||||
<article class="wa-stack">
|
||||
<div class="wa-frame wa-border-radius-l">
|
||||
<img src="https://images.unsplash.com/photo-1614807547811-4174d3582092?q=80&w=2932&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-3xs">
|
||||
<span>Eric Masterson</span>
|
||||
<span>Copy Writer</span>
|
||||
</div>
|
||||
<div class="wa-cluster wa-gap-3xs">
|
||||
<wa-icon-button name="bluesky" family="brands" label="link to Blusky profile"></wa-icon-button>
|
||||
<wa-icon-button name="dribbble" family="brands" label="link to Dribbble profile"></wa-icon-button>
|
||||
</div>
|
||||
</article>
|
||||
<article class="wa-stack">
|
||||
<div class="wa-frame wa-border-radius-l">
|
||||
<img src="https://images.unsplash.com/photo-1559188286-a173792c8340?q=80&w=2906&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-3xs">
|
||||
<span>Stephen Coffee</span>
|
||||
<span>Visual Designer</span>
|
||||
</div>
|
||||
<div class="wa-cluster wa-gap-3xs">
|
||||
<wa-icon-button name="bluesky" family="brands" label="link to Blusky profile"></wa-icon-button>
|
||||
<wa-icon-button name="dribbble" family="brands" label="link to Dribbble profile"></wa-icon-button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
```
|
||||
## Linked Cards with Options Menu
|
||||
|
||||
```html{.example}
|
||||
<div class="wa-grid" style="--min-column-size: 25ch">
|
||||
<wa-card>
|
||||
<div class="wa-flank:end">
|
||||
<a href="" class="wa-flank wa-link-plain">
|
||||
<wa-avatar shape="rounded" style="--background-color: var(--wa-color-yellow-80); --text-color: var(--wa-color-yellow-40)">
|
||||
<wa-icon slot="icon" name="pancakes"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-gap-2xs wa-stack">
|
||||
<span class="wa-heading-s">Breakfast</span>
|
||||
<span class="wa-caption-m">28 Items</span>
|
||||
</div>
|
||||
</a>
|
||||
<wa-dropdown>
|
||||
<wa-icon-button id="more-actions-1" slot="trigger" name="ellipsis-vertical" label="More actions"></wa-icon-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Copy link</wa-menu-item>
|
||||
<wa-menu-item>Rename</wa-menu-item>
|
||||
<wa-menu-item>Move to trash</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
<wa-tooltip for="more-actions-1">More actions</wa-tooltip>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card>
|
||||
<div class="wa-flank:end">
|
||||
<a href="" class="wa-flank wa-link-plain">
|
||||
<wa-avatar shape="rounded" style="--background-color: var(--wa-color-orange-80); --text-color: var(--wa-color-orange-40)">
|
||||
<wa-icon slot="icon" name="burger-cheese"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-gap-2xs wa-stack">
|
||||
<span class="wa-heading-s">Lunch + Dinner</span>
|
||||
<span class="wa-caption-m">40 Items</span>
|
||||
</div>
|
||||
</a>
|
||||
<wa-dropdown>
|
||||
<wa-icon-button id="more-actions-2" slot="trigger" name="ellipsis-vertical" label="More actions"></wa-icon-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Copy link</wa-menu-item>
|
||||
<wa-menu-item>Rename</wa-menu-item>
|
||||
<wa-menu-item>Move to trash</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
<wa-tooltip for="more-actions-2">More actions</wa-tooltip>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card>
|
||||
<div class="wa-flank:end">
|
||||
<a href="" class="wa-flank wa-link-plain">
|
||||
<wa-avatar shape="rounded" style="--background-color: var(--wa-color-indigo-80); --text-color: var(--wa-color-indigo-40)">
|
||||
<wa-icon slot="icon" name="martini-glass-citrus"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-gap-2xs wa-stack">
|
||||
<span class="wa-heading-s">Beverages</span>
|
||||
<span class="wa-caption-m">19 Items</span>
|
||||
</div>
|
||||
</a>
|
||||
<wa-dropdown>
|
||||
<wa-icon-button id="more-actions-3" slot="trigger" name="ellipsis-vertical" label="More actions"></wa-icon-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Copy link</wa-menu-item>
|
||||
<wa-menu-item>Rename</wa-menu-item>
|
||||
<wa-menu-item>Move to trash</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
<wa-tooltip for="more-actions-3">More actions</wa-tooltip>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card>
|
||||
<div class="wa-flank:end">
|
||||
<a href="" class="wa-flank wa-link-plain">
|
||||
<wa-avatar shape="rounded" style="--background-color: var(--wa-color-pink-80); --text-color: var(--wa-color-pink-40)">
|
||||
<wa-icon slot="icon" name="cake-slice"></wa-icon>
|
||||
</wa-avatar>
|
||||
<div class="wa-gap-2xs wa-stack">
|
||||
<span class="wa-heading-s">Dessert</span>
|
||||
<span class="wa-caption-m">11 Items</span>
|
||||
</div>
|
||||
</a>
|
||||
<wa-dropdown>
|
||||
<wa-icon-button id="more-actions-4" slot="trigger" name="ellipsis-vertical" label="More actions"></wa-icon-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Copy link</wa-menu-item>
|
||||
<wa-menu-item>Rename</wa-menu-item>
|
||||
<wa-menu-item>Move to trash</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
<wa-tooltip for="more-actions-4">More actions</wa-tooltip>
|
||||
</div>
|
||||
</wa-card>
|
||||
</div>
|
||||
```
|
||||
## Kanban
|
||||
```html {.example}
|
||||
<div>
|
||||
<h2>Project #487</h2>
|
||||
<div class="wa-grid wa-gap-2xl">
|
||||
<div class="wa-stack">
|
||||
<div class="wa-cluster wa-gap-s"><span>Draft</span> <wa-badge appearance="filled outlined" variant="neutral">1</wa-badge></div>
|
||||
|
||||
|
||||
|
||||
<wa-card>
|
||||
<div class="wa-flank:end">
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<span class="wa-heading-s">Unit Testing</span>
|
||||
<wa-dropdown>
|
||||
<wa-icon-button id="task-action-4" slot="trigger" name="ellipsis" label="More actions"></wa-icon-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Copy link</wa-menu-item>
|
||||
<wa-menu-item>Rename</wa-menu-item>
|
||||
<wa-menu-item>Move to trash</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
<wa-tooltip for="task-action-4">More actions</wa-tooltip>
|
||||
</div>
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<wa-badge appearance="outlined" pill>DevOps</wa-badge> <wa-badge variant="neutral" appearance="outlined" pill>Priority: Low</wa-badge>
|
||||
</div>
|
||||
</div>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1559188286-a173792c8340?q=80&w=2906&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
|
||||
label="profile image"></wa-avatar>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-button appearance="plain">
|
||||
<wa-icon name="plus"></wa-icon>
|
||||
Add Task
|
||||
</wa-button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="wa-stack">
|
||||
<div class="wa-cluster wa-gap-s"><span>In Progress</span> <wa-badge appearance="filled outlined" variant="neutral">2</wa-badge></div>
|
||||
|
||||
<wa-card>
|
||||
<div class="wa-flank:end">
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<span class="wa-heading-s">UX Audit</span>
|
||||
<wa-dropdown>
|
||||
<wa-icon-button id="task-action-2" slot="trigger" name="ellipsis" label="More actions"></wa-icon-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Copy link</wa-menu-item>
|
||||
<wa-menu-item>Rename</wa-menu-item>
|
||||
<wa-menu-item>Move to trash</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
<wa-tooltip for="task-action-2">More actions</wa-tooltip>
|
||||
</div>
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<wa-badge appearance="outlined" pill>Design</wa-badge> <wa-badge variant="warning" appearance="outlined" pill>Priority: Medium</wa-badge>
|
||||
</div>
|
||||
</div>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1613428800237-c86372070fab?q=80&w=3017&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
|
||||
label="profile image"></wa-avatar>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card>
|
||||
<div class="wa-flank:end">
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<span class="wa-heading-s">Visual Testing</span>
|
||||
<wa-dropdown>
|
||||
<wa-icon-button id="task-action-3" slot="trigger" name="ellipsis" label="More actions"></wa-icon-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Copy link</wa-menu-item>
|
||||
<wa-menu-item>Rename</wa-menu-item>
|
||||
<wa-menu-item>Move to trash</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
<wa-tooltip for="task-action-3">More actions</wa-tooltip>
|
||||
</div>
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<wa-badge appearance="outlined" pill>Design</wa-badge> <wa-badge variant="danger" appearance="outlined" pill>Priority: High</wa-badge>
|
||||
</div>
|
||||
</div>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1614807547811-4174d3582092?q=80&w=2932&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="profile image"></wa-avatar>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-button appearance="plain">
|
||||
<wa-icon name="plus"></wa-icon>
|
||||
Add Task
|
||||
</wa-button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="wa-stack">
|
||||
<div class="wa-cluster wa-gap-s"><span>Ready for Review</span> <wa-badge appearance="filled outlined" variant="neutral">1</wa-badge></div>
|
||||
<wa-card>
|
||||
<div class="wa-flank:end">
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<span class="wa-heading-s">Deploy Bug Fixes</span>
|
||||
<wa-dropdown>
|
||||
<wa-icon-button id="task-action-1" slot="trigger" name="ellipsis" label="More actions"></wa-icon-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Copy link</wa-menu-item>
|
||||
<wa-menu-item>Rename</wa-menu-item>
|
||||
<wa-menu-item>Move to trash</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
<wa-tooltip for="task-action-1">More actions</wa-tooltip>
|
||||
</div>
|
||||
<div class="wa-cluster wa-gap-2xs">
|
||||
<wa-badge appearance="outlined" pill>Development</wa-badge> <wa-badge variant="warning" appearance="outlined" pill>Priority: Medium</wa-badge>
|
||||
</div>
|
||||
</div>
|
||||
<wa-avatar initials="KK" label="Avatar with initials: KK"></wa-avatar>
|
||||
</div>
|
||||
</wa-card>
|
||||
|
||||
|
||||
<wa-button appearance="plain">
|
||||
<wa-icon name="plus"></wa-icon>
|
||||
Add Task
|
||||
</wa-button>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
9
docs/docs/patterns/app/index.njk
Normal file
9
docs/docs/patterns/app/index.njk
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: App
|
||||
description: Pre-built action panels, data displays, and more ready to drop into your web app.
|
||||
parent: patterns
|
||||
layout: overview
|
||||
override:tags: []
|
||||
listChildren: true
|
||||
isPro: false
|
||||
---
|
||||
266
docs/docs/patterns/app/leaderboard.md
Normal file
266
docs/docs/patterns/app/leaderboard.md
Normal file
@@ -0,0 +1,266 @@
|
||||
---
|
||||
title: Leaderboard
|
||||
description: 'Engage and motivate users by highlighting top performers, scores, and achievements.'
|
||||
isPro: true
|
||||
---
|
||||
|
||||
## Simple
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-stack">
|
||||
<h3>Daily Crossword</h3>
|
||||
<div class="wa-grid">
|
||||
<wa-callout variant="warning" appearance="filled">
|
||||
<wa-icon slot="icon" name="timer"></wa-icon>
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<span class="wa-heading-l">11h 54m 52s</span>
|
||||
<span class="wa-caption-m">until play ends</span>
|
||||
</div>
|
||||
</wa-callout>
|
||||
<wa-callout variant="neutral" appearance="filled">
|
||||
<wa-icon slot="icon" name="user-group"></wa-icon>
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<span class="wa-heading-l">304</span>
|
||||
<span class="wa-caption-m">players on this leaderboard</span>
|
||||
</div>
|
||||
</wa-callout>
|
||||
</div>
|
||||
<wa-card>
|
||||
<div class="wa-stack">
|
||||
<ol class="wa-stack">
|
||||
<li class="wa-flank">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="1" fixed-width></wa-icon>
|
||||
<wa-avatar>
|
||||
<wa-icon slot="icon" name="hat-wizard"></wa-icon>
|
||||
</wa-avatar>
|
||||
</div>
|
||||
<div class="wa-split wa-gap-xs">
|
||||
<span>wordwiz</span>
|
||||
<small class="wa-caption-l">00:01:41</small>
|
||||
</div>
|
||||
</li>
|
||||
<wa-divider></wa-divider>
|
||||
<li class="wa-flank">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="2" fixed-width></wa-icon>
|
||||
<wa-avatar initials="A"></wa-avatar>
|
||||
</div>
|
||||
<div class="wa-split wa-gap-xs">
|
||||
<span>acrossNdown</span>
|
||||
<small class="wa-caption-l">00:01:58</small>
|
||||
</div>
|
||||
</li>
|
||||
<wa-divider></wa-divider>
|
||||
<li class="wa-flank">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="3" fixed-width></wa-icon>
|
||||
<wa-avatar initials="X"></wa-avatar>
|
||||
</div>
|
||||
<div class="wa-split wa-gap-xs">
|
||||
<span>XwordChamp</span>
|
||||
<small class="wa-caption-l">00:02:14</small>
|
||||
</div>
|
||||
</li>
|
||||
<wa-divider></wa-divider>
|
||||
<li class="wa-flank">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="4" fixed-width></wa-icon>
|
||||
<wa-avatar>
|
||||
<wa-icon slot="icon" name="chess-knight"></wa-icon>
|
||||
</wa-avatar>
|
||||
</div>
|
||||
<div class="wa-split wa-gap-xs">
|
||||
<span>puzzlepoet</span>
|
||||
<small class="wa-caption-l">00:02:16</small>
|
||||
</div>
|
||||
</li>
|
||||
<wa-divider></wa-divider>
|
||||
<li class="wa-flank">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="5" fixed-width></wa-icon>
|
||||
<wa-avatar initials="R"></wa-avatar>
|
||||
</div>
|
||||
<div class="wa-split wa-gap-xs">
|
||||
<span>RiddleMeThis</span>
|
||||
<small class="wa-caption-l">00:02:34</small>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<a href="" class="wa-cluster wa-gap-xs wa-caption-m">
|
||||
<span>View all standings</span>
|
||||
<wa-icon name="arrow-right"></wa-icon>
|
||||
</a>
|
||||
</div>
|
||||
</wa-card>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Two Column
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-stack">
|
||||
<h3>Collective Activity for Yesterday</h3>
|
||||
<div class="wa-grid">
|
||||
<wa-callout variant="neutral" appearance="filled">
|
||||
<wa-icon slot="icon" name="book"></wa-icon>
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<h4 class="wa-heading-xs">Items Studied</h4>
|
||||
<div class="wa-heading-2xl">482,813</div>
|
||||
</div>
|
||||
</wa-callout>
|
||||
<wa-callout variant="brand" appearance="filled">
|
||||
<wa-icon slot="icon" name="diploma"></wa-icon>
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<h4 class="wa-heading-xs">Items Mastered</h4>
|
||||
<div class="wa-heading-2xl">67,106</div>
|
||||
</div>
|
||||
</wa-callout>
|
||||
<wa-callout variant="success" appearance="filled">
|
||||
<wa-icon slot="icon" name="wand-sparkles"></wa-icon>
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<h4 class="wa-heading-xs">Items Created</h4>
|
||||
<div class="wa-heading-2xl">2,080</div>
|
||||
</div>
|
||||
</wa-callout>
|
||||
</div>
|
||||
<div class="wa-grid">
|
||||
<wa-card>
|
||||
<div slot="header" class="wa-flank wa-gap-xl">
|
||||
<wa-icon name="graduation-cap" class="wa-heading-xl"></wa-icon>
|
||||
<span class="wa-gap-2xs wa-stack">
|
||||
<h4>Study Leaders</h4>
|
||||
<span class="wa-caption-m">Items mastered in the last 7 days</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="wa-stack">
|
||||
<ol class="wa-stack">
|
||||
<li class="wa-flank">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="1" fixed-width></wa-icon>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1620428268482-cf1851a36764?q=80&w=3418&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" shape="rounded"></wa-avatar>
|
||||
</div>
|
||||
<div class="wa-split wa-gap-xs">
|
||||
<span>mitsuwo</span>
|
||||
<small class="wa-caption-l">2,753</small>
|
||||
</div>
|
||||
</li>
|
||||
<wa-divider></wa-divider>
|
||||
<li class="wa-flank">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="2" fixed-width></wa-icon>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1639628735078-ed2f038a193e?q=80&w=3348&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" shape="rounded"></wa-avatar>
|
||||
</div>
|
||||
<div class="wa-split wa-gap-xs">
|
||||
<span>knowledgeninja</span>
|
||||
<small class="wa-caption-l">2,298</small>
|
||||
</div>
|
||||
</li>
|
||||
<wa-divider></wa-divider>
|
||||
<li class="wa-flank">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="3" fixed-width></wa-icon>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1638803040283-7a5ffd48dad5?q=80&w=2592&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" shape="rounded"></wa-avatar>
|
||||
</div>
|
||||
<div class="wa-split wa-gap-xs">
|
||||
<span>NxtLvl</span>
|
||||
<small class="wa-caption-l">2,008</small>
|
||||
</div>
|
||||
</li>
|
||||
<wa-divider></wa-divider>
|
||||
<li class="wa-flank">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="4" fixed-width></wa-icon>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1630549316063-7ae02749d2cc?q=80&w=3540&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" shape="rounded"></wa-avatar>
|
||||
</div>
|
||||
<div class="wa-split wa-gap-xs">
|
||||
<span>brainiac</span>
|
||||
<small class="wa-caption-l">1,954</small>
|
||||
</div>
|
||||
</li>
|
||||
<wa-divider></wa-divider>
|
||||
<li class="wa-flank">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="5" fixed-width></wa-icon>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1582845512747-e42001c95638?q=80&w=3540&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" shape="rounded"></wa-avatar>
|
||||
</div>
|
||||
<div class="wa-split wa-gap-xs">
|
||||
<span>eduexplorer</span>
|
||||
<small class="wa-caption-l">1,897</small>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card>
|
||||
<div slot="header" class="wa-flank wa-gap-xl">
|
||||
<wa-icon name="hat-wizard" class="wa-heading-xl"></wa-icon>
|
||||
<span class="wa-gap-2xs wa-stack">
|
||||
<h4>Creation Leaders</h4>
|
||||
<span class="wa-caption-m">Items created in the last 7 days</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="wa-stack">
|
||||
<ol class="wa-stack">
|
||||
<li class="wa-flank">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="1" fixed-width></wa-icon>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1630549316063-7ae02749d2cc?q=80&w=3540&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" shape="rounded"></wa-avatar>
|
||||
</div>
|
||||
<div class="wa-split wa-gap-xs">
|
||||
<span>brainiac</span>
|
||||
<small class="wa-caption-l">134</small>
|
||||
</div>
|
||||
</li>
|
||||
<wa-divider></wa-divider>
|
||||
<li class="wa-flank">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="2" fixed-width></wa-icon>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1546776230-bb86256870ce?q=80&w=3368&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" shape="rounded"></wa-avatar>
|
||||
</div>
|
||||
<div class="wa-split wa-gap-xs">
|
||||
<span>LessonLegend</span>
|
||||
<small class="wa-caption-l">115</small>
|
||||
</div>
|
||||
</li>
|
||||
<wa-divider></wa-divider>
|
||||
<li class="wa-flank">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="3" fixed-width></wa-icon>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1620428268482-cf1851a36764?q=80&w=3418&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" shape="rounded"></wa-avatar>
|
||||
</div>
|
||||
<div class="wa-split wa-gap-xs">
|
||||
<span>mitsuwo</span>
|
||||
<small class="wa-caption-l">98</small>
|
||||
</div>
|
||||
</li>
|
||||
<wa-divider></wa-divider>
|
||||
<li class="wa-flank">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="4" fixed-width></wa-icon>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1586374579358-9d19d632b6df?q=80&w=3387&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" shape="rounded"></wa-avatar>
|
||||
</div>
|
||||
<div class="wa-split wa-gap-xs">
|
||||
<span>wiswiz</span>
|
||||
<small class="wa-caption-l">79</small>
|
||||
</div>
|
||||
</li>
|
||||
<wa-divider></wa-divider>
|
||||
<li class="wa-flank">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="5" fixed-width></wa-icon>
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1639628735078-ed2f038a193e?q=80&w=3348&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" shape="rounded"></wa-avatar>
|
||||
</div>
|
||||
<div class="wa-split wa-gap-xs">
|
||||
<span>knowledgeninja</span>
|
||||
<small class="wa-caption-l">77</small>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</wa-card>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
52
docs/docs/patterns/app/pagination.md
Normal file
52
docs/docs/patterns/app/pagination.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: Pagination
|
||||
description: 'Improve navigation and performance by breaking long lists or content into manageable pages.'
|
||||
isPro: true
|
||||
---
|
||||
|
||||
## Simple
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-stack">
|
||||
<div class="wa-placeholder"></div>
|
||||
<wa-divider></wa-divider>
|
||||
<div class="wa-split">
|
||||
<span class="wa-caption-l">Showing 1 to 10 of 50 Results</span>
|
||||
<div class="wa-cluster wa-gap-xs">
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="prefix" name="chevron-left"></wa-icon>
|
||||
Prev
|
||||
</wa-button>
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon slot="suffix" name="chevron-right"></wa-icon>
|
||||
Next
|
||||
</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## With Button Group
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-stack">
|
||||
<div class="wa-placeholder"></div>
|
||||
<wa-divider></wa-divider>
|
||||
<div class="wa-split">
|
||||
<span class="wa-caption-l">Showing 1 to 10 of 50 Results</span>
|
||||
<wa-button-group orientation="horizontal">
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon name="chevron-left"></wa-icon>
|
||||
</wa-button>
|
||||
<wa-button appearance="accent" variant="brand">1</wa-button>
|
||||
<wa-button appearance="outlined">2</wa-button>
|
||||
<wa-button appearance="outlined">3</wa-button>
|
||||
<wa-button appearance="outlined" disabled>...</wa-button>
|
||||
<wa-button appearance="outlined">10</wa-button>
|
||||
<wa-button appearance="outlined">
|
||||
<wa-icon name="chevron-right"></wa-icon>
|
||||
</wa-button>
|
||||
</wa-button-group>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
229
docs/docs/patterns/app/permissions.md
Normal file
229
docs/docs/patterns/app/permissions.md
Normal file
@@ -0,0 +1,229 @@
|
||||
---
|
||||
title: Permissions
|
||||
description: 'Permission patterns provide or restrict access to users.'
|
||||
isPro: true
|
||||
---
|
||||
|
||||
## With Form Inputs
|
||||
```html{.example}
|
||||
<wa-card with-header style="max-width: 72ch; margin: 0 auto;">
|
||||
<div slot="header" class="wa-split">
|
||||
<h2 class="wa-heading-m">Invite Team Members</h2>
|
||||
<wa-icon name="close"></wa-icon>
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-2xl">
|
||||
<div class="wa-align-items-end wa-flank:end wa-gap-2xs">
|
||||
<wa-input label="Email" placeholder="contact@example.com"></wa-input>
|
||||
<wa-button variant="success">Send Invite</wa-button>
|
||||
</div>
|
||||
|
||||
<div class="wa-stack">
|
||||
<span class="wa-heading-s">Project Members</span>
|
||||
<div class="wa-stack wa-gap-xl">
|
||||
<div class="wa-flank">
|
||||
<wa-avatar label="User avatar" image="https://images.unsplash.com/photo-1580489944761-15a19d654956?q=80&w=1961&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"></wa-avatar>
|
||||
<div class="wa-split">
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<span class="wa-heading-xs">Jessica Jones</span>
|
||||
<span class="wa-caption-m" style="word-break: break-word">jessica.jones@example.com</span>
|
||||
</div>
|
||||
<wa-select value="owner">
|
||||
<wa-option value="owner">Owner</wa-option>
|
||||
<wa-option value="admin">Admin</wa-option>
|
||||
<wa-option value="can-edit">Can Edit</wa-option>
|
||||
<wa-option value="view-only">View Only</wa-option>
|
||||
</wa-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wa-flank">
|
||||
<wa-avatar label="User avatar" image="https://images.unsplash.com/photo-1566492031773-4f4e44671857?q=80&w=2487&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"></wa-avatar>
|
||||
<div class="wa-split">
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<span class="wa-heading-xs">Foggy Nelson</span>
|
||||
<span class="wa-caption-m" style="word-break: break-word">foggy.nelson@example.com</span>
|
||||
</div>
|
||||
<wa-select value="admin">
|
||||
<wa-option value="owner">Owner</wa-option>
|
||||
<wa-option value="admin">Admin</wa-option>
|
||||
<wa-option value="can-edit">Can Edit</wa-option>
|
||||
<wa-option value="view-only">View Only</wa-option>
|
||||
</wa-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wa-flank">
|
||||
<wa-avatar label="User avatar" image="https://images.unsplash.com/photo-1494790108377-be9c29b29330?q=80&w=2487&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"></wa-avatar>
|
||||
<div class="wa-split">
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<span class="wa-heading-xs">Karen Page</span>
|
||||
<span class="wa-caption-m" style="word-break: break-word">karen.page@example.com</span>
|
||||
</div>
|
||||
<wa-select value="can-edit">
|
||||
<wa-option value="owner">Owner</wa-option>
|
||||
<wa-option value="admin">Admin</wa-option>
|
||||
<wa-option value="can-edit">Can Edit</wa-option>
|
||||
<wa-option value="view-only">View Only</wa-option>
|
||||
</wa-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wa-flank">
|
||||
<wa-avatar label="User avatar" image="https://images.unsplash.com/photo-1599566150163-29194dcaad36?q=80&w=2487&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"></wa-avatar>
|
||||
<div class="wa-split">
|
||||
<div class="wa-stack wa-gap-0">
|
||||
<span class="wa-heading-xs">Matt Murdock</span>
|
||||
<span class="wa-caption-m" style="word-break: break-word">matt.murdock@example.com</span>
|
||||
</div>
|
||||
<wa-select value="view-only">
|
||||
<wa-option value="owner">Owner</wa-option>
|
||||
<wa-option value="admin">Admin</wa-option>
|
||||
<wa-option value="can-edit">Can Edit</wa-option>
|
||||
<wa-option value="view-only">View Only</wa-option>
|
||||
</wa-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wa-align-items-end wa-flank:end wa-gap-2xs">
|
||||
<wa-input label="Share Link" value="https://sharelink3435re.com" disabled></wa-input>
|
||||
<wa-button variant="brand" appearance="filled outlined">
|
||||
<wa-icon slot="prefix" name="link" variant="solid"></wa-icon>
|
||||
Copy Link
|
||||
</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
```
|
||||
|
||||
## Link Settings
|
||||
```html {.example}
|
||||
<wa-card style="max-width: 45ch; margin: 0 auto;">
|
||||
<div class="wa-stack">
|
||||
<h2 class="wa-heading-m">Manage Link</h2>
|
||||
<wa-input label="Expiration Date" type="date"></wa-input>
|
||||
<wa-radio-group
|
||||
label="Share Limit"
|
||||
orientation="horizontal"
|
||||
name="share-limit"
|
||||
value="0"
|
||||
>
|
||||
<wa-radio-button value="0">None</wa-radio-button>
|
||||
<wa-radio-button value="5">5</wa-radio-button>
|
||||
<wa-radio-button value="10">10</wa-radio-button>
|
||||
<wa-radio-button value="50">50</wa-radio-button>
|
||||
<wa-radio-button value="100">100</wa-radio-button>
|
||||
</wa-radio-group>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-switch hint="Members are removed after logging out." checked>Temporary Access</wa-switch>
|
||||
<div class="wa-cluster wa-gap-xs" style="justify-content: flex-end">
|
||||
<wa-button size="small" appearance="outlined" pill>Cancel</wa-button>
|
||||
<wa-button size="small" variant="brand" pill>Generate</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</wa-card>
|
||||
```
|
||||
|
||||
## Role Settings
|
||||
```html {.example}
|
||||
<div style="max-width: 78ch; margin: 0 auto;">
|
||||
<h2>Settings</h2>
|
||||
<p>Update settings for this server.</p>
|
||||
<wa-tab-group>
|
||||
<wa-tab panel="general">
|
||||
<div class="wa-cluster">
|
||||
<wa-icon name="user"></wa-icon>
|
||||
<span>User Permissions</span>
|
||||
</div>
|
||||
</wa-tab>
|
||||
<wa-tab-panel name="general">
|
||||
<wa-card>
|
||||
<div class="wa-flank">
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1741289308283-feba56f857cc?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTl8fGJlYXJkfGVufDB8MnwwfHx8Mg%3D%3D" alt="image avatar"></wa-avatar>
|
||||
<div class="wa-split">
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<span class="wa-heading-s">Kris Kringle</span>
|
||||
<span class="wa-caption-m">Admin</span>
|
||||
</div>
|
||||
<wa-button appearance="plain">
|
||||
<wa-icon slot="prefix" name="edit"></wa-icon>
|
||||
Edit Profile
|
||||
</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Role</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="wa-flank">
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1579824894326-77ec5aaf8703?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NDJ8fHJlaW5kZWVyfGVufDB8MnwwfHx8Mg%3D%3D"></wa-avatar>
|
||||
<span class="wa-caption-m">Dasher</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<wa-select value="moderator">
|
||||
<wa-option value="moderator">Moderator</wa-option>
|
||||
<wa-option value="contributor">Contributor</wa-option>
|
||||
<wa-option value="reader">Reader</wa-option>
|
||||
</wa-select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="wa-flank">
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1691065686144-916ff29d1b4f?q=80&w=2666&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"></wa-avatar>
|
||||
<span class="wa-caption-m">Dancer</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<wa-select value="moderator">
|
||||
<wa-option value="moderator">Moderator</wa-option>
|
||||
<wa-option value="contributor">Contributor</wa-option>
|
||||
<wa-option value="reader">Reader</wa-option>
|
||||
</wa-select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="wa-flank">
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1616742618872-9e8a890d90b2?q=80&w=2712&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"></wa-avatar>
|
||||
<span class="wa-caption-m">Prancer</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<wa-select value="contributor">
|
||||
<wa-option value="moderator">Moderator</wa-option>
|
||||
<wa-option value="contributor">Contributor</wa-option>
|
||||
<wa-option value="reader">Reader</wa-option>
|
||||
</wa-select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="wa-flank">
|
||||
<wa-avatar image="https://images.unsplash.com/photo-1728290403857-1b7909db2baa?q=80&w=2680&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"></wa-avatar>
|
||||
<span class="wa-caption-m">Vixen</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<wa-select value="reader">
|
||||
<wa-option value="moderator">Moderator</wa-option>
|
||||
<wa-option value="contributor">Contributor</wa-option>
|
||||
<wa-option value="reader">Reader</wa-option>
|
||||
</wa-select>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</wa-tab-panel>
|
||||
|
||||
</wa-tab-group>
|
||||
</div>
|
||||
```
|
||||
168
docs/docs/patterns/app/pricing.md
Normal file
168
docs/docs/patterns/app/pricing.md
Normal file
@@ -0,0 +1,168 @@
|
||||
---
|
||||
title: Pricing
|
||||
description: 'Help users make informed purchasing decisions with clear, structured pricing.'
|
||||
isPro: true
|
||||
---
|
||||
|
||||
## Three Tiers
|
||||
|
||||
```html{.example}
|
||||
<div class="wa-grid">
|
||||
<wa-card>
|
||||
<div class="wa-stack">
|
||||
<div class="wa-cluster wa-heading-l">
|
||||
<wa-icon name="apple-core"></wa-icon>
|
||||
<h3>Lite</h3>
|
||||
</div>
|
||||
<span class="wa-flank wa-align-items-baseline wa-gap-2xs">
|
||||
<span class="wa-heading-2xl">$60</span>
|
||||
<span class="wa-caption-l">per year</span>
|
||||
</span>
|
||||
<p class="wa-caption-l">An online-only plan for web-based projects.</p>
|
||||
<wa-button variant="brand" appearance="outlined">Get Lite</wa-button>
|
||||
</div>
|
||||
<div slot="footer" class="wa-stack">
|
||||
<h4 class="wa-heading-s">What You Get</h4>
|
||||
<div class="wa-stack">
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="user" fixed-width></wa-icon>
|
||||
<span class="wa-caption-m">1 user</span>
|
||||
</div>
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="suitcase" fixed-width></wa-icon>
|
||||
<span class="wa-caption-m">2 custom kits</span>
|
||||
</div>
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="chart-simple" fixed-width></wa-icon>
|
||||
<span class="wa-caption-m">Up to 100k pageviews</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card>
|
||||
<div class="wa-stack">
|
||||
<div class="wa-split">
|
||||
<div class="wa-cluster wa-heading-l">
|
||||
<wa-icon name="apple-whole"></wa-icon>
|
||||
<h3>Pro</h3>
|
||||
</div>
|
||||
<wa-badge>Most Popular</wa-badge>
|
||||
</div>
|
||||
<span class="wa-flank wa-align-items-baseline wa-gap-2xs">
|
||||
<span class="wa-heading-2xl">$120</span>
|
||||
<span class="wa-caption-l">per year</span>
|
||||
</span>
|
||||
<p class="wa-caption-l">A great all-around plan for online or desktop use.</p>
|
||||
<wa-button variant="brand">Get Pro</wa-button>
|
||||
</div>
|
||||
<div slot="footer" class="wa-stack">
|
||||
<h4 class="wa-heading-s">What You Get</h4>
|
||||
<div class="wa-stack">
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="user" fixed-width></wa-icon>
|
||||
<span class="wa-caption-m">5 users</span>
|
||||
</div>
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="suitcase" fixed-width></wa-icon>
|
||||
<span class="wa-caption-m">20 custom kits</span>
|
||||
</div>
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="chart-simple" fixed-width></wa-icon>
|
||||
<span class="wa-caption-m">Up to 1M pageviews</span>
|
||||
</div>
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="arrow-down-to-line" fixed-width></wa-icon>
|
||||
<span class="wa-caption-m">Kit downloads</span>
|
||||
</div>
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="cloud-plus" fixed-width></wa-icon>
|
||||
<span class="wa-caption-m">Cloud hosting</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card>
|
||||
<div class="wa-stack">
|
||||
<div class="wa-cluster wa-heading-l">
|
||||
<wa-icon name="crate-apple"></wa-icon>
|
||||
<h3>Max</h3>
|
||||
</div>
|
||||
<span class="wa-flank wa-align-items-baseline wa-gap-2xs">
|
||||
<span class="wa-heading-2xl">$600</span>
|
||||
<span class="wa-caption-l">per year</span>
|
||||
</span>
|
||||
<p class="wa-caption-l">Our biggest plan with more of everything.</p>
|
||||
<wa-button variant="brand" appearance="outlined">Get Max</wa-button>
|
||||
</div>
|
||||
<div slot="footer" class="wa-stack">
|
||||
<h4 class="wa-heading-s">What You Get</h4>
|
||||
<div class="wa-stack">
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="user" fixed-width></wa-icon>
|
||||
<span class="wa-caption-m">50 users</span>
|
||||
</div>
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="suitcase" fixed-width></wa-icon>
|
||||
<span class="wa-caption-m">Unlimited custom kits</span>
|
||||
</div>
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="chart-simple" fixed-width></wa-icon>
|
||||
<span class="wa-caption-m">Up to 10M pageviews</span>
|
||||
</div>
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="arrow-down-to-line" fixed-width></wa-icon>
|
||||
<span class="wa-caption-m">Kit downloads</span>
|
||||
</div>
|
||||
<div class="wa-flank">
|
||||
<wa-icon name="cloud-plus" fixed-width></wa-icon>
|
||||
<span class="wa-caption-m">Cloud hosting</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Single Tier
|
||||
|
||||
```html {.example}
|
||||
<wa-card>
|
||||
<div class="wa-grid">
|
||||
<div class="wa-stack">
|
||||
<h3 class="wa-heading-l">Lifetime Membership</h3>
|
||||
<p>Learn at your own pace with expert-led content, exclusive resources, and a community of like-minded learners.</p>
|
||||
<wa-divider></wa-divider>
|
||||
<h4 class="wa-heading-s">Membership Includes:</h4>
|
||||
<div class="wa-grid">
|
||||
<div class="wa-flank wa-gap-xs">
|
||||
<wa-icon name="check"></wa-icon>
|
||||
<span class="wa-caption-m">Private forum access</span>
|
||||
</div>
|
||||
<div class="wa-flank wa-gap-xs">
|
||||
<wa-icon name="check"></wa-icon>
|
||||
<span class="wa-caption-m">Priority admission to events</span>
|
||||
</div>
|
||||
<div class="wa-flank wa-gap-xs">
|
||||
<wa-icon name="check"></wa-icon>
|
||||
<span class="wa-caption-m">Yearly skill assessment</span>
|
||||
</div>
|
||||
<div class="wa-flank wa-gap-xs">
|
||||
<wa-icon name="check"></wa-icon>
|
||||
<span class="wa-caption-m">Members-only swag</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<wa-callout variant="neutral" appearance="filled">
|
||||
<div class="wa-stack wa-align-items-center" style="justify-content: center">
|
||||
<h4 class="wa-heading-s">Pay Once, Own it Forever</h4>
|
||||
<div class="wa-cluster wa-align-items-baseline wa-gap-2xs">
|
||||
<span class="wa-heading-3xl">$459</span>
|
||||
<span>USD</span>
|
||||
</div>
|
||||
<wa-button variant="brand" style="width: 100%">Get Access</wa-button>
|
||||
<small class="wa-caption-m">30-day money back guarantee</small>
|
||||
</div>
|
||||
</wa-callout>
|
||||
</div>
|
||||
</wa-card>
|
||||
```
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: Blog
|
||||
description: TODO
|
||||
unlisted: true
|
||||
isPro: true
|
||||
---
|
||||
|
||||
TODO Page Description
|
||||
@@ -366,4 +368,4 @@ TODO Page Description
|
||||
}
|
||||
|
||||
</style>
|
||||
```
|
||||
```
|
||||
@@ -1,8 +1,10 @@
|
||||
---
|
||||
title: Business
|
||||
description: TODO
|
||||
unlisted: true
|
||||
isPro: true
|
||||
---
|
||||
|
||||
TODO Page Description
|
||||
|
||||
## Examples
|
||||
## Examples
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Category Filter
|
||||
description: TODO
|
||||
parent: ecommerce
|
||||
tags: e-commerce
|
||||
---
|
||||
|
||||
TODO Page Description
|
||||
|
||||
## With inline actions and expandable sidebar filters
|
||||
```html{.example}
|
||||
|
||||
```
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
title: Category Preview
|
||||
description: TODO
|
||||
parent: ecommerce
|
||||
tags: e-commerce
|
||||
---
|
||||
|
||||
TODO Page Description
|
||||
|
||||
## Three Column (WIP)
|
||||
|
||||
```
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
title: Order History
|
||||
description: TODO
|
||||
parent: ecommerce
|
||||
tags: e-commerce
|
||||
---
|
||||
|
||||
TODO Page Description
|
||||
|
||||
## Invoice panels
|
||||
|
||||
```html{.example}
|
||||
|
||||
```
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Product List
|
||||
description: TODO
|
||||
parent: ecommerce
|
||||
tags: e-commerce
|
||||
---
|
||||
|
||||
TODO Page Description
|
||||
|
||||
## With split image
|
||||
```html{.example}
|
||||
|
||||
```
|
||||
@@ -1,129 +0,0 @@
|
||||
---
|
||||
title: Product Detail
|
||||
description: TODO
|
||||
parent: ecommerce
|
||||
tags: e-commerce
|
||||
---
|
||||
|
||||
TODO Page Description
|
||||
|
||||
## With color and size selector
|
||||
```html{.example}
|
||||
<div class="with-inline-price">
|
||||
<wa-card with-header>
|
||||
<div class="card-header" slot="header">
|
||||
<span class="card-title">Graphic Tank</span>
|
||||
<wa-icon-button name="close" label="close-modal"></wa-icon-button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<img style="border-radius: var(--border-radius)" src="/assets/images/patterns/gervyn-louis-IS03ajI00Fc-unsplash.jpg" />
|
||||
<form class="detail">
|
||||
<span class="price">$32</span>
|
||||
<span class="rating"><wa-rating></wa-rating><a style="margin-left: .5rem; " href="*">36 Reviews</a></span>
|
||||
<wa-radio-group style="margin-bottom: 1rem;" label="Select an option" name="a" value="1">
|
||||
<wa-radio-button value="Black">Black</wa-radio-button>
|
||||
<wa-radio-button value="White">White</wa-radio-button>
|
||||
<wa-radio-button value="Gray">Gray</wa-radio-button>
|
||||
</wa-radio-group>
|
||||
<wa-select label="Sizes" placeholder="select size">
|
||||
<wa-option value="option-1">Option 1</wa-option>
|
||||
<wa-option value="option-2">Option 2</wa-option>
|
||||
<wa-option value="option-3">Option 3</wa-option>
|
||||
</wa-select>
|
||||
<wa-button size="medium" style="width: 100%; margin-top: auto;">Medium</wa-button>
|
||||
</form>
|
||||
</div>
|
||||
</wa-card>
|
||||
</div>
|
||||
<style>
|
||||
.with-inline-price {
|
||||
wa-card {
|
||||
width: 100%;
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.card-title {
|
||||
font-size: large;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
.card-body {
|
||||
display: grid;
|
||||
grid-template-columns: 35% 1fr;
|
||||
gap: 1rem;
|
||||
|
||||
.detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.price {
|
||||
font-size: xx-large;
|
||||
font-weight: 600;
|
||||
}
|
||||
.rating {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## with large selector
|
||||
```html{.example}
|
||||
<wa-card class="large-selector">
|
||||
<div class="card-body">
|
||||
<div style="grid-column: 1/6">
|
||||
<img style="border-radius: var(--border-radius); height: 100%; object-fit: cover;" src="/assets/images/patterns/gervyn-louis-IS03ajI00Fc-unsplash.jpg" />
|
||||
</div>
|
||||
<div style="grid-column: 6/-1" class="info">
|
||||
<h2>Basic Tank</h2>
|
||||
<wa-icon-button name="close" label="close-modal"></wa-icon-button>
|
||||
<section>
|
||||
<p style="font-size: x-large;font-weight: 600;">$32</p>
|
||||
<div style="display: flex; align-items: flex-start">
|
||||
<p>3.9</p>
|
||||
<wa-rating></wa-rating>
|
||||
<a href="*" style="margin-left: auto;">See all 512 Reviews</a>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<form>
|
||||
<wa-radio-group label="Color" hint="Choose the most appropriate option." name="a" value="black" style="margin-bottom: 1rem;">
|
||||
<wa-radio value="black">Black</wa-radio>
|
||||
<wa-radio value="gray">Gray</wa-radio>
|
||||
</wa-radio-group>
|
||||
<wa-radio-group label="Size" hint="Select an option that makes you proud." name="a" value="medium" style="margin-bottom: 1rem;">
|
||||
<wa-radio-button value="small">S</wa-radio-button>
|
||||
<wa-radio-button value="medium">M</wa-radio-button>
|
||||
<wa-radio-button value="large">L</wa-radio-button>
|
||||
<wa-radio-button value="extra-large">XL</wa-radio-button>
|
||||
</wa-radio-group>
|
||||
<wa-button size="medium" style="width: 100%; margin-top: auto;margin-bottom: 1rem;">Medium</wa-button>
|
||||
<a href="*" style="display: inline-block;width: 100%;text-align: center;">View full details</a>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
<style>
|
||||
.large-selector .card-body {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
gap: 1rem;
|
||||
|
||||
.info {
|
||||
position: relative;
|
||||
|
||||
wa-icon-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
@@ -1,166 +0,0 @@
|
||||
---
|
||||
title: Product Lists
|
||||
description: TODO
|
||||
parent: ecommerce
|
||||
tags: e-commerce
|
||||
---
|
||||
|
||||
TODO Page Description
|
||||
## With Product Grid
|
||||
|
||||
```html{.example}
|
||||
<div class="with-product-grid">
|
||||
<div class="grid-item">
|
||||
<img class="grid-item-image" src="/assets/images/patterns/mad-rabbit-tattoo-7N4FMowSGek-unsplash.jpg" />
|
||||
<div class="grid-item-name">Shirt</div>
|
||||
<wa-rating label="Rating" readonly value="3"></wa-rating>
|
||||
<a class="grid-item-reviews" href="#">38 Reviews</a>
|
||||
<div class="grid-item-price">$170</div>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<img class="grid-item-image" src="/assets/images/patterns/mad-rabbit-tattoo-7N4FMowSGek-unsplash.jpg" />
|
||||
<div class="grid-item-name">Shirt</div>
|
||||
<wa-rating label="Rating" readonly value="3"></wa-rating>
|
||||
<a class="grid-item-reviews" href="#">38 Reviews</a>
|
||||
<div class="grid-item-price">$170</div>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<img class="grid-item-image" src="/assets/images/patterns/mad-rabbit-tattoo-7N4FMowSGek-unsplash.jpg" />
|
||||
<div class="grid-item-name">Shirt</div>
|
||||
<wa-rating label="Rating" readonly value="3"></wa-rating>
|
||||
<a class="grid-item-reviews" href="#">38 Reviews</a>
|
||||
<div class="grid-item-price">$170</div>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<img class="grid-item-image" src="/assets/images/patterns/mad-rabbit-tattoo-7N4FMowSGek-unsplash.jpg" />
|
||||
<div class="grid-item-name">Shirt</div>
|
||||
<wa-rating label="Rating" readonly value="3"></wa-rating>
|
||||
<a class="grid-item-reviews" href="#">38 Reviews</a>
|
||||
<div class="grid-item-price">$170</div>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.with-product-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
border: var(--wa-panel-border-width) var(--wa-border-style) var(--wa-color-neutral-border-quiet);
|
||||
|
||||
.grid-item {
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.grid-item:nth-of-type(odd) {
|
||||
|
||||
border-right: var(--wa-panel-border-width) var(--wa-border-style) var(--wa-color-neutral-border-quiet);
|
||||
}
|
||||
.grid-item:not(:nth-last-child(-n + 2)) {
|
||||
border-bottom: var(--wa-panel-border-width) var(--wa-border-style) var(--wa-color-neutral-border-quiet);
|
||||
}
|
||||
|
||||
.grid-item-image {
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.grid-item-name {
|
||||
margin-top: 1rem;
|
||||
font-weight: var(--wa-font-weight-bold);
|
||||
}
|
||||
|
||||
.grid-item wa-rating {
|
||||
--symbol-size: var(--wa-font-size-m);
|
||||
margin-top: .5rem;
|
||||
}
|
||||
.grid-item-reviews {
|
||||
--wa-link-decoration-default: none;
|
||||
--wa-color-text-link: var(--wa-color-gray-50);
|
||||
font-size: var(--wa-font-size-m);
|
||||
}
|
||||
.grid-item-price {
|
||||
font-size: var(--wa-font-size-2xl);
|
||||
font-weight: var(--wa-font-weight-bold);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
## Card with full details
|
||||
```html{.example}
|
||||
<div class="card-with-details">
|
||||
<wa-card with-footer>
|
||||
<img class="grid-item-image" src="/assets/images/patterns/mad-rabbit-tattoo-7N4FMowSGek-unsplash.jpg" />
|
||||
<div slot="footer" class="card-footer details">
|
||||
<span class="detail-name">Basic Tee 8-pack</span>
|
||||
<p class="detail-description">Get the full lineup of our Basic Tees. Have a fresh shirt all week, and an extra for laundry day.</p>
|
||||
<span class="detail-color">8 colors</span>
|
||||
<span class="detail-price">$256</span>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card with-footer>
|
||||
<img class="grid-item-image" src="/assets/images/patterns/mad-rabbit-tattoo-7N4FMowSGek-unsplash.jpg" />
|
||||
<div slot="footer" class="card-footer details">
|
||||
<span class="detail-name">Basic Tee 8-pack</span>
|
||||
<p class="detail-description">Get the full lineup of our Basic Tees. Have a fresh shirt all week, and an extra for laundry day.</p>
|
||||
<span class="detail-color">8 colors</span>
|
||||
<span class="detail-price">$256</span>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card with-footer>
|
||||
<img class="grid-item-image" src="/assets/images/patterns/mad-rabbit-tattoo-7N4FMowSGek-unsplash.jpg" />
|
||||
<div slot="footer" class="card-footer details">
|
||||
<span class="detail-name">Basic Tee 8-pack</span>
|
||||
<p class="detail-description">Get the full lineup of our Basic Tees. Have a fresh shirt all week, and an extra for laundry day.</p>
|
||||
<span class="detail-color">8 colors</span>
|
||||
<span class="detail-price">$256</span>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card with-footer>
|
||||
<img class="grid-item-image" src="/assets/images/patterns/mad-rabbit-tattoo-7N4FMowSGek-unsplash.jpg" />
|
||||
<div slot="footer" class="card-footer details">
|
||||
<span class="detail-name">Basic Tee 8-pack</span>
|
||||
<p class="detail-description">Get the full lineup of our Basic Tees. Have a fresh shirt all week, and an extra for laundry day.</p>
|
||||
<span class="detail-color">8 colors</span>
|
||||
<span class="detail-price">$256</span>
|
||||
</div>
|
||||
</wa-card>
|
||||
</div>
|
||||
<style>
|
||||
.card-with-details {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
.card-with-details wa-card::part(body) {
|
||||
padding: 0;
|
||||
}
|
||||
.card-with-details .card-footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.details {
|
||||
.detail-description {
|
||||
color: var(--wa-color-gray-50);
|
||||
}
|
||||
.detail-name {
|
||||
font-size: var(--wa-font-size-l);
|
||||
font-weight: var(--wa-font-weight-action);
|
||||
}
|
||||
.detail-color {
|
||||
color: var(--wa-color-gray-50);
|
||||
font-style: italic;
|
||||
}
|
||||
.detail-price {
|
||||
font-size: var(--wa-font-size-xl);
|
||||
font-weight: var(--wa-font-weight-action);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
```
|
||||
## With color swatches (WIP)
|
||||
```html{.example}
|
||||
|
||||
```
|
||||
@@ -1,465 +0,0 @@
|
||||
---
|
||||
title: Product Reviews
|
||||
description: TODO
|
||||
parent: ecommerce
|
||||
tags: e-commerce
|
||||
---
|
||||
|
||||
TODO Page Description
|
||||
|
||||
## With images grid
|
||||
```html{.example}
|
||||
<div class="with-image-grid">
|
||||
<wa-breadcrumb>
|
||||
<wa-breadcrumb-item>Clothing</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Men's</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Shirts & Tops</wa-breadcrumb-item>
|
||||
</wa-breadcrumb>
|
||||
<div class="image-grid">
|
||||
<img src="/assets/images/patterns/gervyn-louis-IS03ajI00Fc-unsplash.jpg" />
|
||||
<img src="/assets/images/patterns/gervyn-louis-KXvd7y7AU6Q-unsplash.jpg" />
|
||||
<img src="/assets/images/patterns/gervyn-louis-semwwyXFQho-unsplash.jpg" />
|
||||
<img src="/assets/images/patterns/mad-rabbit-tattoo-7N4FMowSGek-unsplash.jpg" />
|
||||
</div>
|
||||
<div>
|
||||
<h2>Tank top</h2>
|
||||
<p>The Basic Tee 6-Pack allows you to fully express your vibrant personality with three grayscale options. Feeling adventurous? Put on a heather gray tee. Want to be a trendsetter? Try our exclusive colorway: "Black". Need to add an extra pop of color to your outfit? Our white tee has you covered.</p>
|
||||
<h3>Highlights</h3>
|
||||
<ul>
|
||||
<li>Hand cut and sewn locally</li>
|
||||
</ul>
|
||||
|
||||
<h3>Highlights</h3>
|
||||
<p>The 6-Pack includes two black, two white, and two heather gray Basic Tees. Sign up for our subscription service and be the first to get new, exciting colors, like our upcoming "Charcoal Gray" limited release.</p>
|
||||
|
||||
<span>$192</span>
|
||||
<div>
|
||||
<wa-rating label="Rating" precision="0.5" value="2.5"></wa-rating>
|
||||
<a href="#">117 Reviews</a>
|
||||
</div>
|
||||
<wa-radio-group label="Select an option" hint="Select an option that makes you proud." name="a" value="1">
|
||||
<wa-radio-button value="1">Option 1</wa-radio-button>
|
||||
<wa-radio-button value="2">Option 2</wa-radio-button>
|
||||
<wa-radio-button value="3">Option 3</wa-radio-button>
|
||||
</wa-radio-group>
|
||||
<wa-radio-group label="Select an option" hint="Select an option that makes you proud." name="a" value="1">
|
||||
<wa-radio-button value="1">Option 1</wa-radio-button>
|
||||
<wa-radio-button value="2">Option 2</wa-radio-button>
|
||||
<wa-radio-button value="3">Option 3</wa-radio-button>
|
||||
</wa-radio-group>
|
||||
<wa-button>Add to Cart</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.with-image-grid {
|
||||
wa-breadcrumb::part(base) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.image-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.image-grid img:nth-of-type(1) {
|
||||
grid-column: 1/-1;
|
||||
}
|
||||
|
||||
.image-grid img:nth-of-type(2) {
|
||||
grid-column: 1/7;
|
||||
}
|
||||
.image-grid img:nth-of-type(3) {
|
||||
grid-column: 7/-1;
|
||||
}
|
||||
.image-grid img:nth-of-type(4) {
|
||||
grid-column: 1/-1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## With Tiered Images
|
||||
|
||||
```html{.example}
|
||||
<div class="with-tiered-images">
|
||||
<wa-breadcrumb>
|
||||
<wa-breadcrumb-item>Clothing</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Men's</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Shirts & Tops</wa-breadcrumb-item>
|
||||
</wa-breadcrumb>
|
||||
<div>
|
||||
<div class="heading">
|
||||
<h2>Basic Tee</h2>
|
||||
<span style="font-size: var(--wa-font-size-2xl)">$35</span>
|
||||
</div>
|
||||
<div class="rating">
|
||||
<span>3.9</span>
|
||||
<wa-rating label="Rating" precision="0.5" value="3.9"></wa-rating>
|
||||
<a href="#">117 Reviews</a>
|
||||
</div>
|
||||
<div class="tiered-images">
|
||||
<img src="/assets/images/patterns/gervyn-louis-IS03ajI00Fc-unsplash.jpg" />
|
||||
<img src="/assets/images/patterns/gervyn-louis-KXvd7y7AU6Q-unsplash.jpg" />
|
||||
<img src="/assets/images/patterns/gervyn-louis-semwwyXFQho-unsplash.jpg" />
|
||||
</div>
|
||||
</div>
|
||||
<wa-radio-group label="Select an option" hint="Select an option that makes you proud." name="a" value="1">
|
||||
<wa-radio-button value="1">Option 1</wa-radio-button>
|
||||
<wa-radio-button value="2">Option 2</wa-radio-button>
|
||||
<wa-radio-button value="3">Option 3</wa-radio-button>
|
||||
</wa-radio-group>
|
||||
<wa-radio-group label="Select an option" hint="Select an option that makes you proud." name="a" value="1">
|
||||
<wa-radio-button value="1">Option 1</wa-radio-button>
|
||||
<wa-radio-button value="2">Option 2</wa-radio-button>
|
||||
<wa-radio-button value="3">Option 3</wa-radio-button>
|
||||
</wa-radio-group>
|
||||
<wa-button>Add to Cart</wa-button>
|
||||
<h3>Description</h3>
|
||||
<p>The Basic tee is an honest new take on a classic. The tee uses super soft, pre-shrunk cotton for true comfort and a dependable fit. They are hand cut and sewn locally, with a special dye technique that gives each tee it's own look.</p>
|
||||
<p>Looking to stock your closet? The Basic tee also comes in a 3-pack or 5-pack at a bundle discount.</p>
|
||||
<hr />
|
||||
<h3>Highlights</h3>
|
||||
<ul>
|
||||
<li>Hand cut and sewn locally</li>
|
||||
</ul>
|
||||
<div>
|
||||
<wa-card>
|
||||
<wa-icon family="solid" name="earth-americas"></wa-icon>
|
||||
<h3>International delivery</h3>
|
||||
<p>Get your order in 2 years</p>
|
||||
</wa-card>
|
||||
<wa-card>
|
||||
<wa-icon family="solid" name="earth-americas"></wa-icon>
|
||||
<h3>International delivery</h3>
|
||||
<p>Get your order in 2 years</p>
|
||||
|
||||
</wa-card>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.with-tiered-images {
|
||||
wa-breadcrumb::part(base) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.heading {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.rating {
|
||||
display: flex;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
wa-rating {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.tiered-images {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.tiered-images img:nth-of-type(1) {
|
||||
grid-column: 1/-1;
|
||||
}
|
||||
|
||||
.tiered-images img:nth-of-type(2) {
|
||||
grid-column: 1/7;
|
||||
}
|
||||
.tiered-images img:nth-of-type(3) {
|
||||
grid-column: 7/-1;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
## with images and expandable details
|
||||
|
||||
```html {.example}
|
||||
<wa-carousel class="carousel-thumbnails" navigation loop>
|
||||
<wa-carousel-item>
|
||||
<img
|
||||
alt="The sun shines on the mountains and trees (by Adam Kool on Unsplash)"
|
||||
src="/assets/examples/carousel/pullover-1.jpg"
|
||||
/>
|
||||
</wa-carousel-item>
|
||||
<wa-carousel-item>
|
||||
<img
|
||||
alt="A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)"
|
||||
src="/assets/examples/carousel/pullover-2.jpg"
|
||||
/>
|
||||
</wa-carousel-item>
|
||||
<wa-carousel-item>
|
||||
<img
|
||||
alt="The sun is setting over a lavender field (by Leonard Cotte on Unsplash)"
|
||||
src="/assets/examples/carousel/pullover-3.jpg"
|
||||
/>
|
||||
</wa-carousel-item>
|
||||
<wa-carousel-item>
|
||||
<img
|
||||
alt="A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)"
|
||||
src="/assets/examples/carousel/pullover-4.jpg"
|
||||
/>
|
||||
</wa-carousel-item>
|
||||
<wa-carousel-item>
|
||||
<img
|
||||
alt="A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)"
|
||||
src="/assets/examples/carousel/pullover-5.jpg"
|
||||
/>
|
||||
</wa-carousel-item>
|
||||
</wa-carousel>
|
||||
|
||||
<div class="thumbnails">
|
||||
<div class="thumbnails__scroller">
|
||||
<img alt="Thumbnail by 1" class="thumbnails__image active" src="/assets/examples/carousel/pullover-1.jpg" />
|
||||
<img alt="Thumbnail by 2" class="thumbnails__image" src="/assets/examples/carousel/pullover-2.jpg" />
|
||||
<img alt="Thumbnail by 3" class="thumbnails__image" src="/assets/examples/carousel/pullover-3.jpg" />
|
||||
<img alt="Thumbnail by 4" class="thumbnails__image" src="/assets/examples/carousel/pullover-4.jpg" />
|
||||
<img alt="Thumbnail by 5" class="thumbnails__image" src="/assets/examples/carousel/pullover-5.jpg" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style="--wa-space-xl: 0;">Pullover Sweater</h3>
|
||||
<span class="price-big">$140</span>
|
||||
<wa-rating class="sweater-rating" label="Rating" precision="0.5" value="2.5"></wa-rating>
|
||||
<p>The Zip Tote Basket is the perfect midpoint between shopping tote and comfy backpack. With convertible straps, you can hand carry, should sling, or backpack this convenient and spacious bag. The zip top and durable canvas construction keeps your goods protected for all-day use.</p>
|
||||
<wa-radio-group label="Select Color" hint="Select an option that makes you proud." name="a" value="1">
|
||||
<wa-radio-button value="1"></wa-radio-button>
|
||||
<wa-radio-button value="2"></wa-radio-button>
|
||||
<wa-radio-button value="3"></wa-radio-button>
|
||||
</wa-radio-group>
|
||||
<div>
|
||||
<wa-button>Add to cart</wa-button>
|
||||
<wa-icon-button name="gear" label="Settings"></wa-icon-button>
|
||||
</div>
|
||||
<div class="details-group-example">
|
||||
<wa-details summary="First" open>
|
||||
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="Second">
|
||||
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="Third">
|
||||
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>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.carousel-thumbnails {
|
||||
--slide-aspect-ratio: 3 / 2;
|
||||
}
|
||||
|
||||
wa-radio-button #shadow-root div .button--medium {
|
||||
padding: var(--wa-space-xs) var(--wa-space-xs);
|
||||
}
|
||||
|
||||
.color-circle {
|
||||
--background: #000;
|
||||
background: var(--background);
|
||||
|
||||
width: 50px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sweater-rating {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.price-big {
|
||||
display: block;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.thumbnails {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.thumbnails__scroller {
|
||||
display: flex;
|
||||
gap: var(--wa-space-s);
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
scroll-behavior: smooth;
|
||||
scroll-padding: var(--wa-space-s);
|
||||
}
|
||||
|
||||
.thumbnails__scroller::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.thumbnails__image {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
object-fit: cover;
|
||||
|
||||
opacity: 0.3;
|
||||
will-change: opacity;
|
||||
transition: 250ms opacity;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.thumbnails__image.active {
|
||||
opacity: 1;
|
||||
}
|
||||
.details-group-example wa-details:not(:last-of-type) {
|
||||
margin-bottom: var(--wa-space-2xs);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
{
|
||||
const carousel = document.querySelector('.carousel-thumbnails');
|
||||
const scroller = document.querySelector('.thumbnails__scroller');
|
||||
const thumbnails = document.querySelectorAll('.thumbnails__image');
|
||||
|
||||
scroller.addEventListener('click', e => {
|
||||
const target = e.target;
|
||||
|
||||
if (target.matches('.thumbnails__image')) {
|
||||
const index = [...thumbnails].indexOf(target);
|
||||
carousel.goToSlide(index);
|
||||
}
|
||||
});
|
||||
|
||||
carousel.addEventListener('wa-slide-change', e => {
|
||||
const slideIndex = e.detail.index;
|
||||
|
||||
[...thumbnails].forEach((thumb, i) => {
|
||||
thumb.classList.toggle('active', i === slideIndex);
|
||||
if (i === slideIndex) {
|
||||
thumb.scrollIntoView({
|
||||
block: 'nearest'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const container = document.querySelector('.details-group-example');
|
||||
|
||||
// Close all other details when one is shown
|
||||
container.addEventListener('wa-show', event => {
|
||||
if (event.target.localName === 'wa-details') {
|
||||
[...container.querySelectorAll('wa-details')].map(details => (details.open = event.target === details));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
## Split with image
|
||||
|
||||
```html {.example}
|
||||
<div class="split-with-image">
|
||||
<div class="div-1">
|
||||
<wa-breadcrumb>
|
||||
<wa-breadcrumb-item>Clothing</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Men's</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Shirts & Tops</wa-breadcrumb-item>
|
||||
</wa-breadcrumb>
|
||||
<h2>Everyday Ruck Snack</h2>
|
||||
<span>
|
||||
<span>$220</span> |
|
||||
<wa-rating label="Rating" precision="0.5" value="2.5"></wa-rating>
|
||||
<span>1624 reviews</span>
|
||||
</span>
|
||||
<p>Don't compromise on snack-carrying capacity with this lightweight and spacious bag. The drawstring top keeps all your favorite chips, crisps, fries, biscuits, crackers, and cookies secure.</p>
|
||||
<span><wa-icon family="solid" name="check"></wa-icon> In stock and ready to ship</span>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="div-2">
|
||||
<img src="/assets/images/patterns/gervyn-louis-IS03ajI00Fc-unsplash.jpg" />
|
||||
</div>
|
||||
<div class="div-3">
|
||||
<wa-radio-group label="Select an option" hint="Select an option that makes you proud." name="a" value="1">
|
||||
<wa-radio-button value="1">Option 1</wa-radio-button>
|
||||
<wa-radio-button value="2">Option 2</wa-radio-button>
|
||||
<wa-radio-button value="3">Option 3</wa-radio-button>
|
||||
</wa-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.split-with-image {
|
||||
display: grid;
|
||||
/* grid-template-columns: repeat(2, 1fr); */
|
||||
/* height: 1000px; */
|
||||
/* gap: 1rem; */
|
||||
.div-1 {
|
||||
|
||||
}
|
||||
.div-2 {
|
||||
/* background-color: black;
|
||||
grid-column-start: 2;
|
||||
grid-row: span 2 / span 2; */
|
||||
}
|
||||
.div-3 {
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
## With tabs
|
||||
|
||||
```html{.example}
|
||||
<div>
|
||||
<wa-rating class="sweater-rating" label="Rating" precision="0.5" value="2.5"></wa-rating>
|
||||
<h2>Application UI Icon Pack</h2>
|
||||
<img alt="Sample of 30 icons with friendly and fun details in outline, filled, and brand color styles." src="https://tailwindui.com/img/ecommerce-images/product-page-05-product-01.jpg" class="aqk aql">
|
||||
<p>The Application UI Icon Pack comes with over 200 icons in 3 styles: outline, filled, and branded. This playful icon pack is tailored for complex application user interfaces with a friendly and legible look.</p>
|
||||
<wa-button variant="brand">Brand</wa-button>
|
||||
|
||||
<wa-button variant="success">Success</wa-button>
|
||||
<hr />
|
||||
<h3>Highlights</h3>
|
||||
<ul>
|
||||
<li>200+ SVG icons in 3 unique styles</li>
|
||||
<li>Compatible with Figma, Sketch, and Adobe XD</li>
|
||||
<li>Drawn on 24 x 24 pixel grid</li>
|
||||
</ul>
|
||||
<hr />
|
||||
<h3>License</h3>
|
||||
<p>For personal and professional use. You cannot resell or redistribute these icons in their original or modified state. <a href="#">Read full license</a></p>
|
||||
<hr />
|
||||
<h3>Share</h3>
|
||||
|
||||
|
||||
<wa-icon family="brands" name="facebook"></wa-icon>
|
||||
<wa-icon family="brands" name="instagram"></wa-icon>
|
||||
<wa-icon family="brands" name="x-twitter"></wa-icon>
|
||||
<wa-tab-group>
|
||||
<wa-tab panel="general">General</wa-tab>
|
||||
<wa-tab panel="custom">Custom</wa-tab>
|
||||
<wa-tab panel="advanced">Advanced</wa-tab>
|
||||
<wa-tab panel="disabled" disabled>Disabled</wa-tab>
|
||||
|
||||
<wa-tab-panel name="general">
|
||||
<div></div>
|
||||
<div>
|
||||
<h3>Hector Gibbons</h3>
|
||||
<p>July 12, 2021</p>
|
||||
<wa-rating label="Rating" precision="0.5" value="2.5"></wa-rating>
|
||||
<p>Blown away by how polished this icon pack is. Everything looks so consistent and each SVG is optimized out of the box so I can use it directly with confidence. It would take me several hours to create a single icon this good, so it's a steal at this price.</p>
|
||||
</div>
|
||||
</wa-tab-panel>
|
||||
<wa-tab-panel name="custom">This is the custom tab panel.</wa-tab-panel>
|
||||
<wa-tab-panel name="advanced">This is the advanced tab panel.</wa-tab-panel>
|
||||
<wa-tab-panel name="disabled">This is a disabled tab panel.</wa-tab-panel>
|
||||
</wa-tab-group>
|
||||
|
||||
</div>
|
||||
|
||||
```
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
title: Shopping Cart
|
||||
description: TODO
|
||||
parent: ecommerce
|
||||
tags: e-commerce
|
||||
---
|
||||
|
||||
TODO Page Description
|
||||
|
||||
## Examples
|
||||
File diff suppressed because it is too large
Load Diff
84
docs/docs/patterns/ecommerce/category-filter.md
Normal file
84
docs/docs/patterns/ecommerce/category-filter.md
Normal file
@@ -0,0 +1,84 @@
|
||||
---
|
||||
title: Category Filter
|
||||
description: 'Helps the user find the right products with filters to refine search results by specific attributes.'
|
||||
icon: checkbox
|
||||
isPro: true
|
||||
---
|
||||
|
||||
## Sidebar with Checkboxes & Expandable Filters
|
||||
|
||||
```html{.example}
|
||||
<h1>New Arrivals</h1>
|
||||
<div class="wa-flank wa-align-items-start" style="--flank-size: 200px;">
|
||||
<form class="wa-stack">
|
||||
<wa-checkbox checked>All Products</wa-checkbox>
|
||||
<wa-checkbox>Sale</wa-checkbox>
|
||||
<wa-checkbox>Travel</wa-checkbox>
|
||||
<wa-checkbox>Organization</wa-checkbox>
|
||||
<wa-checkbox>Accessories</wa-checkbox>
|
||||
<wa-details summary="Color" open>
|
||||
<div class="wa-stack">
|
||||
<wa-checkbox>White</wa-checkbox>
|
||||
<wa-checkbox>Beige</wa-checkbox>
|
||||
<wa-checkbox>Blue</wa-checkbox>
|
||||
<wa-checkbox>Brown</wa-checkbox>
|
||||
<wa-checkbox>Green</wa-checkbox>
|
||||
</div>
|
||||
</wa-details>
|
||||
<wa-details summary="Category">
|
||||
<div class="wa-stack">
|
||||
<wa-checkbox>Outdoor</wa-checkbox>
|
||||
<wa-checkbox>Indoor</wa-checkbox>
|
||||
<wa-checkbox>All Weather</wa-checkbox>
|
||||
</div>
|
||||
</wa-details>
|
||||
<wa-details summary="Size">
|
||||
<div class="wa-stack">
|
||||
<wa-checkbox>Small</wa-checkbox>
|
||||
<wa-checkbox>Medium</wa-checkbox>
|
||||
<wa-checkbox>Large</wa-checkbox>
|
||||
<wa-checkbox>XL</wa-checkbox>
|
||||
<wa-checkbox>XXL</wa-checkbox>
|
||||
</div>
|
||||
</wa-details>
|
||||
</form>
|
||||
<div class="wa-placeholder"></div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Sidebar with Dropdowns
|
||||
|
||||
```html{.example}
|
||||
<h1>New Arrivals</h1>
|
||||
<div class="wa-flank wa-align-items-start">
|
||||
<div class="wa-stack">
|
||||
<wa-select label="Product Type" placeholder="Products" value="all-products">
|
||||
<wa-option value="all-products">All Products</wa-option>
|
||||
<wa-option value="sale">Sale</wa-option>
|
||||
<wa-option value="travel">Travel</wa-option>
|
||||
<wa-option value="organization">Organization</wa-option>
|
||||
<wa-option value="accessories">Accessories</wa-option>
|
||||
</wa-select>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-select label="Color" placeholder="Color" value="black" multiple>
|
||||
<wa-option value="black">Black</wa-option>
|
||||
<wa-option value="white">White</wa-option>
|
||||
<wa-option value="gray">Gray</wa-option>
|
||||
</wa-select>
|
||||
<wa-select label="Category" placeholder="Category" value="outdoor" multiple>
|
||||
<wa-option value="outdoor">Outdoor</wa-option>
|
||||
<wa-option value="indoor">Indoor</wa-option>
|
||||
<wa-option value="all-weather">All Weather</wa-option>
|
||||
</wa-select>
|
||||
<wa-select label="Size" placeholder="Size" value="xl xxl" multiple>
|
||||
<wa-option value="s">Small</wa-option>
|
||||
<wa-option value="m">Medium</wa-option>
|
||||
<wa-option value="l">Large</wa-option>
|
||||
<wa-option value="xl">XL</wa-option>
|
||||
<wa-option value="xxl">XXL</wa-option>
|
||||
</wa-select>
|
||||
</div>
|
||||
<div class="wa-placeholder"></div>
|
||||
</div>
|
||||
```
|
||||
163
docs/docs/patterns/ecommerce/category-preview.md
Normal file
163
docs/docs/patterns/ecommerce/category-preview.md
Normal file
@@ -0,0 +1,163 @@
|
||||
---
|
||||
title: Category Preview
|
||||
description: 'Help shoppers discover your product offerings with showcases of product categories.'
|
||||
icon: preview
|
||||
isPro: true
|
||||
---
|
||||
|
||||
## Split with Image Grid
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-flank wa-align-items-start" style="--flank-size: 20rem;">
|
||||
<div class="wa-stack wa-gap-2xl">
|
||||
<h2 class="wa-heading-xl">Casual Collection</h2>
|
||||
<p class="wa-body-s">Look good — without looking like you're trying too hard. Our casual collection includes laid back styles that work in <em>almost</em> any situation.</p>
|
||||
<wa-button>View the Collection</wa-button>
|
||||
</div>
|
||||
<div class="wa-stack">
|
||||
<div class="wa-frame:landscape wa-border-radius-s">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1544441893-675973e31985?q=80&w=2340&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
|
||||
alt="An analog watch, cotton pants, crew neck tee, and pair of tennis shoes (Photograph by Mnz)"
|
||||
/>
|
||||
</div>
|
||||
<div class="wa-grid">
|
||||
<div class="wa-frame:landscape wa-border-radius-s">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1548768041-2fceab4c0b85?q=80&w=3540&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
|
||||
alt="Stack of three folded solid color tees (Photograph by Mnz)"
|
||||
/>
|
||||
</div>
|
||||
<div class="wa-frame:landscape wa-border-radius-s">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1544441892-794166f1e3be?q=80&w=3540&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
|
||||
alt="Pair of bright white tennis shoes(Photograph by Mnz)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Columns with Tall Images
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-stack">
|
||||
<h2 class="wa-heading-xl">Shop by Category</h2>
|
||||
<div class="wa-grid">
|
||||
<a href="" class="wa-stack wa-link-plain">
|
||||
<div class="wa-frame:portrait wa-border-radius-s">
|
||||
<img
|
||||
src="https://uploads.webawesome.com/organization.jpg"
|
||||
alt="Inside of a closet filled with clothes on wooden hangers and integrated shelving with shoes"
|
||||
/>
|
||||
</div>
|
||||
<span class="wa-caption-xl">Organization</span>
|
||||
</a>
|
||||
<a href="" class="wa-stack wa-link-plain">
|
||||
<div class="wa-frame:portrait wa-border-radius-s">
|
||||
<img
|
||||
src="https://uploads.webawesome.com/bags.jpg"
|
||||
alt="Young person hugging a small floral patterned book bag between their arms"
|
||||
/>
|
||||
</div>
|
||||
<span class="wa-caption-xl">Bags</span>
|
||||
</a>
|
||||
<a href="" class="wa-stack wa-link-plain">
|
||||
<div class="wa-frame:portrait wa-border-radius-s">
|
||||
<img
|
||||
src="https://uploads.webawesome.com/outdoor-2.jpg"
|
||||
alt="Person in a mountain clearing wearing a waterproof hooded windbreaker in black and orange"
|
||||
/>
|
||||
</div>
|
||||
<span class="wa-caption-xl">Outdoor</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Columns with Cards
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-stack">
|
||||
<div class="wa-split">
|
||||
<h2 class="wa-heading-xl">Shop by Category</h2>
|
||||
<a href="" class="wa-cluster">
|
||||
<span>Browse All Categories</span>
|
||||
<wa-icon name="arrow-right"></wa-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="wa-grid">
|
||||
<a href="" class="wa-link-plain">
|
||||
<wa-card style="height: 100%">
|
||||
<img
|
||||
slot="image"
|
||||
src="https://img.fortawesome.com/cfa83f3c/outdoor-3x.jpg"
|
||||
alt="Two hikers wearing long canvas pants, weatherproof jackets, and backpacks"
|
||||
/>
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<span class="wa-heading-m">Outdoor</span>
|
||||
<p class="wa-caption-m">Durable canvas gear for all conditions.</p>
|
||||
</div>
|
||||
</wa-card>
|
||||
</a>
|
||||
<a href="" class="wa-link-plain">
|
||||
<wa-card style="height: 100%">
|
||||
<img
|
||||
slot="image"
|
||||
src="https://img.fortawesome.com/cfa83f3c/home.jpg"
|
||||
alt="Woman sitting on a couch in a bright home, wearing a thick knit sweater"
|
||||
/>
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<span class="wa-heading-m">Home</span>
|
||||
<p class="wa-caption-m">Cozy up on the couch and relax in soft cotton.</p>
|
||||
</div>
|
||||
</wa-card>
|
||||
</a>
|
||||
<a href="" class="wa-link-plain">
|
||||
<wa-card style="height: 100%">
|
||||
<img
|
||||
slot="image"
|
||||
src="https://img.fortawesome.com/cfa83f3c/fitness.jpg"
|
||||
alt="Athlete training in fitted active wear tee and shorts"
|
||||
/>
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<span class="wa-heading-m">Active</span>
|
||||
<p class="wa-caption-m">Get fit in style with breathable poly blends.</p>
|
||||
</div>
|
||||
</wa-card>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Square Image Grid
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-stack wa-gap-2xl">
|
||||
<div class="wa-stack wa-gap-xs wa-align-items-center">
|
||||
<h2 class="wa-heading-xl">New Arrivals</h2>
|
||||
<p class="wa-caption-l">Explore brand new furniture to accentuate your home aesthetic — just for you.</p>
|
||||
</div>
|
||||
<div class="wa-grid">
|
||||
<div class="wa-stack">
|
||||
<div class="wa-frame wa-border-radius-m">
|
||||
<img
|
||||
src="https://uploads.webawesome.com/indoor-furniture.jpg"
|
||||
alt="Sunny room with a mid-century modern couch, accent chair, and elegant lamp"
|
||||
/>
|
||||
</div>
|
||||
<wa-button appearance="outlined">View Indoor Furniture</wa-button>
|
||||
</div>
|
||||
<div class="wa-stack">
|
||||
<div class="wa-frame wa-border-radius-m">
|
||||
<img
|
||||
src="https://uploads.webawesome.com/outdoor-furniture.jpg"
|
||||
alt="Covered patio with rustic wooden cabinets, writing desk, and stool"
|
||||
/>
|
||||
</div>
|
||||
<wa-button appearance="outlined">View Outdoor Furniture</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
245
docs/docs/patterns/ecommerce/checkout-form.md
Normal file
245
docs/docs/patterns/ecommerce/checkout-form.md
Normal file
@@ -0,0 +1,245 @@
|
||||
---
|
||||
title: Checkout Form
|
||||
description: 'Let shoppers checkout with ease with streamlined forms to capture shipping and payment info.'
|
||||
isPro: true
|
||||
---
|
||||
|
||||
|
||||
## Full Form with Order Summary Card
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-grid wa-gap-3xl">
|
||||
<div class="wa-stack">
|
||||
<h4>Contact</h4>
|
||||
<wa-input type="email" label="Email Address"></wa-input>
|
||||
<wa-divider></wa-divider>
|
||||
<h4>Shipping</h4>
|
||||
<wa-select label="Country" value="us">
|
||||
<wa-option value="ca">Canada</wa-option>
|
||||
<wa-option value="mx">Mexico</wa-option>
|
||||
<wa-option value="us">United States</wa-option>
|
||||
</wa-select>
|
||||
<div class="wa-grid">
|
||||
<wa-input label="First Name"></wa-input>
|
||||
<wa-input label="Last Name"></wa-input>
|
||||
</div>
|
||||
<wa-input label="Company"></wa-input>
|
||||
<wa-input label="Address"></wa-input>
|
||||
<div class="wa-grid" style="--min-column-size: 10ch;">
|
||||
<wa-input label="City"></wa-input>
|
||||
<wa-input label="State"></wa-input>
|
||||
<wa-input label="Postal Code"></wa-input>
|
||||
</div>
|
||||
<wa-input label="Phone"></wa-input>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-radio-group label="Shipping Method" name="shipping-method" value="standard" orientation="horizontal">
|
||||
<wa-radio value="standard" hint="7-10 business days">Standard</wa-radio>
|
||||
<wa-radio value="express" hint="2-5 business days">Express</wa-radio>
|
||||
</wa-radio-group>
|
||||
<wa-divider></wa-divider>
|
||||
<h4>Payment</h4>
|
||||
<wa-radio-group label="Payment Method" name="payment-method" value="credit" orientation="horizontal">
|
||||
<wa-radio value="credit">Credit Card</wa-radio>
|
||||
<wa-radio value="paypal">Paypal</wa-radio>
|
||||
</wa-radio-group>
|
||||
<wa-input label="Card Number"></wa-input>
|
||||
<wa-input label="Name on Card"></wa-input>
|
||||
<div class="wa-grid">
|
||||
<wa-input label="Expiration Date" placeholder="MM/YY"></wa-input>
|
||||
<wa-input label="CVC"></wa-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wa-stack">
|
||||
<h4>Order Summary</h4>
|
||||
<wa-card>
|
||||
<div class="wa-stack">
|
||||
<div class="wa-flank wa-align-items-start" style="--flank-size: 7rem">
|
||||
<div class="wa-frame wa-border-radius-s">
|
||||
<img src="https://images.unsplash.com/photo-1595950653106-6c9ebd614d3a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w1OTAyOTl8MHwxfGFsbHx8fHx8fHx8fDE3MTg2NDM1MzB8&ixlib=rb-4.0.3&q=80&w=1080" alt="">
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<div class="wa-split">
|
||||
<span class="wa-heading-s">Dolce Runners</span>
|
||||
<wa-icon-button name="trash" label="Remove from cart"></wa-icon-button>
|
||||
</div>
|
||||
<span class="wa-caption-m">Cream/Seafoam</span>
|
||||
<span class="wa-caption-m">12.5</span>
|
||||
<div class="wa-split">
|
||||
<span>$135.00</span>
|
||||
<wa-select value="1" size="small" style="max-width: 8ch">
|
||||
<wa-option value="1">1</wa-option>
|
||||
<wa-option value="2">2</wa-option>
|
||||
<wa-option value="3">3</wa-option>
|
||||
</wa-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
<div class="wa-flank wa-align-items-start" style="--flank-size: 7rem">
|
||||
<div class="wa-frame wa-border-radius-s">
|
||||
<img src="https://images.unsplash.com/photo-1514989940723-e8e51635b782?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w1OTAyOTl8MHwxfGFsbHx8fHx8fHx8fDE3MTg2NDM1Njh8&ixlib=rb-4.0.3&q=80&w=1080" alt="">
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<div class="wa-split">
|
||||
<span class="wa-heading-s">Dunk High</span>
|
||||
<wa-icon-button name="trash" label="Remove from cart"></wa-icon-button>
|
||||
</div>
|
||||
<span class="wa-caption-m">Sand/Amber/Black</span>
|
||||
<span class="wa-caption-m">12.5</span>
|
||||
<div class="wa-split">
|
||||
<span>$180.00</span>
|
||||
<wa-select value="1" size="small" style="max-width: 8ch">
|
||||
<wa-option value="1">1</wa-option>
|
||||
<wa-option value="2">2</wa-option>
|
||||
<wa-option value="3">3</wa-option>
|
||||
</wa-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
<div class="wa-flank wa-align-items-start" style="--flank-size: 7rem">
|
||||
<div class="wa-frame wa-border-radius-s">
|
||||
<img src="https://images.unsplash.com/photo-1606107557195-0e29a4b5b4aa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w1OTAyOTl8MHwxfGFsbHx8fHx8fHx8fDE3MTg2NDM2MTF8&ixlib=rb-4.0.3&q=80&w=1080" alt="">
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<div class="wa-split">
|
||||
<span class="wa-heading-s">NB Runner</span>
|
||||
<wa-icon-button name="trash" label="Remove from cart"></wa-icon-button>
|
||||
</div>
|
||||
<span class="wa-caption-m">Forrest Green</span>
|
||||
<span class="wa-caption-m">12.5</span>
|
||||
<div class="wa-split">
|
||||
<span>$48.99</span>
|
||||
<wa-select value="1" size="small" style="max-width: 8ch">
|
||||
<wa-option value="1">1</wa-option>
|
||||
<wa-option value="2">2</wa-option>
|
||||
<wa-option value="3">3</wa-option>
|
||||
</wa-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
<div class="wa-stack">
|
||||
<div class="wa-split wa-caption-m">
|
||||
<span>Subtotal</span>
|
||||
<span>$363.99</span>
|
||||
</div>
|
||||
<div class="wa-split wa-caption-m">
|
||||
<span>Shipping</span>
|
||||
<span>FREE</span>
|
||||
</div>
|
||||
<div class="wa-split wa-heading-m">
|
||||
<span>Total</span>
|
||||
<span>$363.99</span>
|
||||
</div>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-button variant="brand">Confirm Order</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Short Form with Order Summary
|
||||
|
||||
```html {.example}
|
||||
<div class="wa-grid wa-gap-3xl">
|
||||
<div class="wa-stack wa-gap-xl">
|
||||
<h2>Payment</h2>
|
||||
<wa-input type="email" label="Email" placeholder="ex. tanderson@metacortex.com">
|
||||
<wa-icon slot="prefix" name="envelope"></wa-icon>
|
||||
</wa-input>
|
||||
<wa-input label="Card Number" placeholder="1234 1234 1234 1234">
|
||||
<wa-icon slot="prefix" name="credit-card"></wa-icon>
|
||||
</wa-input>
|
||||
<div class="wa-grid" style="--min-column-size: 12ch">
|
||||
<wa-input label="Expiration" placeholder="MM/YY">
|
||||
<wa-icon slot="prefix" name="calendar"></wa-icon>
|
||||
</wa-input>
|
||||
<wa-input label="CVC" placeholder="CVC">
|
||||
<wa-icon slot="prefix" name="lock"></wa-icon>
|
||||
</wa-input>
|
||||
</div>
|
||||
<wa-input label="Cardholder Name" placeholder="Thomas Anderson">
|
||||
<wa-icon slot="prefix" name="user"></wa-icon>
|
||||
</wa-input>
|
||||
<div class="wa-grid" style="--min-column-size: 12ch">
|
||||
<wa-select label="Country" value="us">
|
||||
<wa-icon slot="prefix" name="globe"></wa-icon>
|
||||
<wa-option value="ca">Canada</wa-option>
|
||||
<wa-option value="us">United States</wa-option>
|
||||
<wa-option value="mx">Mexico</wa-option>
|
||||
</wa-select>
|
||||
<wa-input label="ZIP" placeholder="12345">
|
||||
<wa-icon slot="prefix" name="location-dot"></wa-icon>
|
||||
</wa-input>
|
||||
</div>
|
||||
<wa-switch checked>Sign me up for more offers from this store</wa-switch>
|
||||
<wa-button variant="brand">Pay Now</wa-button>
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-xl">
|
||||
<h2>Order Summary</h2>
|
||||
<div class="wa-split">
|
||||
<div class="wa-cluster">
|
||||
<div class="wa-frame wa-border-radius-m" style="max-width: 4rem">
|
||||
<img src="https://images.unsplash.com/photo-1618677366787-9727aacca7ea?q=80&w=3255&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="Glasses with black wire frames and dark tinted, circular lenses (Photograph by Colin Lloyd)">
|
||||
</div>
|
||||
<strong>Morpheus</strong>
|
||||
</div>
|
||||
<div class="wa-cluster">
|
||||
<wa-input type="number" value="1" style="max-width: 5rem"></wa-input>
|
||||
<span>$120.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wa-split">
|
||||
<div class="wa-cluster">
|
||||
<div class="wa-frame wa-border-radius-m" style="max-width: 4rem">
|
||||
<img src="https://images.unsplash.com/photo-1511499767150-a48a237f0083?q=80&w=3558&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="Glasses with rose gold wire frames and green tinted, circular lenses (Photograph by Charles Deluvio)">
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-3xs">
|
||||
<strong>Seraph</strong>
|
||||
<em class="wa-caption-m">Tinted</em>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wa-cluster">
|
||||
<wa-input type="number" value="1" style="max-width: 5rem"></wa-input>
|
||||
<span>$180.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wa-split">
|
||||
<div class="wa-cluster">
|
||||
<div class="wa-frame wa-border-radius-m" style="max-width: 4rem">
|
||||
<img src="https://images.unsplash.com/photo-1547104442-a40f335740cb?q=80&w=3348&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="Glasses with tortoise shell half frames and large, rounded lenses (Photograph by Sincerely Media)">
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-3xs">
|
||||
<strong>Keymaker</strong>
|
||||
<em class="wa-caption-m">Glossy</em>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wa-cluster">
|
||||
<wa-input type="number" value="1" style="max-width: 5rem"></wa-input>
|
||||
<span>$50.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wa-flank:end">
|
||||
<wa-input placeholder="Discount code or gift card"></wa-input>
|
||||
<wa-button appearance="filled">Apply</wa-button>
|
||||
</div>
|
||||
<div class="wa-stack wa-gap-s">
|
||||
<div class="wa-split">
|
||||
<span>Subtotal</span>
|
||||
<strong>$530.00</strong>
|
||||
</div>
|
||||
<div class="wa-split">
|
||||
<span>Shipping</span>
|
||||
<span>$8.00</span>
|
||||
</div>
|
||||
<div class="wa-split">
|
||||
<strong>Total</strong>
|
||||
<strong>$538.00</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user