Compare commits

..

147 Commits

Author SHA1 Message Date
Kelsey Jackson
3b30f9f39e Merge pull request #859 from shoelace-style/kj/patterns-home-stretch
Kj/patterns home stretch
2025-04-04 12:17:39 -05:00
Kelsey Jackson
1999b65591 a few more app patterns 2025-04-04 12:16:21 -05:00
Kelsey Jackson
444e0d3e46 having local browset issues 2025-04-04 11:10:40 -05:00
Kelsey Jackson
5aeedceb2d added post articles 2025-04-03 21:53:50 -05:00
Kelsey Jackson
1e1cd2406e switching machines 2025-04-03 19:14:19 -05:00
Kelsey Jackson
e72f2c1847 added permissions pattern 2025-04-03 09:47:12 -05:00
Kelsey Jackson
910c6cb3fb Merge branch 'pattern-main' into kj/patterns-home-stretch 2025-04-02 17:24:17 -05:00
Kelsey Jackson
55edcd2470 Merge branch 'next' into pattern-main 2025-04-02 17:23:49 -05:00
Kelsey Jackson
952a44b5eb catching up with next 2025-04-02 17:23:06 -05:00
Kelsey Jackson
2ca8422bc8 switching machines 2025-04-02 16:05:09 -05:00
Kelsey Jackson
beb88fac56 fixed conflicts 2025-04-01 13:00:16 -05:00
Lindsay M
a588194b95 App pattern tweaks (#827)
* Fix `patterns.css` reference

* Tweaks to action panel patterns

* Tweaks to comments patterns

* Progress on data display patterns

* Progress on empty state patterns

* Use email-related data from recent update in `pattern-main` (altered slightly)

* Tweaks to data display patterns

* Tweaks to empty state patterns

* Tweaks to FAQ patterns

* Tweaks to feed patterns

* Tweaks to grid patterns

* Tweaks to pagination patterns

* Tweaks to pricing patterns

* Tweaks to description list patterns

* Tweaks to leaderboard patterns

* Add and update names and descriptions

* Ensure comments fields have labels

* Tweak recent additions
2025-04-01 13:45:26 -04:00
Kelsey Jackson
a1ba80fe8d add more app patterns 2025-04-01 00:36:12 -05:00
Kelsey Jackson
a3edcaf27d added a few more 2025-03-30 23:54:39 -05:00
Kelsey Jackson
595c40c0fa Merge pull request #820 from shoelace-style/kj/info-patterns
got three done
2025-03-28 04:18:46 -05:00
Kelsey Jackson
7e2f32a965 a few more 2025-03-28 04:17:12 -05:00
Kelsey Jackson
7b3bd8e027 fixed conflicts 2025-03-28 01:58:11 -05:00
Kelsey Jackson
5711819e9c Merge pull request #847 from shoelace-style/kj/pattern-additions
reworked feeds'
2025-03-28 01:55:02 -05:00
Kelsey Jackson
760c09781d reworked feeds' 2025-03-28 01:52:57 -05:00
Kelsey Jackson
5b59bee2ff added login patterns 2025-03-27 22:50:00 -05:00
Kelsey Jackson
ec1ffcefdc some newsletter signups 2025-03-27 15:35:18 -05:00
Lea Verou
d828dd3600 Use overviews in pattern subcategories (#826)
* Do not error if no pages

* Automatically set parents and tags for patterns

* Update overview.njk

* [WIP] Use overview pages for pattern listings

* Remove explicit parents

---------

Co-authored-by: lindsaym-fa <dev@lindsaym.design>
2025-03-26 15:22:48 -04:00
lindsaym-fa
0984cde1c5 Merge branch 'next' into pattern-main 2025-03-26 15:14:42 -04:00
Kelsey Jackson
5edad481f0 Merge branch 'kj/info-patterns' of github.com:shoelace-style/webawesome into kj/info-patterns 2025-03-26 13:49:29 -05:00
Kelsey Jackson
aab221dcb1 initial paywalls 2025-03-26 13:49:23 -05:00
lindsaym-fa
bbe6b4c6b3 Quick formatting adjustment 2025-03-24 16:45:19 -04:00
Kelsey Jackson
6ee5c27b9d updated social share 2025-03-24 14:24:26 -05:00
Lea Verou
c3af1174ca Merge branch 'next' into pattern-main 2025-03-21 17:43:21 -04:00
Kelsey Jackson
db5b967390 Merge pull request #822 from shoelace-style/kj/pattern-polish
a little cleanup and adding detail
2025-03-21 10:45:55 -05:00
Kelsey Jackson
8e3ffc4abe got three done 2025-03-20 17:16:22 -05:00
Kelsey Jackson
b5523c33b7 a little cleanup and adding detail 2025-03-19 17:17:02 -05:00
Kelsey Jackson
f031cab138 splitting up the old blog page 2025-03-19 12:09:42 -05:00
Kelsey Jackson
67c46a21dd created info category 2025-03-18 18:56:23 -05:00
Kelsey Jackson
c310ee1072 Merge pull request #804 from shoelace-style/kj/pattern-sidebar
Kj/pattern sidebar
2025-03-18 13:40:39 -05:00
Kelsey Jackson
8fff50d3d8 updated sidebar 2025-03-18 13:28:41 -05:00
Kelsey Jackson
944f9002c7 working on sidebar 2025-03-18 09:38:39 -05:00
Kelsey Jackson
eba2a75ffb Merge pull request #775 from shoelace-style/kj/app-pattern
App Patterns
2025-03-14 16:04:09 -05:00
Kelsey Jackson
c3de5a8915 removed old file 2025-03-14 16:02:39 -05:00
Kelsey Jackson
c18aa23d76 merging pattern-maiin 2025-03-14 16:00:32 -05:00
Kelsey Jackson
54e14a20c0 Merge pull request #703 from shoelace-style/kj/e-commerce-patterns
Kj/e commerce patterns
2025-03-14 15:57:58 -05:00
Kelsey Jackson
97b8e96a6e Merge branch 'pattern-main' into kj/e-commerce-patterns 2025-03-14 15:56:08 -05:00
Kelsey Jackson
55a362b741 Merge branch 'pattern-main' into kj/app-pattern 2025-03-14 15:55:45 -05:00
Kelsey Jackson
d9cafdafb7 Merge branch 'next' into pattern-main 2025-03-14 15:55:15 -05:00
Kelsey Jackson
81bf6865ec big switchover 2025-03-14 06:10:40 -05:00
lindsaym-fa
873eb47d18 Clean up utility usage and code formatting for product previews 2025-03-11 17:20:07 -04:00
lindsaym-fa
a564d3061f Clean up utility usage and code formatting for order summaries 2025-03-11 14:22:04 -04:00
Kelsey Jackson
ef865396f2 switching machines
:
2025-03-11 09:26:58 -05:00
Kelsey Jackson
903085257d removed store navigation 2025-03-07 11:12:39 -06:00
Kelsey Jackson
27237441a1 some heavy duty updates 2025-03-07 10:59:24 -06:00
Kelsey Jackson
b0291653f8 more polish 2025-03-06 22:43:13 -06:00
Kelsey Jackson
3962c50844 added review variant 2025-03-06 12:59:44 -06:00
Kelsey Jackson
314801c368 updated reviews 2025-03-05 16:40:36 -06:00
Kelsey Jackson
dc30ed8503 updated incentive images 2025-03-05 16:37:49 -06:00
lindsaym-fa
e12be798b8 Clean up utilities and formatting, replace placeholder text in order history 2025-03-04 14:30:00 -05:00
Kelsey Jackson
7f29f1b4ea started updating with style utilities 2025-03-04 13:09:41 -06:00
lindsaym-fa
3563d6a2dd Clean up utility usage and code formatting for category previews 2025-03-04 12:14:48 -05:00
Kelsey Jackson
007d93077f updated incentives 2025-03-04 11:04:31 -06:00
Kelsey Jackson
80b78aeb16 Merge branch 'pattern-main' into kj/e-commerce-patterns 2025-03-04 10:14:31 -06:00
Kelsey Jackson
8bc8ea85a5 Merge branch 'next' into pattern-main 2025-03-04 10:14:14 -06:00
Kelsey Jackson
61a6cd9fb9 updated image 2025-03-03 14:46:54 -06:00
Kelsey Jackson
7fb3fd5982 updated product preview 2025-03-03 14:22:45 -06:00
Kelsey Jackson
dc580b3351 committing to pull down changes 2025-03-03 10:38:10 -06:00
lindsaym-fa
74719d4c06 Refactor shopping cart patterns 2025-02-28 15:46:06 -05:00
Kelsey Jackson
47a7cdedd0 switching machines 2025-02-28 09:37:05 -06:00
lindsaym-fa
d906940726 Refactor product overview patterns 2025-02-27 17:04:10 -05:00
Kelsey Jackson
a782470c6a more polish 2025-02-27 11:47:56 -06:00
lindsaym-fa
de175ed4a1 Refactor product list patterns 2025-02-26 11:39:41 -05:00
lindsaym-fa
3612f8fdfa Add link style utilities 2025-02-26 11:39:11 -05:00
Kelsey Jackson
bc76df4b31 switching machines 2025-02-26 07:31:36 -06:00
lindsaym-fa
9ad7f4a6be Reorganize app patterns into separate pages 2025-02-25 12:02:29 -05:00
Kelsey Jackson
a7457630aa inital commit 2025-02-24 16:45:27 -06:00
Kelsey Jackson
e19928123f Merge branch 'pattern-main' into kj/e-commerce-patterns 2025-02-24 11:05:08 -06:00
Kelsey Jackson
e644862f58 Merge branch 'next' into pattern-main 2025-02-24 11:04:45 -06:00
Kelsey Jackson
76529c628e committing to bring branches up to next 2025-02-24 11:04:07 -06:00
Kelsey Jackson
62ded7b15a updated order history 2025-02-21 15:27:43 -06:00
Kelsey Jackson
65af9980f1 updated content for category preview 2025-02-21 10:08:33 -06:00
Kelsey Jackson
db592194fd updtated content 2025-02-21 09:45:19 -06:00
Kelsey Jackson
0f9ea14033 unlisted links 2025-02-19 11:12:05 -06:00
Kelsey Jackson
057b76a10a created e-commerce index 2025-02-19 10:09:42 -06:00
Kelsey Jackson
b466ba9d0f Merge pull request #748 from shoelace-style/lm/ecommerce-patterns-revisions
Suggested Revisions to E-commerce Patterns
2025-02-18 13:34:45 -06:00
Kelsey Jackson
9c979931da fixed conflicts 2025-02-18 13:33:46 -06:00
Kelsey Jackson
898311590a switching branches to merge 2025-02-18 13:31:53 -06:00
Kelsey Jackson
5f4510f355 more tweaks 2025-02-18 09:56:49 -06:00
Kelsey Jackson
b8eeb3db23 a little more polish 2025-02-14 07:42:36 -06:00
lindsaym-fa
8574270340 Add checkout form example 2025-02-13 23:06:50 -05:00
lindsaym-fa
aa042a0a6e Merge branch 'kj/e-commerce-patterns' into lm/ecommerce-patterns-revisions 2025-02-13 21:53:47 -05:00
Kelsey Jackson
fe81a41a6b Merge branch 'kj/e-commerce-patterns' of github.com:shoelace-style/webawesome into kj/e-commerce-patterns 2025-02-13 15:09:27 -06:00
Kelsey Jackson
f3628ad2d8 tweaked customer review 2025-02-13 15:08:30 -06:00
lindsaym-fa
169337077d Add docs layout for patterns with stylesheet 2025-02-13 15:49:09 -05:00
lindsaym-fa
406d9a9708 Update category descriptions and headings 2025-02-13 15:47:21 -05:00
lindsaym-fa
faaf75c0a2 Add "What's a Pattern?" and "Using Patterns" to index 2025-02-13 13:22:46 -05:00
Kelsey Jackson
13b67db869 Merge branch 'kj/e-commerce-patterns' of github.com:shoelace-style/webawesome into kj/e-commerce-patterns 2025-02-13 10:35:40 -06:00
Kelsey Jackson
b7fdda4b03 cleaned up checkout form 2025-02-13 10:35:31 -06:00
lindsaym-fa
d60e675702 Reimplement checkout form revisions 2025-02-13 10:23:46 -05:00
lindsaym-fa
414a318f0b Revert checkout form changes in attempt to improve diff 2025-02-13 10:00:45 -05:00
lindsaym-fa
ff8ed89645 Merge branch 'kj/e-commerce-patterns' into lm/ecommerce-patterns-revisions 2025-02-13 09:55:45 -05:00
lindsaym-fa
e027ab1291 Fix typo in file name 2025-02-13 09:52:56 -05:00
lindsaym-fa
d8f97b15b4 Add missing file extension 2025-02-13 09:52:00 -05:00
Kelsey Jackson
a8ed6f2c19 tweaked order summary 2025-02-12 16:14:21 -06:00
Kelsey Jackson
c7f5dc69ad cleaned up shopping cart 2025-02-12 15:07:03 -06:00
lindsaym-fa
42381722c5 Re-add navigation to "Product Features" carousel 2025-02-12 15:10:42 -05:00
lindsaym-fa
b2f9ec573f Merge branch 'kj/e-commerce-patterns' into lm/ecommerce-patterns-revisions 2025-02-12 15:05:33 -05:00
lindsaym-fa
b85e59b8ca "Checkout Form" revisions 2025-02-12 15:01:29 -05:00
Kelsey Jackson
8bd69694ce tweak category preview 2025-02-12 13:28:00 -06:00
Kelsey Jackson
7672923479 tweaked order history 2025-02-12 12:05:43 -06:00
Kelsey Jackson
c94006c6aa tweaked product overview 2025-02-12 11:21:17 -06:00
Kelsey Jackson
91ce2f2271 cleaned up product list 2025-02-12 10:08:51 -06:00
lindsaym-fa
9c609e44de "Category Filters" revisions + wa-placeholder utility 2025-02-11 17:06:40 -05:00
lindsaym-fa
28a8e4250a Revisions to "Product Features" 2025-02-11 17:05:38 -05:00
Kelsey Jackson
656a38cd3d updated prouct preview 2025-02-11 11:58:52 -06:00
Kelsey Jackson
9b90e56b45 more filter work 2025-02-11 10:57:29 -06:00
Kelsey Jackson
0c84a2ff37 Merge branch 'pattern-main' into kj/e-commerce-patterns 2025-02-11 09:11:36 -06:00
Kelsey Jackson
511cca7931 Merge branch 'next' into pattern-main 2025-02-11 09:10:58 -06:00
Kelsey Jackson
2069d223f0 adding some polish 2025-02-11 09:10:18 -06:00
Kelsey Jackson
37d2455965 updated descriptions 2025-02-10 14:37:00 -06:00
Kelsey Jackson
85b05fc655 editing descriptions 2025-02-07 15:50:15 -06:00
Kelsey Jackson
52328d9805 fixing conflicts 2025-02-07 14:24:40 -06:00
Kelsey Jackson
2c89c5f510 switching machines 2025-02-07 11:42:00 -06:00
Kelsey Jackson
5ac9e8cff1 Merge branch 'pattern-main' into kj/e-commerce-patterns 2025-02-05 12:16:50 -06:00
Kelsey Jackson
e238e35b3b Merge branch 'next' into pattern-main 2025-02-05 12:16:22 -06:00
Kelsey Jackson
c6d8d37871 switching machines 2025-01-31 09:43:40 -06:00
Kelsey Jackson
4dddb183fd updated width of container 2025-01-29 13:39:51 -06:00
Kelsey Jackson
d49ed53e9a added utilities to order-summary 2025-01-29 13:33:48 -06:00
Kelsey Jackson
99e2ffc17b added utilities to order history 2025-01-29 13:06:14 -06:00
Kelsey Jackson
97c79c29b9 Merge branch 'kj/e-commerce-patterns' of github.com:shoelace-style/webawesome into kj/e-commerce-patterns 2025-01-29 12:32:10 -06:00
Kelsey Jackson
7da9f53e27 filter work 2025-01-28 15:04:03 -06:00
Kelsey Jackson
a9cc02193f updated some patterns 2025-01-27 13:30:18 -06:00
Kelsey Jackson
9ad1c8f060 updated product list patterns 2025-01-27 13:17:09 -06:00
Kelsey Jackson
e0eaea2024 upated the product detail patterns 2025-01-27 11:20:40 -06:00
Kelsey Jackson
a8b7b6a93f Merge branch 'pattern-main' into kj/e-commerce-patterns 2025-01-23 08:56:50 -06:00
Kelsey Jackson
581e47043a Merge branch 'pattern-main' into kj/e-commerce-patterns 2025-01-22 18:50:38 -06:00
Kelsey Jackson
9ffe0cfe3c Merge branch 'next' into pattern-main 2025-01-22 18:50:11 -06:00
Kelsey Jackson
45df4a924f commiting to switch machines 2025-01-22 14:08:14 -06:00
Kelsey Jackson
43eed9d15e Merge branch 'next' into kj/e-commerce-patterns 2025-01-22 12:30:32 -06:00
Kelsey Jackson
a0ff411463 commiting to merge in next 2025-01-22 12:27:47 -06:00
Kelsey Jackson
04cd027c7e started split image pattern: 2025-01-17 14:44:14 -06:00
Kelsey Jackson
a68a157ebc switching machines 2025-01-15 16:41:13 -06:00
Kelsey Jackson
b2a24a3b52 Merge branch 'pattern-main' into kj/e-commerce-patterns 2025-01-09 11:31:46 -06:00
Kelsey Jackson
cbb456ffda switching machines 2025-01-09 11:29:32 -06:00
Kelsey Jackson
e0038c3125 Initial Commit 2025-01-09 10:59:33 -06:00
Kelsey Jackson
3e5d3120b1 Merge branch 'next' into kj/e-commerce-patterns 2025-01-08 16:19:29 -06:00
Kelsey Jackson
020f4cbaed building out the filter 2025-01-07 11:26:29 -06:00
Kelsey Jackson
9106bb88cf fixed merge conflicts 2025-01-06 10:34:51 -06:00
Kelsey Jackson
a822ab98c8 Merge branch 'next' into kj/e-commerce-patterns 2025-01-02 11:57:42 -06:00
Kelsey Jackson
026777c085 started invoice table 2024-12-18 09:53:15 -06:00
Kelsey Jackson
6d87b9b24a Merge branch 'next' into kj/e-commerce-patterns 2024-12-17 12:23:27 -06:00
Kelsey Jackson
d5affa01e9 dividing up e-commerce patterns 2024-12-11 15:06:01 -06:00
300 changed files with 4274 additions and 9304 deletions

View File

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

View File

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

View File

@@ -1,7 +1,5 @@
*.hbs
*.md
!docs/docs/patterns/**/*.md
docs/docs/patterns/blog-news/post-list.md
.cache
.github
cspell.json

View File

@@ -148,7 +148,6 @@
"scrollbars",
"scrollend",
"scroller",
"Scrollers",
"Segoe",
"semibold",
"shadowrootmode",

View File

@@ -5,6 +5,7 @@ import { copyCodePlugin } from './_utils/copy-code.js';
import { currentLink } from './_utils/current-link.js';
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 { readFile } from 'fs/promises';
@@ -21,10 +22,12 @@ 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');
const globalData = {
package: packageData,
isAlpha,
layout: 'page.njk',
server: {
head: '',
@@ -33,21 +36,25 @@ const globalData = {
},
};
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/**');
eleventyConfig.ignores.add('**/layout/**');
eleventyConfig.ignores.add('**/patterns/**');
eleventyConfig.ignores.add('**/style-utilities/**');
eleventyConfig.ignores.add('**/components/code-demo.md');
eleventyConfig.ignores.add('**/components/viewport-demo.md');
}
// Add template data
for (let name in globalData) {
eleventyConfig.addGlobalData(name, globalData[name]);
@@ -102,6 +109,9 @@ export default function (eleventyConfig) {
// Helpers
// Remove elements that have [data-alpha="remove"]
eleventyConfig.addPlugin(removeDataAlphaElements({ isAlpha }));
// Use our own markdown instance
eleventyConfig.setLibrary('md', markdown);
@@ -153,15 +163,6 @@ export default function (eleventyConfig) {
]),
);
eleventyConfig.addPreprocessor('unpublished', '*', (data, content) => {
if (data.unpublished && process.env.ELEVENTY_RUN_MODE === 'build') {
// Exclude "unpublished" pages from final builds.
return false;
}
return content;
});
// Build the search index
eleventyConfig.addPlugin(
searchPlugin({

View File

@@ -1 +1 @@
export { hueRanges as default } from '../assets/data/index.js';
export { hueRanges as default } from '../assets/scripts/tweak/data.js';

View File

@@ -4,6 +4,7 @@
{% 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>
@@ -19,7 +20,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="" mobile-breakpoint="1140">
<wa-page view="desktop" disable-navigation-toggle="">
<header slot="header" class="wa-split">
{# Logo #}
<div id="docs-branding">
@@ -32,13 +33,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="wa-desktop-only">{{ package.version }}</small>
<wa-badge variant="warning" appearance="filled" class="wa-desktop-only">Alpha</wa-badge>
<small id="version-number" class="only-desktop">{{ package.version }}</small>
<wa-badge variant="warning" appearance="filled" class="only-desktop">Alpha</wa-badge>
</div>
<div id="docs-toolbar" class="wa-cluster wa-gap-xs">
{# Desktop selectors #}
<div class="wa-desktop-only wa-cluster wa-gap-xs">
<div class="only-desktop wa-cluster wa-gap-xs">
{% include "preset-theme-selector.njk" %}
{% include "color-scheme-selector.njk" %}
</div>
@@ -47,7 +48,7 @@
<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="wa-desktop-only">/</kbd>
<kbd slot="suffix" class="only-desktop">/</kbd>
</wa-button>
{# Login #}

View File

@@ -1,7 +1,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="{{ description }}">
{% if noindex or unlisted %}<meta name="robots" content="noindex">{% endif %}
{% if noindex %}<meta name="robots" content="noindex">{% endif %}
<title>{{ title }}</title>
@@ -32,8 +32,7 @@
<script type="module" src="/assets/scripts/theme-picker.js"></script>
{# Preset Theme #}
{% if noTheme %}
{% elif forceTheme %}
{% if forceTheme %}
<link id="theme-stylesheet" rel="stylesheet" id="theme-stylesheet" href="/dist/styles/themes/{{ forceTheme }}.css" render="blocking" fetchpriority="high" />
{% else %}
<noscript><link id="theme-stylesheet" rel="stylesheet" id="theme-stylesheet" href="/dist/styles/themes/default.css" render="blocking" fetchpriority="high" /></noscript>

View File

@@ -1,5 +1,5 @@
{%- if not page.data.unlisted -%}
{% if page.url %}<a href="{{ page.url }}"{{ page.data.keywords | attr('data-keywords') }}>{% endif %}
<a href="{{ page.url }}"{{ page.data.keywords | attr('data-keywords') }}>
<wa-card with-header>
<div slot="header">
{% include "svgs/" + (page.data.icon or "thumbnail-placeholder") + ".njk" ignore missing %}
@@ -9,5 +9,5 @@
<div class="wa-caption-s">{{ pageSubtitle }}</div>
{%- endif %}
</wa-card>
{% if page.url %}</a>{% endif %}
</a>
{% endif %}

View File

@@ -2,7 +2,7 @@
<wa-select appearance="filled" size="small" value="default" pill class="preset-theme-selector">
<wa-icon slot="prefix" name="paintbrush" variant="regular"></wa-icon>
{% for theme in collections.theme | sort %}
<wa-option value="{{ theme.page.fileSlug }}">
<wa-option value="{{ theme.page.fileSlug }}"{{ ' data-alpha="remove"' if theme.noAlpha }}>
{{ theme.data.title }}
</wa-option>
{% endfor %}

View File

@@ -1,4 +1,4 @@
<wa-dialog id="site-search" without-header light-dismiss>
<wa-dialog id="site-search" no-header light-dismiss>
<div id="site-search-container">
{# Header #}
<header>

View File

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

View File

@@ -1,3 +0,0 @@
<svg width="72" height="72" viewBox="0 0 72 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M63 6.32811V61.3125C63 66.375 56.6719 68.9062 53.2969 65.25L49.9219 61.7344C43.7344 55.2656 35.7188 51.1875 27 50.0625V64.125C27 68.4844 23.3438 72 19.125 72H16.875C12.5156 72 9 68.4844 9 64.125V49.5C3.9375 49.5 0 45.5625 0 40.5V27C0 22.0781 3.9375 18 9 18H20.1094L24.1875 17.8594C34.0312 17.2969 43.1719 13.0781 49.9219 5.90624L53.2969 2.39061C56.6719 -1.26564 63 1.12499 63 6.32811ZM22.5 49.6406L20.1094 49.5H13.5V64.125C13.5 66.0937 14.9062 67.5 16.875 67.5H19.125C20.9531 67.5 22.5 66.0937 22.5 64.125V49.6406ZM56.5312 5.48436L53.1562 8.99999C47.25 15.1875 39.6562 19.5469 31.5 21.375V46.2656C39.6562 48.0937 47.25 52.3125 53.1562 58.6406L56.5312 62.1562C57.2344 62.8594 58.5 62.2969 58.5 61.3125V6.32811C58.5 5.20311 57.2344 4.78124 56.5312 5.48436ZM27 22.0781C26.1562 22.2187 25.3125 22.3594 24.4688 22.3594L20.25 22.5H9C6.46875 22.5 4.5 24.6094 4.5 27V40.5C4.5 43.0312 6.46875 45 9 45H20.25L24.4688 45.2812C25.3125 45.4219 26.1562 45.4219 27 45.5625V22.0781ZM69.75 27C70.875 27 72 28.125 72 29.25V38.25C72 39.5156 70.875 40.5 69.75 40.5C68.4844 40.5 67.5 39.5156 67.5 38.25V29.25C67.5 28.125 68.4844 27 69.75 27Z" fill="var(--wa-color-neutral-on-quiet"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,10 +0,0 @@
<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="var(--wa-color-surface-default)" stroke="var(--wa-color-surface-border)" stroke-width="2"/>
<rect x="7" y="8" width="41" height="5" fill="var(--wa-color-neutral-on-quiet)"/>
<rect x="7" y="19" width="23" height="14" rx="3" fill="var(--wa-color-neutral-fill-normal)"/>
<rect x="35" y="19" width="23" height="14" rx="3" fill="var(--wa-color-neutral-fill-normal)"/>
<rect x="63" y="19" width="23" height="14" rx="3" fill="var(--wa-color-neutral-fill-normal)"/>
<rect x="7" y="36" width="23" height="14" rx="3" fill="var(--wa-color-neutral-fill-normal)"/>
<rect x="35" y="36" width="23" height="14" rx="3" fill="var(--wa-color-neutral-fill-normal)"/>
<rect x="63" y="36" width="23" height="14" rx="3" fill="var(--wa-color-neutral-fill-normal)"/>
</svg>

Before

Width:  |  Height:  |  Size: 986 B

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,31 +0,0 @@
<svg width="96" height="64" viewBox="0 0 96 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 1H84C90.0751 1 95 5.92487 95 12V52C95 58.0751 90.0751 63 84 63H12C5.92487 63 1 58.0751 1 52V12C1 5.92487 5.92487 1 12 1Z" fill="var(--wa-color-surface-default)"/>
<path d="M12 1H84C90.0751 1 95 5.92487 95 12V52C95 58.0751 90.0751 63 84 63H12C5.92487 63 1 58.0751 1 52V12C1 5.92487 5.92487 1 12 1Z" stroke="var(--wa-color-surface-border)" stroke-width="2"/>
<rect x="12" y="12" width="24" height="4" rx="2" fill="var(--wa-color-neutral-fill-quiet)"/>
<rect x="12" y="21" width="24" height="4" rx="2" fill="var(--wa-color-neutral-fill-quiet)"/>
<rect x="12" y="30" width="24" height="4" rx="2" fill="var(--wa-color-neutral-fill-quiet)"/>
<rect x="44" y="12" width="24" height="4" rx="2" fill="var(--wa-color-neutral-fill-quiet)"/>
<rect x="44" y="21" width="24" height="4" rx="2" fill="var(--wa-color-neutral-fill-quiet)"/>
<rect x="44" y="30" width="24" height="4" rx="2" fill="var(--wa-color-neutral-fill-quiet)"/>
<rect x="76" y="12" width="20" height="4" rx="2" fill="url(#paint0_linear_302_2)"/>
<rect x="76" y="21" width="20" height="4" rx="2" fill="url(#paint1_linear_302_2)"/>
<rect x="76" y="30" width="20" height="4" rx="2" fill="url(#paint2_linear_302_2)"/>
<rect x="4" y="44" width="88" height="16" rx="8" fill="var(--wa-color-neutral-fill-normal)"/>
<path d="M85.8438 51.6562C86.0469 51.8438 86.0469 52.1719 85.8438 52.3594L82.8438 55.3594C82.6562 55.5625 82.3281 55.5625 82.1406 55.3594C81.9375 55.1719 81.9375 54.8438 82.1406 54.6562L84.7812 52L82.1406 49.3594C81.9375 49.1719 81.9375 48.8438 82.1406 48.6562C82.3281 48.4531 82.6562 48.4531 82.8438 48.6562L85.8438 51.6562Z" fill="var(--wa-color-neutral-border-loud)"/>
<path d="M10.1406 51.6562L13.1406 48.6562C13.3281 48.4531 13.6562 48.4531 13.8438 48.6562C14.0469 48.8438 14.0469 49.1719 13.8438 49.3594L11.2031 52L13.8438 54.6562C14.0469 54.8438 14.0469 55.1719 13.8438 55.3594C13.6562 55.5625 13.3281 55.5625 13.1406 55.3594L10.1406 52.3594C9.9375 52.1719 9.9375 51.8438 10.1406 51.6562Z" fill="var(--wa-color-neutral-border-loud)"/>
<path d="M20 52C20 49.7909 21.7909 48 24 48H48C50.2091 48 52 49.7909 52 52V52C52 54.2091 50.2091 56 48 56H24C21.7909 56 20 54.2091 20 52V52Z" fill="var(--wa-color-neutral-border-loud)"/>
<defs>
<linearGradient id="paint0_linear_302_2" x1="76" y1="14" x2="96" y2="14" gradientUnits="userSpaceOnUse">
<stop offset="0.533654" stop-color="var(--wa-color-neutral-fill-quiet)"/>
<stop offset="1" stop-color="var(--wa-color-neutral-fill-quiet)" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint1_linear_302_2" x1="76" y1="23" x2="96" y2="23" gradientUnits="userSpaceOnUse">
<stop offset="0.533654" stop-color="var(--wa-color-neutral-fill-quiet)"/>
<stop offset="1" stop-color="var(--wa-color-neutral-fill-quiet)" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint2_linear_302_2" x1="76" y1="32" x2="96" y2="32" gradientUnits="userSpaceOnUse">
<stop offset="0.533654" stop-color="var(--wa-color-neutral-fill-quiet)"/>
<stop offset="1" stop-color="var(--wa-color-neutral-fill-quiet)" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,14 +1,14 @@
<div class="showcase-examples-wrapper" aria-hidden="true" data-no-outline>
<div class="showcase-examples">
<wa-card>
<wa-card with-header with-footer>
<div slot="header" class="wa-split">
<h3 class="wa-heading-m">Your Cart</h3>
<wa-icon-button name="xmark" tabindex="-1"></wa-icon-button>
</div>
<div class="wa-stack wa-gap-xl">
<div class="wa-flank">
<wa-avatar shape="rounded" style="--background-color: var(--wa-color-green-60); --text-color: var(--wa-color-green-95);">
<wa-icon slot="icon" name="sword-laser"></wa-icon>
<wa-avatar shape="rounded" style="--size: 3em; --background-color: var(--wa-color-green-60); --text-color: var(--wa-color-green-95);">
<wa-icon slot="icon" name="sword-laser" family="duotone" style="font-size: 1.5em;"></wa-icon>
</wa-avatar>
<div class="wa-stack wa-gap-2xs">
<div class="wa-split wa-gap-2xs">
@@ -23,8 +23,8 @@
</div>
<wa-divider></wa-divider>
<div class="wa-flank">
<wa-avatar shape="rounded" style="--background-color: var(--wa-color-cyan-60); --text-color: var(--wa-color-cyan-95);">
<wa-icon slot="icon" name="robot-astromech"></wa-icon>
<wa-avatar shape="rounded" style="--size: 3em; --background-color: var(--wa-color-cyan-60); --text-color: var(--wa-color-cyan-95);">
<wa-icon slot="icon" name="robot-astromech" family="duotone" style="font-size: 1.5em;"></wa-icon>
</wa-avatar>
<div class="wa-stack wa-gap-2xs">
<div class="wa-split wa-gap-2xs">
@@ -52,7 +52,7 @@
</wa-card>
<wa-card>
<wa-avatar shape="rounded" style="--size: 1.9lh; float: left; margin-right: var(--wa-space-m);">
<wa-icon slot="icon" name="hat-wizard" style="font-size: 1.75em;"></wa-icon>
<wa-icon slot="icon" name="hat-wizard" family="duotone" style="font-size: 1.75em;"></wa-icon>
</wa-avatar>
<p class="wa-body-l" style="margin: 0;">&ldquo;All we have to decide is what to do with the time that is given to us. There are other forces at work in this world, Frodo, besides the will of evil.&rdquo;</p>
</wa-card>
@@ -69,7 +69,7 @@
<a href="#" tabindex="-1" class="wa-body-s">I forgot my password</a>
</div>
</wa-card>
<wa-card>
<wa-card with-footer>
<div class="wa-stack">
<div class="wa-split">
<h3 class="wa-heading-m">To-Do</h3>
@@ -140,196 +140,5 @@
</div>
</div>
</wa-card>
<wa-card>
<div class="wa-stack">
<div class="wa-flank:end">
<h3 id="odds-label" class="wa-heading-m">Tell Me the Odds</h3>
<wa-switch size="large" aria-labelledby="odds-label"></wa-switch>
</div>
<p class="wa-body-s">Allow protocol droids to inform you of probabilities, such as the success rate of navigating an asteroid field. We recommend setting this to "Never."</p>
</div>
</wa-card>
<wa-card>
<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-center">
<dt><wa-icon name="user" label="Name" fixed-width></wa-icon></dt>
<dd>Tom Bombadil</dd>
</div>
<div class="wa-flank wa-align-items-center">
<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-center">
<dt><wa-icon name="coin-vertical" fixed-width></wa-icon></dt>
<dd>Paid with copper pennies</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>
<wa-card>
<div class="wa-stack">
<div class="wa-split">
<div class="wa-cluster wa-heading-l">
<wa-icon name="book-sparkles"></wa-icon>
<h3>Fellowship</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">Carry great power (and great responsibility).</p>
<wa-button variant="brand">Get this Plan</wa-button>
</div>
<div slot="footer" class="wa-stack wap-gap-s">
<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">9 users</span>
</div>
<div class="wa-flank">
<wa-icon name="ring" fixed-width></wa-icon>
<span class="wa-caption-m">1 ring</span>
</div>
<div class="wa-flank">
<wa-icon name="chess-rook" fixed-width></wa-icon>
<span class="wa-caption-m">API access to Isengard</span>
</div>
<div class="wa-flank">
<wa-icon name="feather" fixed-width></wa-icon>
<span class="wa-caption-m">Priority eagle support</span>
</div>
</div>
</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">Migs Mayfeld</h3 class="wa-heading-s">
<wa-badge pill>Admin</wa-badge>
</div>
<span class="wa-caption-m">Bounty Hunter</span>
</div>
<wa-avatar image="https://images.unsplash.com/photo-1633268335280-a41fbde58707?q=80&w=3348&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" label="Avatar of a man wearing a sci-fi helmet (Photograph by Nandu Vasudevan)"></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>
<div class="wa-flank:end">
<a href="" class="wa-flank wa-link-plain">
<wa-avatar shape="rounded" style="--background-color: var(--wa-color-yellow-90); --text-color: var(--wa-color-yellow-50)">
<wa-icon slot="icon" name="egg-fried"></wa-icon>
</wa-avatar>
<div class="wa-gap-2xs wa-stack">
<span class="wa-heading-s">Second Breakfast</span>
<span class="wa-caption-m">19 Items</span>
</div>
</a>
<wa-dropdown>
<wa-icon-button id="more-actions-2" slot="trigger" name="ellipsis-vertical" label="View menu"></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">View menu</wa-tooltip>
</div>
</wa-card>
<wa-card with-header with-footer>
<div slot="header" class="wa-stack wa-gap-xs">
<h2 class="wa-heading-m">Decks</h2>
</div>
<div class="wa-stack wa-gap-xl">
<p class="wa-caption-m">You havent created any decks yet. Get started by selecting an aspect that matches your play style.</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-blue-90);color: var(--wa-color-blue-50);">
<wa-icon slot="icon" name="shield"></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">
Vigilance <wa-icon name="arrow-right"></wa-icon>
</span>
<p class="wa-caption-m">
Protect, defend, and restore as you ready heavy-hitters.
</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-50);">
<wa-icon slot="icon" name="chevrons-up"></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">
Command <wa-icon name="arrow-right"></wa-icon>
</span>
<p class="wa-caption-m">
Build imposing armies and stockpile resources.
</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-50);">
<wa-icon slot="icon" name="explosion"></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">
Aggression <wa-icon name="arrow-right"></wa-icon>
</span>
<p class="wa-caption-m">
Relentlessly deal damage and apply pressure to your opponent.
</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-yellow-90);color: var(--wa-color-yellow-50);">
<wa-icon slot="icon" name="moon-stars"></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">
Cunning <wa-icon name="arrow-right"></wa-icon>
</span>
<p class="wa-caption-m">
Disrupt and frustrate your opponent with dastardly tricks.
</p>
</div>
</a>
</div>
</div>
<div slot="footer">
<a href="" class="wa-cluster wa-gap-xs">
<span>Or start a deck from scratch</span>
<wa-icon name="arrow-right"></wa-icon>
</a>
</div>
</wa-card>
</div>
</div>

View File

@@ -2,7 +2,6 @@
<html lang="en" data-fa-kit-code="b10bfbde90" data-cdn-url="{% cdnUrl %}">
<head>
{% include 'head.njk' %}
{% block head %}{% endblock %}
</head>
<body class="layout-{{ layout | stripExtension }}">

View File

@@ -255,7 +255,7 @@
{# Importing #}
<h2>Importing</h2>
<p>
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.
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.
</p>
<wa-tab-group label="How would you like to import this component?">

View File

@@ -122,8 +122,19 @@
</div>
<div class="popup">
{% if hue === 'gray' %}
<swatch-select label="Gray undertone" shape="circle" :values="hues" v-model="grayColor"></swatch-select>
<wa-radio-group class="core-color" orientation="horizontal" v-model="grayColor">
{% for h in hues -%}
{%- if h !== 'gray' -%}
<wa-radio-button id="gray-undertone-{{ h }}" value="{{ h }}" label="{{ h | capitalize }}" style="--color: var(--wa-color-{{ h }})"></wa-radio-button>
<wa-tooltip for="gray-undertone-{{ h }}" hoist>
{{ h | capitalize }}
</wa-tooltip>
{%- endif -%}
{%- endfor -%}
<div slot="label">
Gray undertone
</div>
</wa-radio-group>
<div class="decorated-slider gray-chroma-slider" :style="{'--max': maxGrayChroma}">
<wa-slider name="gray-chroma" v-model="grayChroma" ref="grayChromaSlider"
value="0" min="0" :max="maxGrayChroma" step="0.01"

View File

@@ -5,22 +5,35 @@
{% extends '../_includes/base.njk' %}
{% block head %}
<script>
globalThis.wa_data ??= {};
wa_data.baseTheme = "{{ page.fileSlug }}";
wa_data.themes = {
{% for theme in collections.theme -%}
"{{ theme.fileSlug }}": {
"title": "{{ theme.data.title }}",
"palette": "{{ theme.data.palette }}",
"brand": "{{ theme.data.brand }}"
},
{% endfor %}
};
wa_data.palettes = {
{% for palette in collections.palette -%}
"{{ palette.fileSlug }}": {
"title": "{{ palette.data.title }}",
},
{% endfor %}
};
</script>
<link href="{{ page.url }}../remix.css" rel="stylesheet">
<script type="module" src="{{ page.url }}../edit/index.js"></script>
<script src="{{ page.url }}../remix.js" type="module"></script>
{% endblock %}
{% block header %}
<script>
if (location.pathname.endsWith('/custom/') && !location.search) {
location.href = "../edit/";
}
</script>
<div id="theme-app" data-theme-id="{{ page.fileSlug }}">
<iframe ref="preview" :src="'{{ page.url }}demo.html' + urlParams" src='{{ page.url }}demo.html' id="demo"></iframe>
<iframe src='{{ page.url }}demo.html' id="demo"></iframe>
{% if page.fileSlug !== 'custom' %}
<wa-details id="mix_and_match" class="wa-gap-m" :open="saved || unsavedChanges">
<wa-details id="mix_and_match" class="wa-gap-m" >
<h4 slot="summary" data-no-anchor data-no-outline id="remix">
<wa-icon name="arrows-rotate"></wa-icon>
Remix this theme
@@ -28,64 +41,92 @@ if (location.pathname.endsWith('/custom/') && !location.search) {
<wa-tooltip for="what-is-remixing">Customize this theme by changing its colors and/or remixing it with design elements from other themes!</wa-tooltip>
</h4>
<wa-select name="palette" label="Color palette" clearable v-model="theme.palette">
<wa-icon name="swatchbook" slot="prefix" variant="regular"></wa-icon>
<wa-option v-for="(palette, paletteId) in palettes" :label="palette.title" :value="paletteId === baseTheme.palette ? '' : paletteId">
<palette-card :palette="paletteId" size="small">
<template #extra>
<wa-badge v-if="paletteId === baseTheme.palette" variant="neutral" appearance="outlined">Theme default</wa-badge>
</template>
</palette-card>
</wa-option>
</wa-select>
<color-select :model-value="computed.brand" @update:model-value="value => theme.brand = value" label="Brand color"
:values="hues"></color-select>
<wa-select name="colors" class="theme-colors-select" label="Color contrast from…" value="" clearable v-model="theme.colors">
<wa-select name="colors" label="Colors from…" value="" clearable>
<wa-icon name="palette" slot="prefix" variant="regular"></wa-icon>
<template v-for="(themeMeta, themeId) in themes">
<wa-option v-if="themeId !== 'custom'" :label="themeMeta.title" :value="themeId === computed.colors ? '' : themeId">
<theme-card :theme="themeId" type="colors" :rest="{base: computed.base, palette: computed.palette, brand: computed.brand}" size="small">
<template #extra>
<wa-badge v-if="themeId === theme.base" variant="neutral" appearance="outlined">This theme</wa-badge>
</template>
</theme-card>
{% for theme in collections.theme | sort %}
{% set currentTheme = theme.fileSlug == page.fileSlug %}
<wa-option label="{{ theme.data.title }}" value="{{ theme.fileSlug if not currentTheme }}" {{ (theme.fileSlug if currentTheme) | attr('data-id') }}>
<wa-card with-header>
<div slot="header">
{% include "svgs/theme-color.njk" %}
</div>
<span class="page-name">
{{ theme.data.title }}
{% if theme.data.isPro %}<wa-badge class="pro">PRO</wa-badge>{% endif %}
{% if currentTheme %}<wa-badge variant="neutral" appearance="outlined">This theme</wa-badge>{% endif %}
</span>
</wa-card>
</wa-option>
</template>
{% endfor %}
</wa-select>
<wa-select name="typography" label="Typography from…" clearable v-model="theme.typography">
<wa-icon name="font-case" slot="prefix"></wa-icon>
<wa-select name="palette" label="Palette" clearable>
<wa-icon name="swatchbook" slot="prefix" variant="regular"></wa-icon>
{% set defaultPalette = palette %}
{% for palette in collections.palette | sort %}
{% set currentPalette = palette.fileSlug == defaultPalette %}
<wa-option label="{{ palette.data.title }}" value="{{ palette.fileSlug if not currentPalette }}" {{ (palette.fileSlug if currentPalette) | attr('data-id') }}>
<wa-card with-header>
<div slot="header">
{% include "svgs/" + (palette.data.icon or "thumbnail-placeholder") + ".njk" ignore missing %}
</div>
<span class="page-name">
{{ palette.data.title }}
{% if palette.data.isPro %}<wa-badge class="pro">PRO</wa-badge>{% endif %}
{% if currentPalette %}<wa-badge variant="neutral" appearance="outlined">Theme default</wa-badge>{% endif %}
</span>
</wa-card>
</wa-option>
{% endfor %}
{% set palette = defaultPalette %}
</wa-select>
<wa-option v-for="(themeMeta, themeId) in themes" :label="themeMeta.title" :value="themeId === theme.base ? '' : themeId">
<fonts-card :theme="themeId" size="small">
<template #extra>
<wa-badge v-if="themeId === theme.base" variant="neutral" appearance="outlined">This theme</wa-badge>
</template>
</fonts-card>
</wa-option>
<wa-select name="brand" label="Brand color" value="" clearable>
<div class="selected-swatch" slot="prefix"></div>
{% for hue in hues %}
{% set currentBrand = hue == brand %}
<wa-option label="{{ hue | capitalize }}" value="{{ hue if not currentBrand }}" {{ (hue if currentBrand) | attr('data-id') }} style="--color: var(--wa-color-{{ hue }})">
{{ hue | capitalize }}
{% if currentBrand %}<wa-badge variant="neutral" appearance="outlined">Theme default</wa-badge>{% endif %}
</wa-option>
{% endfor %}
</wa-select>
<wa-select name="typography" label="Typography from…" clearable>
<wa-icon name="font-case" slot="prefix"></wa-icon>
{% for theme in collections.theme | sort %}
{% set currentTheme = theme.fileSlug == page.fileSlug %}
<wa-option label="{{ theme.data.title }}" value="{{ theme.fileSlug if not currentTheme }}" {{ (theme.fileSlug if currentTheme) | attr('data-id') }}>
<wa-card with-header>
<div slot="header">
{% include "svgs/theme-typography.njk" %}
</div>
<span class="page-name">
{{ theme.data.title }}
{% if theme.data.isPro %}<wa-badge class="pro">PRO</wa-badge>{% endif %}
{% if currentTheme %}<wa-badge variant="neutral" appearance="outlined">This theme</wa-badge>{% endif %}
</span>
</wa-card>
</wa-option>
{% endfor %}
</wa-select>
</wa-details>
{% endif %}
<h2>Color</h2>
{% set paletteURL = '/docs/palettes/' + palette + '/' %}
<div class="index-grid">
{% if page.fileSlug === 'custom' %}
<palette-card :palette="computed.palette" subtitle="Color palette"></palette-card>
{% else %}
{% set themePage = page %}
{% set paletteURL = '/docs/palettes/' + palette + '/' %}
{% set page = paletteURL | getCollectionItemFromUrl %}
{% set pageSubtitle = "Default color palette" %}
{% include 'page-card.njk' %}
{% set page = themePage %}
{% endif %}
<wa-card class="wa-palette-{{ palette }}" style="--header-background: var(--wa-color-{{ brand }})"
:class="`wa-palette-${computed.palette}`" :style="{'--header-background': palettes[computed.palette]?.colors[computed.brand]?.key}">
{% set themePage = page %}
{% set page = paletteURL | getCollectionItemFromUrl %}
{% set pageSubtitle = "Default color palette" %}
{% include 'page-card.njk' %}
{% set page = themePage %}
<wa-card style="--header-background: var(--wa-color-{{ brand }})" class="wa-palette-{{ palette }}">
<div slot="header"></div>
<div class="page-name" v-content="capitalize(computed.brand)">{{ brand | capitalize }}</div>
<div class="wa-caption-s">{{ 'Brand color' if page.fileSlug === 'custom' else 'Default brand color' }}</div>
<div class="page-name">{{ brand | capitalize }}</div>
<div class="wa-caption-s">Default brand color</div>
</wa-card>
</div>
{% endblock %}
@@ -98,25 +139,7 @@ if (location.pathname.endsWith('/custom/') && !location.search) {
You can import this theme from the Web Awesome CDN.
{% set stylesheet = 'styles/themes/' + page.fileSlug + '.css' %}
<wa-tab-group class="import-stylesheet-code">
<wa-tab panel="html">In HTML</wa-tab>
<wa-tab panel="css">In CSS</wa-tab>
<wa-tab-panel name="html">
Add the following code to the `<head>` of your page:
```html { v-content:html="code.html.highlighted" }
<link rel="stylesheet" href="{% cdnUrl stylesheet %}" />
```
</wa-tab-panel>
<wa-tab-panel name="css">
Add the following code at the top of your CSS file:
```css { v-content:html="code.css.highlighted" }
@import url('{% cdnUrl stylesheet %}');
```
</wa-tab-panel>
</wa-tab-group>
{% include 'import-stylesheet-code.md.njk' %}
## Dark mode
@@ -180,6 +203,5 @@ systemDark.addEventListener('change', applyDark);
applyDark();
```
</div> {# end theme app #}
{% endmarkdown %}
{% endblock %}

View File

@@ -102,7 +102,12 @@ const templates = {
export function codeExamplesPlugin(eleventyConfig, options = {}) {
const defaultOptions = {
container: 'body',
defaultOpen: () => false,
defaultOpen: (code, { outputPathIndex }) => {
return (
outputPathIndex === 1 && // is first
code.textContent.length < 500
); // is short
},
};
options = { ...defaultOptions, ...options };

View File

@@ -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"></wa-copy-button>`;
<wa-copy-button from="${codeId}" class="copy-button" hoist></wa-copy-button>`;
});
return doc.toString();

View File

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

View File

@@ -0,0 +1,23 @@
import { parse } from 'node-html-parser';
/**
* Eleventy plugin to add remove elements with <div data-alpha="remove"> from the alpha build.
*/
export function removeDataAlphaElements(options = {}) {
options = {
isAlpha: false,
...options,
};
return function (eleventyConfig) {
eleventyConfig.addTransform('remove-data-alpha-elements', content => {
const doc = parse(content, { blockTextElements: { code: true } });
if (options.isAlpha) {
doc.querySelectorAll('[data-alpha="remove"]').forEach(el => el.remove());
}
return doc.toString();
});
};
}

View File

@@ -24,23 +24,9 @@ export function searchPlugin(options = {}) {
};
return function (eleventyConfig) {
const pagesToIndex = new Map();
eleventyConfig.addPreprocessor('exclude-unlisted-from-search', '*', function (data, content) {
if (data.unlisted) {
// no-op
} else {
pagesToIndex.set(data.page.inputPath, {});
}
return content;
});
const pagesToIndex = [];
eleventyConfig.addTransform('search', function (content) {
if (!pagesToIndex.has(this.page.inputPath)) {
return content;
}
const doc = parse(content, {
blockTextElements: {
script: false,
@@ -56,7 +42,7 @@ export function searchPlugin(options = {}) {
doc.querySelectorAll(selector).forEach(el => el.remove());
});
pagesToIndex.set(this.page.inputPath, {
pagesToIndex.push({
title: collapseWhitespace(options.getTitle(doc)),
description: collapseWhitespace(options.getDescription(doc)),
headings: options.getHeadings(doc).map(collapseWhitespace),
@@ -79,7 +65,7 @@ export function searchPlugin(options = {}) {
this.field('h', { boost: 10 });
this.field('c');
for (const [_inputPath, page] of pagesToIndex) {
for (const page of pagesToIndex) {
this.add({ id: index, t: page.title, h: page.headings, c: page.content });
map[index] = { title: page.title, description: page.description, url: page.url };
index++;

View File

@@ -1,65 +0,0 @@
import { deepEntries } from '../scripts/util/deep.js';
import { themeConfig } from './theming.js';
import themes from '/assets/data/themes.js';
/**
* Map of font pairings (body + heading) to the first theme that uses them.
*/
export const pairings = {};
// NOTE Do not use Symbols, we want these to be enumerable when used as keys
export const sameAs = { body: '$body' };
export const fontNames = {
'system-ui': 'OS Default',
'ui-serif': 'OS Default Serif',
'ui-sans-serif': 'OS Default Sans Serif',
'ui-monospace': 'OS Default Code Font',
'ui-monospace': 'OS Default Code Font',
};
export function defaultTitle(fonts) {
let { body, heading = sameAs.body } = fonts;
let names = [body];
if (heading !== sameAs.body) {
names.unshift(heading);
}
return names.map(name => fontNames[name] ?? name).join(' • ');
}
for (let id in themes) {
let theme = themes[id];
let { fonts } = theme;
if (fonts) {
let { body, heading = sameAs.body } = fonts;
pairings[body] ??= {};
pairings[body][heading] ??= {
id, // First theme that uses this pairing
ids: new Set([id]), // All themes that use this pairing
url: themeConfig.typography.url(id), // Stylesheet URL
fonts,
get title() {
return defaultTitle(this.fonts);
},
};
pairings[body][heading].ids.add(id);
}
}
export const pairingsEntries = deepEntries(pairings, {
descend(value, key, parent, path) {
if (value?.fonts) {
return false; // Don't recurse into pairing objects
}
},
filter(value, key, parent, path) {
// Only keep 2 levels (body → heading → pairing)
return path.length === 1;
},
});
export const pairingsList = pairingsEntries.map(arg => arg.at(-1));

View File

@@ -1,7 +0,0 @@
export const iconLibraries = {
default: {
title: 'Font Awesome',
family: ['classic', 'sharp', 'duotone', 'sharp-duotone'],
style: ['solid', 'regular', 'light', 'thin'],
},
};

View File

@@ -1,6 +0,0 @@
export * from './colors.js';
// export * from './fonts.js';
export * from './icons.js';
export * from './theming.js';
export const cdnUrl = globalThis.document ? document.documentElement.dataset.cdnUrl : '/dist/';

View File

@@ -1,32 +0,0 @@
---
layout: null
permalink: '/assets/data/palettes.js'
eleventyExcludeFromCollections: true
---
export default {
{%- for palette in collections.palette | sort %}
{%- if not palette.data.unlisted %}
{% set paletteId = palette.fileSlug -%}
{%- set colors = palettes[paletteId] -%}
'{{ paletteId }}': {
id: '{{ paletteId }}',
title: '{{ palette.data.title }}',
colors: {
{% for hue, tints in colors -%}
'{{ hue }}': {
{% for tint, value in tints -%}
{%- if tint != '05' -%}
'{{ '05' if tint == '5' else tint }}': '{{ value | safe }}',
{%- endif %}
{% endfor %}
get key() {
return this[this.maxChromaTint];
}
},
{% endfor -%} // end colors
}
}, // end palette
{%- endif -%}
{% endfor %}
};

View File

@@ -1,22 +0,0 @@
---
layout: null
permalink: '/assets/data/themes.js'
eleventyExcludeFromCollections: true
---
export default {
{%- for theme in collections.theme | sort %}
{%- if not theme.data.unlisted %}
{% set themeId = theme.fileSlug -%}
{%- set colors = themes[themeId] -%}
'{{ themeId }}': {
id: '{{ themeId }}',
title: '{{ theme.data.title }}',
palette: '{{ theme.data.palette }}',
brand: '{{ theme.data.brand }}',
isPro: {{ theme.data.isPro or 'pro' in theme.data.tags }},
fonts: {{ (theme.data.fonts | dump or 'null') | safe }},
icons: {{ (theme.data.icons | dump or 'null') | safe }},
},
{%- endif %}
{% endfor %}
};

View File

@@ -1,82 +0,0 @@
import { deepEach, isPlainObject } from '../scripts/util/deep.js';
/**
* Data related to themes, theme remixing
* Must work in both browser and Node.js
*/
export const cdnUrl = globalThis.document ? document.documentElement.dataset.cdnUrl : '/dist/';
// This should eventually replace all uses of `urls` and `themeParams`
export const themeConfig = {
base: { url: id => `styles/themes/${id}.css`, default: 'default' },
colors: {
url: id => `styles/themes/${id}/color.css`,
docs: '/docs/themes/',
icon: 'palette',
default() {
return this.base;
},
},
palette: {
url: id => `styles/color/${id}.css`,
docs: '/docs/palette/',
icon: 'swatchbook',
default(baseTheme) {
return baseTheme?.palette;
},
},
brand: {
url: id => `styles/brand/${id}.css`,
icon: 'droplet',
default(baseTheme) {
return baseTheme?.brand;
},
},
typography: {
url: id => `styles/themes/${id}/typography.css`,
docs: '/docs/themes/',
icon: 'font-case',
default() {
return this.base;
},
},
icon: {
library: { cssProperty: '--wa-icon-library', default: 'default' },
family: {
cssProperty: '--wa-icon-family',
default(baseTheme) {
return baseTheme?.icon?.family ?? 'classic';
},
},
style: {
cssProperty: '--wa-icon-variant',
default(baseTheme) {
return baseTheme?.icon?.style ?? 'solid';
},
},
},
};
// Shallow remixing params in correct order
// base must be first. brand needs to come after palette, which needs to come after colors.
export const themeParams = Object.keys(themeConfig).filter(aspect => themeConfig[aspect].url);
export const urls = themeParams.reduce((acc, aspect) => {
acc[aspect] = themeConfig[aspect].url;
return acc;
}, {});
export const themeDefaults = { ...themeConfig };
deepEach(themeDefaults, (value, key, parent, path) => {
if (isPlainObject(value)) {
// Replace w/ default value or shallow clone
return value.default ?? { ...value };
}
});
export const selectors = {
palette: id =>
[':where(:root)', ':host', ":where([class^='wa-theme-'], [class*=' wa-theme-'])", `.wa-palette-${id}`].join(',\n'),
theme: id => [':where(:root)', ':host', `.wa-theme-${id}`].join(',\n'),
};

View File

@@ -0,0 +1,59 @@
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 data-fa-kit-code="b10bfbde90" 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();
}
});

View File

@@ -51,7 +51,7 @@
<button class="diff-dialog-toggle">
Show Hydration Mismatch
</button>
<wa-dialog class="diff-dialog" light-dismiss>
<wa-dialog class="diff-dialog" with-header light-dismiss>
<div class="diff-grid">
<div>
<div>Server</div>

View File

@@ -165,8 +165,3 @@ my.palettes = new SavedEntities({
key: 'savedPalettes',
type: 'palette',
});
my.themes = new SavedEntities({
key: 'savedThemes',
type: 'theme',
});

View File

@@ -74,8 +74,7 @@ const sidebar = {
a = sidebar.addChild(a, parentA);
// This is mainly to port Pro badges
let badges = Array.from(parentLi.querySelectorAll(':scope > wa-badge'), badge => badge.cloneNode(true));
let badges = Array.from(parentLi.querySelectorAll('wa-badge'), badge => badge.cloneNode(true));
let append = [...badges];
if (entity.delete) {

View File

@@ -1,5 +1,34 @@
import { domChange } from './util/dom-change.js';
export { domChange };
let initialPageLoadComplete = document.readyState === 'complete';
if (!initialPageLoadComplete) {
window.addEventListener('load', () => {
initialPageLoadComplete = true;
});
}
// Helper for view transitions
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;
}
}
export function nextFrame() {
return new Promise(resolve => requestAnimationFrame(resolve));
@@ -106,6 +135,6 @@ document.addEventListener('keydown', event => {
!event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))
) {
event.preventDefault();
colorScheme.set(colorScheme.get() === 'dark' ? 'light' : 'dark');
colorScheme.set(theming.colorScheme.resolvedValue === 'dark' ? 'light' : 'dark');
}
});

View File

@@ -1,6 +1,6 @@
/**
* Get import code for remixed themes and tweaked palettes.
*/
export { cdnUrl, hueRanges, hues, selectors, tints, urls } from '../data/index.js';
export { default as Permalink } from './permalink.js';
export { getThemeCode } from './tweak/code.js';
export { cdnUrl, hueRanges, hues, selectors, tints, urls } from './tweak/data.js';
export { default as Permalink } from './tweak/permalink.js';

View File

@@ -1,8 +1,7 @@
/**
* Get import code for remixed themes and tweaked palettes.
*/
import { selectors, themeConfig } from '../../data/theming.js';
import { deepEach, deepGet } from '/assets/scripts/util/deep.js';
import { urls } from './data.js';
export function cssImport(url, options = {}) {
let { language = 'html', cdnUrl = '/dist/', attributes } = options;
@@ -22,65 +21,29 @@ export function cssLiteral(value, options = {}) {
if (language === 'css') {
return value;
} else {
return `<style${options.attributes ?? ''}>\n${value}\n</style>`;
return `<style>\n${value}\n</style>`;
}
}
/**
* Get code for a theme, including tweaks
* @param {*} theme
* @param {*} options
* @returns
*/
export function getThemeCode(theme, options = {}) {
let urls = [];
let declarations = [];
let id = options.id ?? theme.base ?? 'default';
// Params in correct order
export const themeParams = ['colors', 'palette', 'brand', 'typography'];
deepEach(themeConfig, (config, aspect, obj, path) => {
if (!config?.default) {
// We're not in a config object
return;
}
export function getThemeCode(base, params, options) {
let ret = [];
let value = deepGet(theme, [...path, aspect]);
if (base) {
ret.push(urls.theme(base));
}
if (!value) {
return;
}
for (let aspect of themeParams) {
let value = params[aspect];
if (config.url) {
// This is implemented by pulling in different CSS files
urls.push(config.url(value));
} else {
if (config.cssProperty) {
declarations.push(`${config.cssProperty}: ${value};`);
}
}
});
let ret = urls.map(url => cssImport(url, options)).join('\n');
if (declarations.length > 0) {
let cssCode = cssRule(selectors.theme(id), declarations, options);
let faKitAttribute = ` data-fa-kit-code="${theme.icon.kit}"`;
if (theme.icon.kit) {
options.attributes ??= '';
options.attributes += faKitAttribute;
cssCode =
`/* Note: To use Font Awesome Pro icons,\n set ${faKitAttribute} on the <link> (or any other) element */\n\n` +
cssCode;
}
cssCode = cssLiteral(cssCode, options);
if (ret) {
ret += '\n\n' + cssCode;
if (value) {
ret.push(urls[aspect](value));
}
}
return ret;
return ret.map(url => cssImport(url, options)).join('\n');
}
export function cssRule(selector, declarations, { indent = ' ' } = {}) {

View File

@@ -1,9 +1,21 @@
/**
* Data related to palettes and colors.
* Data related to theme remixing and palette tweaking
* Must work in both browser and Node.js
*/
export const cdnUrl = globalThis.document ? document.documentElement.dataset.cdnUrl : '/dist/';
export const tints = ['05', '10', '20', '30', '40', '50', '60', '70', '80', '90', '95'];
export const urls = {
theme: id => `styles/themes/${id}.css`,
colors: id => `styles/themes/${id}/color.css`,
palette: id => `styles/color/${id}.css`,
brand: id => `styles/brand/${id}.css`,
typography: id => `styles/themes/${id}/typography.css`,
};
export const selectors = {
palette: id =>
[':where(:root)', ':host', ":where([class^='wa-theme-'], [class*=' wa-theme-'])", `.wa-palette-${id}`].join(',\n'),
};
export const hueRanges = {
red: { min: 5, max: 35 }, // 30
@@ -17,9 +29,6 @@ export const hueRanges = {
pink: { min: 320, max: 365 }, // 45
};
export const hues = Object.keys(hueRanges);
export const allHues = [...hues, 'gray'];
export const moreHue = {
red: 'Redder',
orange: 'More orange', // https://www.reddit.com/r/grammar/comments/u9n0uo/is_it_oranger_or_more_orange/
@@ -45,3 +54,20 @@ export const maxGrayChroma = {
purple: 0.3,
pink: 0.25,
};
export const docsURLs = {
colors: '/docs/themes/',
palette: '/docs/palettes/',
typography: '/docs/themes/',
};
export const icons = {
colors: 'palette',
palette: 'swatchbook',
brand: 'droplet',
typography: 'font-case',
};
export const hues = Object.keys(hueRanges);
export const tints = ['05', '10', '20', '30', '40', '50', '60', '70', '80', '90', '95'];

View File

@@ -1,4 +1,4 @@
import { deepEach, deepGet, deepSet } from './util/deep.js';
const IDENTITY = x => x;
export default class Permalink extends URLSearchParams {
/** Params changed since last URL I/O */
@@ -13,59 +13,6 @@ export default class Permalink extends URLSearchParams {
return Object.fromEntries(this.entries());
}
/**
* Set multiple values from an object. Nested values will be joined with a hyphen.
* @param {object} values - The object containing the values to set.
* @param {object} defaults - The object containing the default values.
*
*/
setAll(values, defaults) {
deepEach(values, (value, key, parent, path) => {
let fullPath = [...path, key];
let param = fullPath.join('-');
let defaultValue = deepGet(defaults, fullPath);
if (typeof value === 'object') {
// We'll handle this when we descend into it
return;
}
if (!value || value === defaultValue) {
// Remove the param from the URL
this.delete(param);
return;
}
this.set(param, value);
});
}
getAll(...args) {
if (args.length > 0) {
return super.getAll(...args);
}
// Get all values as a nested object
// Assumes that hyphens always mean nesting
let obj = {};
for (let [key, value] of this.entries()) {
let path = key.split('-');
deepSet(obj, path, value);
}
return obj;
}
delete(key, value) {
let hadValue = this.has(key);
super.delete(key, value);
if (hadValue) {
this.changed = true;
}
}
set(key, value, defaultValue) {
if (equals(value, defaultValue) || equals(value, '')) {
value = null;

View File

@@ -1,17 +0,0 @@
/**
* Picks a random element from an array.
* @param {any[]} arr
*/
export function sample(arr) {
if (!Array.isArray(arr)) {
return arr;
}
if (arr.length < 2) {
return arr[0];
}
let index = Math.floor(Math.random() * arr.length);
return arr[index];
}

View File

@@ -1,180 +0,0 @@
/**
* @typedef { string | number | Symbol } Property
* @typedef { (value: any, key: Property, parent: object, path: Property[]) => any } EachCallback
*/
export function isPlainObject(obj) {
return isObject(obj, 'Object');
}
export function isObject(obj, type) {
if (!obj || typeof obj !== 'object') {
return false;
}
let proto = Object.getPrototypeOf(obj);
return proto.constructor?.name === type;
}
export function deepMerge(target, source, options = {}) {
let {
emptyValues = [undefined],
containers = ['Object', 'EventTarget'],
isContainer = value => containers.some(type => isObject(value, type)),
} = options;
if (isContainer(target) && isContainer(source)) {
for (let key in source) {
if (key in target && isContainer(target[key]) && isContainer(source[key])) {
target[key] = deepMerge(target[key], source[key], options);
} else if (!emptyValues.includes(source[key])) {
target[key] = source[key];
}
}
return target;
}
return target ?? source;
}
/**
* Iterate over a deep array, recursively for plain objects
* @param { any } obj The object to iterate over. Can be an array or a plain object, or even a primitive value.
* @param { EachCallback } callback. value is === parent[key]
* @param { object } [parentObj] The parent object of the current value Mainly used internally to facilitate recursion.
* @param { Property } [key] The key of the current value. Mainly used internally to facilitate recursion.
* @param { Property[] } [path] Any existing path (not including the key). Mainly used internally to facilitate recursion.
*/
export function deepEach(obj, callback, parentObj, key, path = []) {
if (key !== undefined) {
let ret = callback(obj, key, parentObj, path);
if (ret !== undefined) {
if (ret === false) {
// Do not descend further
return;
}
// Overwrite value
parentObj[key] = ret;
obj = ret;
}
}
let newPath = key !== undefined ? [...path, key] : path;
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
deepEach(obj[i], callback, obj, i, newPath);
}
} else if (isPlainObject(obj)) {
for (let key in obj) {
deepEach(obj[key], callback, obj, key, newPath);
}
}
}
/**
* Get a value from a deeply nested object
* @param {*} obj
* @param {PropertyPath} path
* @returns
*/
export function deepGet(obj, path) {
if (path.length === 0) {
return obj;
}
let ret = obj;
for (let key of path) {
if (ret === undefined) {
return undefined;
}
ret = ret[key];
}
return ret;
}
/**
* Set a value in a deep object, creating object literals as needed
* @param { * } obj
* @param { Property[] } path
* @param { any } value
*/
export function deepSet(obj, path, value) {
if (path.length === 0) {
return;
}
let key = path.pop();
let ret = path.reduce((acc, property) => {
if (acc[property] === undefined) {
acc[property] = {};
}
return acc[property];
}, obj);
ret[key] = value;
}
export function deepClone(obj) {
if (!obj) {
return obj;
}
let ret = obj;
if (Array.isArray(obj)) {
ret = obj.map(item => deepClone(item));
} else if (isPlainObject(obj)) {
ret = { ...obj };
for (let key in obj) {
ret[key] = deepClone(obj[key]);
}
}
return ret;
}
/**
* Like Object.entries, but for deeply nested objects.
* For shallow objects the output is the same as Object.entries.
* @param {*} obj
* @param { object } options
* @param { EachCallback } each - If this returns false, the entry is not added to the result and the recursion is stopped.
* @param { EachCallback } filter - If this returns false, the entry is not added to the result.
* @param { EachCallback } descend - If this returns false, recursion is stopped.
* @returns {any[][]}
*/
export function deepEntries(obj, options = {}) {
let { each, filter, descend } = options;
let entries = [];
deepEach(obj, (value, key, parent, path) => {
let ret = each?.(value, key, parent, path);
if (ret !== false) {
let included = filter?.(value, key, parent, path) ?? true;
if (included) {
entries.push([...path, key, value]);
}
let descendRet = descend?.(value, key, parent, path);
if (descendRet === false) {
return false; // Stop recursion
}
}
return ret;
});
return entries;
}

View File

@@ -1,39 +0,0 @@
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;
}
}
export default domChange;

View File

@@ -1,24 +0,0 @@
/**
* Make the first letter of a string uppercase
* @param {*} str
* @returns
*/
export function capitalize(str) {
str += '';
return str[0].toUpperCase() + str.slice(1);
}
/**
* Convert a readable string to a slug.
* @param {*} str - Input string. If argument is not a string, it will be stringified.
* @returns {string} - The slugified string
*/
export function slugify(str) {
return (str + '')
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '') // Convert accented letters to ASCII
.replace(/[^\w\s-]/g, '') // Remove remaining non-ASCII characters
.trim()
.replace(/\s+/g, '-') // Convert whitespace to hyphens
.toLowerCase();
}

View File

@@ -1,5 +1,5 @@
import my from '/assets/scripts/my.js';
import Permalink from '/assets/scripts/permalink.js';
import Permalink from '/assets/scripts/tweak/permalink.js';
export default {
data() {

View File

@@ -0,0 +1,87 @@
.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;
}
}

View File

@@ -1,3 +1,4 @@
@import 'code-examples.css';
@import 'code-highlighter.css';
@import 'copy-code.css';
@import 'outline.css';
@@ -256,6 +257,15 @@ wa-page > main {
}
h1.title {
wa-icon-button {
font-size: var(--wa-font-size-l);
color: var(--wa-color-text-quiet);
&:not(:hover, :focus) {
opacity: 0.5;
}
}
wa-badge {
vertical-align: middle;
font-size: 1.5rem;
@@ -383,7 +393,19 @@ wa-page > main:has(> .index-grid) {
}
wa-card {
box-shadow: none;
--spacing: var(--wa-space-m);
inline-size: 100%;
&:hover {
--border-color: var(--wa-color-brand-border-loud);
border-color: var(--border-color);
box-shadow: 0 0 0 var(--wa-border-width-s) var(--border-color);
.page-name {
color: var(--wa-color-brand-on-quiet);
}
}
[slot='header'] {
display: flex;
@@ -397,11 +419,11 @@ wa-page > main:has(> .index-grid) {
min-block-size: calc(6rem + var(--spacing));
}
}
}
wa-card .page-name {
font-size: var(--wa-font-size-s);
font-weight: var(--wa-font-weight-action);
.page-name {
font-size: var(--wa-font-size-s);
font-weight: var(--wa-font-weight-action);
}
}
.index-category {
@@ -410,146 +432,6 @@ wa-card .page-name {
margin-block-start: var(--wa-space-2xl);
}
/* Interactive cards */
wa-card[role='button'][tabindex='0'],
button,
a[href],
wa-option,
wa-radio,
wa-checkbox {
/* Disabled state */
&:is(:disabled, [disabled], [aria-disabled='true']) {
&:is(wa-card, :has(> wa-card)) {
opacity: 60%;
cursor: not-allowed;
}
}
&:where(:not(:disabled, [disabled], [aria-disabled='true'])) {
&:has(> wa-card) {
/* Parents only (not interactive <wa-card>) */
margin: calc(var(--wa-border-width-m) + 1px);
padding: 0;
/* Hover state */
&:hover,
&:state(hover),
&:state(current) {
/* Do not change the parent background as a hover effect (we style the card instead) */
background: transparent !important;
}
&::part(control),
&:is(wa-option)::part(checked-icon) {
--background-color-checked: var(--wa-color-brand-fill-loud);
--checked-icon-scale: 0.5;
--offset: var(--wa-space-2xs);
position: absolute;
inset: calc(var(--offset) + var(--wa-border-width-m));
inset-block-end: auto;
inset-inline-start: auto;
z-index: 1;
margin: 0;
background: var(--wa-color-brand-fill-loud);
color: var(--wa-color-brand-on-loud);
}
&::part(checked-icon) {
color: var(--wa-color-brand-on-loud);
}
&:is(wa-option)::part(checked-icon) {
inset-block-start: calc(var(--wa-space-smaller) - 0.5em);
inset-inline-end: calc(var(--wa-space-smaller) - 0.5em);
width: 1em;
height: 1em;
line-height: 1em;
padding: 0.4em;
border-radius: var(--wa-border-radius-circle);
text-align: center;
font-size: var(--wa-font-size-xs);
}
}
/* Hover state */
&:hover,
&:state(hover),
&:state(current) {
&:is(wa-card),
> wa-card {
--border-color: var(--wa-color-brand-border-loud);
border-color: var(--border-color);
box-shadow: 0 0 0 var(--wa-border-width-s) var(--border-color);
}
}
&:is(wa-card, :has(> wa-card)) {
/* Interactive card parent */
position: relative;
cursor: pointer;
/* Unselected state */
&:where(:not(:state(checked), :state(selected), [aria-checked='true'], [aria-selected='true'])) {
&::part(checked-icon),
&::part(control) {
display: none;
}
}
}
&:is(wa-card),
> wa-card {
/* The card itself */
box-shadow: none;
}
}
}
/* Selected cards */
:state(selected),
:state(checked),
[aria-checked='true'],
[aria-selected='true'] {
&:is(wa-card, :has(> wa-card)) {
background: transparent;
}
&:is(wa-card),
> wa-card {
--border-color: var(--wa-color-brand-border-loud);
box-shadow: 0 0 0 var(--wa-border-width-m) var(--border-color);
&::part(body) {
background: var(--wa-color-brand-fill-quiet);
}
}
}
wa-select:has(> wa-option > wa-card) {
&::part(listbox) {
--column-width: 1fr;
--columns: 1;
--gap: var(--wa-space-smaller);
display: grid;
grid-template-columns: repeat(auto-fill, minmax(var(--column-width), 1fr));
width: calc(var(--columns) * var(--column-width) + (var(--columns) - 1) * var(--gap) + 2 * var(--wa-space));
max-width: var(--auto-size-available-width, 90vw);
gap: var(--gap);
padding: var(--wa-space-smaller) var(--wa-space);
}
> wa-option > wa-card {
--spacing: var(--wa-space-s);
}
}
wa-radio:has(> wa-card) {
grid-template-columns: 1fr;
width: auto;
}
/* Swatches */
.swatch {
position: relative;
@@ -726,6 +608,13 @@ 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 */

View File

@@ -41,9 +41,9 @@
}
/* Header */
#site-search-container header {
header {
flex: 0 0 auto;
align-items: center;
align-items: middle;
/* Fixes an iOS Safari 16.4 bug that draws the parent element's border radius incorrectly when showing/hiding results */
border-radius: var(--wa-border-radius-l);
}

View File

@@ -1,9 +1,4 @@
wa-card:has(
> .theme-icon-host,
> [slot='header'] > .theme-icon-host,
> .fonts-icon-host,
> [slot='header'] > .fonts-icon-host
) {
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;
@@ -17,7 +12,6 @@ wa-card:has(
}
.theme-icon-host,
.fonts-icon-host,
.palette-icon-host {
flex: 1;
border-radius: inherit;
@@ -29,17 +23,12 @@ wa-card:has(
}
}
.theme-icon:not(.theme-color-icon),
.palette-icon,
.icons-icon {
min-height: 5.5rem;
}
.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 {
@@ -53,8 +42,7 @@ wa-card:has(
}
}
.theme-icon,
.fonts-icon {
.theme-icon {
min-width: 18ch;
padding: var(--wa-space-xs) var(--wa-space-m);
border-radius: inherit;
@@ -69,18 +57,11 @@ wa-card:has(
}
.theme-color-icon {
display: flex;
display: grid;
gap: var(--wa-space-xs);
min-width: 15ch;
background: var(--wa-color-surface-lowered);
& + & {
border-start-start-radius: 0;
border-start-end-radius: 0;
}
grid-template-columns: repeat(4, auto);
div {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
@@ -91,17 +72,26 @@ wa-card:has(
padding: var(--wa-space-2xs) var(--wa-space-xs);
color: var(--text-color);
font-weight: var(--wa-font-weight-semibold);
&.plain {
font-weight: var(--wa-font-weight-bold);
}
}
}
.theme-icon.theme-overall-icon,
.fonts-icon {
.theme-typography-icon {
display: flex;
flex-direction: column;
gap: var(--wa-space-xs);
}
.theme-overall-icon {
display: flex;
flex-flow: column;
gap: var(--wa-space-2xs);
gap: var(--wa-space-xs);
justify-content: center;
width: 100%;
min-height: 6.75rem;
min-height: 7.5rem;
box-sizing: border-box;
background: var(--wa-color-surface-lowered);
@@ -140,50 +130,3 @@ wa-card:has(
}
}
}
.fonts-icon {
font-family: var(--wa-font-family-body);
padding-block: var(--wa-space-s);
overflow: hidden;
position: relative;
& h2,
& p {
white-space: nowrap;
}
&::after {
content: '';
position: absolute;
right: 0;
width: 50%;
height: 100%;
background-image: linear-gradient(to left, var(--wa-color-surface-lowered), 20%, transparent);
}
}
.icons-icon {
display: grid;
grid-template-columns: repeat(var(--columns, 5), auto);
gap: var(--wa-space-xs);
place-items: center;
place-content: center;
& wa-icon {
font-size: 1.25em;
}
}
.page-card {
wa-badge {
margin-inline: var(--wa-space-3xs);
}
}
:is(.theme-card, .icons-card)::part(header) {
background: var(--wa-color-surface-lowered);
}
.icons-card::part(header) {
color: var(--wa-color-neutral-on-quiet);
}

View File

@@ -1,145 +0,0 @@
/* App UI, for themer, palette tweaking etc */
:root {
--fa-sliders-simple: '\f1de';
}
.title {
wa-icon-button {
font-size: var(--wa-font-size-l);
color: var(--wa-color-text-quiet);
&:not(:hover, :focus) {
opacity: 0.5;
}
}
}
.popup {
background: var(--wa-color-surface-default);
border: 1px solid var(--wa-color-surface-border);
padding: var(--wa-space-xl);
border-radius: var(--wa-border-radius-m);
max-height: 90dvh;
overflow: auto;
code {
white-space: nowrap;
}
}
.color-select {
&.default::part(display-input) {
opacity: 0.6;
font-style: italic;
}
> small {
margin-inline-start: var(--wa-space-xs);
padding-block: 0 var(--wa-space-xs);
}
&::part(combobox)::before,
wa-option::before {
content: '';
display: inline-block;
width: 1.2em;
aspect-ratio: 1;
margin-inline-end: var(--wa-space-xs);
flex: none;
border-radius: var(--wa-border-radius-m);
background: var(--color);
border: 1px solid var(--wa-color-surface-default);
}
wa-option {
white-space: nowrap;
&::before {
width: 1em;
margin-inline: var(--wa-space-xs);
}
&::part(checked-icon) {
order: 2;
}
}
.default-badge {
opacity: 0.6;
margin-inline-start: var(--wa-space-xs);
}
}
.swatch-select {
padding: 2px;
wa-radio-button {
--swatch-border-color: color-mix(in oklab, canvastext, transparent 80%);
&::part(base) {
/* a <button> */
width: 2em;
height: 2em;
padding: 0;
border-radius: var(--border-radius, var(--wa-border-radius-m));
background: var(--color);
background-clip: border-box;
border-color: var(--swatch-border-color);
}
}
&.swatch-shape-circle {
--border-radius: var(--wa-border-radius-circle);
}
wa-radio-button:is([checked], :state(checked)) {
--swatch-border-color: var(--wa-color-surface-default);
&::part(base) {
box-shadow:
inset 0 0 0 var(--indicator-width) var(--wa-color-surface-default),
0 0 0 calc(var(--indicator-width) + 1px) var(--indicator-color);
}
}
&::part(form-control-input) {
flex-wrap: wrap;
gap: var(--wa-space-xs);
}
}
/* Repeated to increase specificity */
.editable-text.editable-text {
display: inline-flex;
align-items: center;
gap: var(--wa-space-xs);
--edit-hint-color: oklab(from var(--wa-color-warning-fill-quiet) l a b / 50%);
> .text {
&:hover,
&:focus {
background-color: var(--edit-hint-color);
box-shadow: 0 0 0 var(--wa-space-2xs) var(--edit-hint-color);
color: inherit;
border-radius: calc(var(--wa-border-radius-m) - var(--wa-space-2xs));
}
}
> input {
font: inherit;
margin-block: calc(-1 * var(--wa-space-smaller));
field-sizing: content;
}
wa-icon-button {
font-size: 90%;
}
}
.info-tip-default-trigger {
color: var(--wa-color-text-quiet);
&:not(:hover, :focus) {
opacity: 65%;
}
}

View File

@@ -1,78 +0,0 @@
import { capitalize } from '../../scripts/util/string.js';
const template = `
<wa-select class="color-select" name="brand" :label="label" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)"
:style="{'--color': getColor(modelValue)}">
<template v-for="values, group in computedGroups">
<template v-if="group">
<wa-divider v-if="group !== firstGroup"></wa-divider>
<small>{{ group }}</small>
</template>
<wa-option v-if="values?.length" v-for="value of values" :label="getLabel(value)" :value="value" :style="{'--color': getColor(value)}" v-html="getContent?.(value) ?? getLabel(value)"></wa-option>
</template>
<slot></slot>
</wa-select>
`;
export default {
props: {
modelValue: String,
label: String,
getLabel: {
type: Function,
default: capitalize,
},
getContent: {
type: Function,
},
getColor: {
type: Function,
default: value => `var(--wa-color-${value})`,
},
values: {
type: Array,
default: [],
},
groups: {
type: Object,
},
},
emits: ['update:modelValue', 'input'],
data() {
return {};
},
computed: {
computedGroups() {
let ret = {};
if (this.values?.length) {
ret[''] = this.values;
}
if (this.groups) {
for (let group in this.groups) {
if (this.groups[group]?.length) {
ret[group] = this.groups[group];
}
}
}
return ret;
},
firstGroup() {
return Object.keys(this.computedGroups)[0];
},
},
methods: {
capitalize,
handleInput(e) {
this.$emit('input', this.modelValue);
},
},
template,
compilerOptions: {
isCustomElement: tag => tag.startsWith('wa-'),
},
};

View File

@@ -1,82 +0,0 @@
const template = `
<span class="editable-text">
<template v-if="isEditing">
<input ref="input" class="wa-size-s" :aria-label="label" :value="value" @input="handleInput" @keydown.enter="done" @keydown.esc="cancel" />
<wa-icon-button name="check" label="Done editing" @click="done"></wa-icon-button>
<wa-icon-button name="xmark" label="Cancel" @click="cancel"></wa-icon-button>
</template>
<template v-else>
<span class="text" ref="wrapper" @focus="edit" @click="edit" tabindex="0">{{ value }}</span>
<wa-icon-button name="pencil" :label="'Edit ' + label" @click="edit"></wa-icon-button>
</template>
</span>
`;
export default {
props: {
modelValue: String,
label: {
type: String,
default: 'Rename',
},
},
emits: ['update:modelValue', 'submit'],
data() {
return {
value: this.modelValue,
previousValue: undefined,
isEditing: false,
};
},
computed: {},
methods: {
edit(event) {
if (this.isEditing) {
return;
}
event.stopPropagation();
this.isEditing = true;
this.previousValue = this.value;
this.$nextTick(() => {
this.$refs.input.focus();
this.$refs.input.select();
});
},
done(event) {
if (!this.isEditing) {
return;
}
event.stopPropagation();
this.isEditing = false;
if (!this.previousValue || this.previousValue !== this.value) {
this.$emit('submit', this.value);
}
},
cancel(event) {
if (!this.isEditing) {
return;
}
event.stopPropagation();
this.isEditing = false;
this.value = this.previousValue;
},
handleInput(event) {
this.value = event.target.value;
},
},
watch: {
value(newValue) {
this.$emit('update:modelValue', newValue);
},
},
template,
};

View File

@@ -1,132 +0,0 @@
import PageCard from './page-card.js';
import { defaultTitle, pairings, sameAs } from '/assets/data/fonts.js';
import { themeConfig } from '/assets/data/theming.js';
import { cssImport, getThemeCode } from '/assets/scripts/tweak/code.js';
import themes from '/docs/themes/data.js';
const template = `
<page-card class="fonts-card" :info="computedPairing">
<template #icon>
<wa-scoped slot="header" class="fonts-icon-host" inert>
<template v-html="html"></template>
<template>
<link rel="stylesheet" href="/dist/styles/native/content.css">
<link rel="stylesheet" href="/assets/styles/theme-icons.css">
<div class="fonts-icon" role="presentation">
<h2>When my six o'clock alarm buzzes, I require a pot of good java.</h2>
<p>By quarter past seven, I've jotted hazy musings in a flax-bound notebook, sipping lukewarm espresso.</p>
</div>
</template>
</wa-scoped>
</template>
<slot></slot>
<template #extra>
<slot name="extra" />
</template>
</page-card>
`;
export default {
props: {
theme: String,
src: String,
fonts: Object,
pairing: Object,
},
data() {
return {};
},
computed: {
content() {
let pairingTitle = this.computedPairing.title;
// let themeTitle = this.themeId ? `As seen in ${this.themeMeta.title}` : '';
if (this.title) {
return { title: this.title, subtitle: this.subtitle ?? pairingTitle };
} else {
return { title: pairingTitle, subtitle: this.subtitle };
}
},
url() {
let ret = this.src ?? this.pairing?.url;
if (!ret && this.theme) {
return themeConfig.typography.url(this.theme);
}
return ret;
},
themeId() {
return this.theme ?? this.pairing?.id;
},
themeMeta() {
return themes[this.themeId] ?? {};
},
computedFonts() {
let ret = this.fonts ?? this.pairing?.fonts ?? this.themeMeta?.fonts;
let defaults = themes.default.fonts;
return Object.assign({}, defaults, { ...ret });
},
computedPairing() {
let ret;
if (this.pairing) {
ret = { ...this.pairing };
} else {
// Get from theme
let fonts = this.computedFonts;
let { body, heading = sameAs.body } = fonts;
let pairing = pairings[body]?.[heading];
ret = Object.assign({ fonts }, pairing);
}
ret.url = this.url;
ret.title ??= defaultTitle(fonts);
return ret;
},
computed() {
let ret = { fonts: this.computedFonts };
for (let key in ret.fonts) {
if (ret.fonts[key] === sameAs.body) {
ret.fonts[key] = ret.fonts.body;
}
}
ret.pairing = this.computedPairing;
ret.theme = this.themeId;
ret.url = this.url;
return ret;
},
html() {
let { id, url } = this.computedPairing;
if (id) {
let theme = { typography: id };
return getThemeCode(theme, { id, language: 'html' });
} else {
return cssImport(url, { language: 'html' });
}
},
},
template,
components: {
PageCard,
},
compilerOptions: {
isCustomElement: tag => tag.startsWith('wa-'),
},
};

View File

@@ -1,175 +0,0 @@
import { sample } from '../../scripts/util/array.js';
import { capitalize } from '../../scripts/util/string.js';
import PageCard from './page-card.js';
import { iconLibraries } from '/assets/data/icons.js';
const iconNames = [
'user',
'paper-plane',
'face-laugh',
'pen-to-square',
'trash',
'cart-shopping',
'link',
'sun',
'bookmark',
'sparkles',
'thumbs-up',
'gear',
];
const brands = new Set(['web-awesome', 'font-awesome']);
const ICON_GRID = { columns: 6, rows: 2 };
const TOTAL_ICONS = ICON_GRID.columns * ICON_GRID.rows;
const template = `
<page-card class="icons-card" :class="'icons-' + type + '-card'" :pro="$slots.default ? false : iconsMeta.isPro" :info="iconsMeta">
<template #icon>
<div slot="header" class="icons-icon" :class="'icons-' + type + '-icon'" :style="{ '--columns': ICON_GRID.columns }">
<template v-for="icon of icons">
<wa-icon v-bind="icon"></wa-icon>
</template>
</div>
</template>
<slot></slot>
</page-card>
`;
const defaultDefaults = {
library: 'default',
family: 'classic',
style: 'solid',
};
export default {
props: {
library: String,
family: String,
style: String,
defaults: Object,
type: {
type: String,
validate(value) {
return ['library', 'family', 'style'].includes(value);
},
},
vary: {
type: [Array, String],
validate(value) {
if (Array.isArray(value)) {
return value.every(v => ['family', 'style'].includes(v));
}
return ['family', 'style'].includes(value);
},
default() {
return [];
},
},
},
data() {
return {};
},
created() {
Object.assign(this, { iconNames, brands, ICON_GRID });
},
computed: {
computedLibrary() {
return this.library ?? 'default';
},
libraryMeta() {
return iconLibraries[this.computedLibrary] ?? {};
},
defaultTitle() {
let titles = {};
for (let key in this.computed) {
let value = this.computed[key];
if (key === 'library') {
titles[key] = iconLibraries[value].title;
}
titles[key] ??= capitalize(value);
}
if (this.type) {
return titles[this.type];
} else {
return titles.library + ' ' + titles.family + ' • ' + titles.style;
}
},
icons() {
let { family, style } = this.computed;
let library = this.libraryMeta;
let vary = Array.isArray(this.vary) ? this.vary : [this.vary];
let ret = [];
if (vary.length > 0) {
for (let param of vary) {
let allValues = library[param];
let random = (allValues.random ??= []);
while (random.length < TOTAL_ICONS) {
random.push(sample(allValues));
}
}
}
while (ret.length < TOTAL_ICONS) {
ret.push(
...iconNames.map((name, i) => {
let index = ret.length + i;
return {
library: this.computedLibrary,
name,
family: !this.family && vary.includes('family') ? library.family.random[index] : family,
variant: !this.style && vary.includes('style') ? library.style.random[index] : style,
};
}),
);
}
return ret.slice(0, TOTAL_ICONS);
},
computedDefaults() {
return Object.assign({}, defaultDefaults, this.defaults);
},
computed() {
let { library, family, style } = this;
let ret = { library, family, style };
for (let key in this.computedDefaults) {
if (!ret[key]) {
ret[key] = this.computedDefaults[key];
}
}
return ret;
},
iconsMeta() {
return { title: this.defaultTitle };
},
},
methods: {
capitalize,
},
template,
components: {
PageCard,
},
compilerOptions: {
isCustomElement: tag => tag.startsWith('wa-'),
},
};

View File

@@ -1,12 +0,0 @@
export { default as ColorSelect } from './color-select.js';
export { default as EditableText } from './editable-text.js';
export { default as FontsCard } from './fonts-card.js';
export { default as IconsCard } from './icons-card.js';
export { default as InfoTip } from './info-tip.js';
export { default as PageCard } from './page-card.js';
export { default as PaletteCard } from './palette-card.js';
export { default as SwatchSelect } from './swatch-select.js';
export { default as ThemeCard } from './theme-card.js';
export { default as UiPanelContainer } from './ui-panel-container.js';
export { default as UiPanel } from './ui-panel.js';
export { default as UiScrollable } from './ui-scrollable.js';

View File

@@ -1,38 +0,0 @@
const template = `
<slot>
<wa-icon :slot class="info-tip-default-trigger" :id="id" name="circle-question" variant="regular" tabindex="0"></wa-icon>
</slot>
<wa-tooltip :slot :for="id" ref="tooltip"><slot name="content"></slot></wa-tooltip>
`;
let maxUid = 0;
export default {
props: {
slot: String,
},
data() {
let uid = ++maxUid;
return { uid, id: 'info-tip-' + uid };
},
mounted() {
let tooltip = this.$refs.tooltip;
if (tooltip) {
// Find trigger
let trigger = tooltip.previousElementSibling;
if (trigger) {
if (trigger.id) {
// Already has id
this.id = trigger.id;
} else {
trigger.id = this.id;
}
}
}
},
computed: {},
template,
compilerOptions: {
isCustomElement: tag => tag.startsWith('wa-'),
},
};

View File

@@ -1,83 +0,0 @@
/**
* Generic component for displaying a (possibly interactive) card that represents a page
* For more specific use cases check out theme-card, icons-card, etc.
*/
export const ICON_PLACEHOLDER = `
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 7C1 3.68629 3.68629 1 7 1H43C46.3137 1 49 3.68629 49 7V43C49 46.3137 46.3137 49 43 49H7C3.68629 49 1 46.3137 1 43V7Z" stroke="var(--wa-color-surface-border)" stroke-width="2" stroke-linecap="round" stroke-dasharray="6 6"/>
<path d="M14.1566 18.7199L21.5367 16.7424C22.6036 16.4565 23.7003 17.0896 23.9862 18.1566L26.8463 28.8306C27.1322 29.8975 26.499 30.9942 25.4321 31.2801L18.052 33.2576C16.985 33.5435 15.8884 32.9103 15.6025 31.8434L12.7424 21.1694C12.4565 20.1024 13.0897 19.0057 14.1566 18.7199Z" stroke="var(--wa-color-neutral-border-normal)" stroke-width="2"/>
<path d="M33.8449 16.3273H26.2045C23.9953 16.3273 22.2045 18.1181 22.2045 20.3273V31.3778C22.2045 33.587 23.9953 35.3778 26.2045 35.3778H33.8449C36.0541 35.3778 37.8449 33.587 37.8449 31.3778V20.3273C37.8449 18.1181 36.0541 16.3273 33.8449 16.3273Z" fill="var(--wa-color-neutral-border-normal)" stroke="var(--wa-color-neutral-fill-quiet)" stroke-width="2"/>
</svg>`;
const template = `
<wa-card with-header class="page-card" :aria-disabled="disabled ? 'true' : null" :inert="disabled"
@click="handleClick" @keyup.enter="handleClick" @keyup.space="handleClick"
:role="action ? 'button' : null" :tabindex="action? 0 : null">
<slot name="icon" slot="header">
<div slot="header" v-html="icon || ICON_PLACEHOLDER"></div>
</slot>
<div class="page-name">
<div>
<slot>
{{ content.title }}
<wa-badge class="pro" v-if="pro">PRO</wa-badge>
<div v-if="content.subtitle" class="wa-caption-m">{{ content.subtitle }}</div>
</slot>
</div>
<slot name="extra"></slot>
<wa-icon v-if="action" name="angle-right" class="angle-right" variant="regular"></wa-icon>
</div>
</wa-card>
`;
export default {
props: {
title: String,
subtitle: String,
info: Object,
icon: String,
pro: Boolean,
disabled: Boolean,
action: Function,
},
data() {
return {};
},
created() {
Object.assign(this, { ICON_PLACEHOLDER });
},
computed: {
content() {
let defaultTitle = this.info?.title ?? {};
if (this.title) {
return { title: this.title, subtitle: this.subtitle ?? defaultTitle };
} else {
return { title: defaultTitle, subtitle: this.subtitle };
}
},
},
methods: {
handleClick(event) {
if (this.disabled) {
event.stopImmediatePropagation();
return;
}
if (this.action) {
this.action(event);
}
},
},
template,
components: {},
compilerOptions: {
isCustomElement: tag => tag.startsWith('wa-'),
},
};

View File

@@ -1,63 +0,0 @@
import PageCard from './page-card.js';
import { hues } from '/assets/data/index.js';
import palettes from '/docs/palettes/data.js';
// TODO import from data.js once available
const allHues = [...hues, 'gray'];
const template = `
<page-card class="palette-card" :pro="$slots.default ? false : paletteMeta.isPro" :info="paletteMeta">
<template #icon>
<wa-scoped slot="header" class="palette-icon-host">
<template>
<link rel="stylesheet" :href="'/dist/styles/color/' + palette + '.css'">
<link rel="stylesheet" href="/assets/styles/theme-icons.css">
<div class="palette-icon" style="--hues: {{ hues|length }}; --suffixes: {{ suffixes|length }}">
<template v-for="(hue, hueIndex) of hues">
<div class="swatch" v-for="(suffix, suffixIndex) of suffixes"
:data-hue="hue" :data-suffix="suffix"
:style="{
'--color': 'var(--wa-color-' + hue + suffix + ')',
gridColumn: hueIndex + 1,
gridRow: suffixIndex + 1
}">&nbsp;</div>
</template>
</div>
</template>
</wa-scoped>
</template>
<slot></slot>
<template #extra>
<slot name="extra" />
</template>
</page-card>
`;
export default {
props: {
palette: String,
},
data() {
return {};
},
created() {
Object.assign(this, { hues: allHues, suffixes: ['-80', '', '-20'] });
},
computed: {
paletteMeta() {
return palettes[this.palette] ?? {};
},
},
template,
components: {
PageCard,
},
compilerOptions: {
isCustomElement: tag => tag.startsWith('wa-'),
},
};

View File

@@ -1,173 +0,0 @@
.sidebar.panel-container {
position: relative;
display: flex;
flex-flow: column;
gap: 0;
padding: 0;
width: 32ch;
overflow: hidden;
scrollbar-width: thin;
}
@keyframes back-icon-hover {
to {
transform: translateX(-0.2em);
}
}
.panel {
/* Remove the uniform spacing used in wa-details */
--spacing: 0;
/* Specify value to manually set spacing where needed */
--panel-spacing: var(--wa-space-2xl);
--panel-background: var(--wa-color-surface-default);
display: flex;
flex-flow: column;
max-height: 100%;
margin-bottom: 0;
position: relative;
background: var(--panel-background);
border: none;
transition:
translate var(--wa-transition-slow) allow-discrete,
opacity var(--wa-transition-slow) 25ms allow-discrete;
/* Ensure horizontal scrollbar isn't visible when translate takes effect */
overflow-x: hidden !important;
@starting-style {
display: block;
}
.panel-header {
flex-direction: row-reverse;
justify-content: start;
gap: var(--wa-space-xs);
cursor: pointer;
background: var(--panel-background);
color: var(--wa-color-text-normal);
padding-block-end: var(--panel-spacing);
padding-inline: var(--panel-spacing);
transition: inherit;
transition-property: all;
margin-block: 0;
font-size: inherit;
[data-step='0'] &,
.previous & {
padding-block-start: var(--panel-spacing);
}
.back-icon {
vertical-align: -0.15em;
margin-inline-end: var(--wa-space-xs);
font-size: var(--wa-font-size-m);
transition: transform var(--wa-transition-normal);
}
&:hover .back-icon {
animation: back-icon-hover var(--wa-transition-slow) alternate infinite;
}
label {
pointer-events: none;
font: inherit;
color: inherit;
}
}
.panel-content {
flex: 1;
min-height: 0;
display: flex;
flex-flow: column;
gap: var(--panel-spacing);
padding-block-end: var(--panel-spacing);
padding-inline: var(--panel-spacing);
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin-block-end: 0;
}
&:not(.open) {
padding: 0;
&:not(.previous, .next) {
/* Hide all but the immediately preceding or following steps */
display: none;
}
&.next {
height: 0;
overflow: hidden;
}
&.next {
opacity: 0;
}
&.next {
translate: 100% 0%;
}
.panel-header {
font-size: var(--wa-font-size-s);
margin: 0;
}
.panel-content {
opacity: 0;
pointer-events: none;
content-visibility: hidden;
padding: 0;
}
}
&.open {
flex: 1;
opacity: 1;
.panel-header {
font-size: var(--wa-font-size-l);
.back-icon {
display: none;
}
}
}
.panel-content {
flex: 1;
min-height: 0;
display: flex;
flex-flow: column;
transition: inherit;
@starting-style {
display: flex;
content-visibility: visible;
}
}
&:not(.open) {
&.previous {
.panel-content {
opacity: 0;
translate: -100% 0%;
}
}
&.next {
.panel-content {
opacity: 0;
translate: inherit;
}
}
}
}

View File

@@ -1,89 +0,0 @@
/**
* Scrollable element in a vertical flex container
* Showing shadows as an indicator of scrollability (PE wherever scroll-timeline is supported for now, can be polyfilled with JS later)
*/
.scrollable {
--scroll-shadow-height: 0.5em;
flex-shrink: 1;
min-height: 0;
overflow: auto;
position: relative;
scrollbar-width: inherit;
&:is(.panel-content > div) {
display: flex;
flex-flow: column;
gap: inherit;
}
.scroll-shadow {
position: sticky;
z-index: 1;
inset-inline: 0;
display: block;
&::before {
content: '';
position: absolute;
inset-inline: 0;
height: var(--scroll-shadow-height);
pointer-events: none;
mix-blend-mode: multiply;
background: radial-gradient(farthest-side, var(--wa-color-shadow) 10%, transparent) center / 120% 200%;
transition: var(--wa-transition-slow);
/* transition-property: opacity, transform, display, height, min-height; */
transition-behavior: allow-discrete;
}
}
&:not(.can-scroll-top) .scroll-shadow-top,
&:not(.can-scroll-bottom) .scroll-shadow-bottom {
opacity: 0;
&::before {
height: 0;
}
}
&:not(.can-scroll-top) .scroll-shadow-top {
&::before {
transform: translateY(-100%);
}
}
.scroll-shadow-top {
top: 0;
&::before {
background-position: bottom;
}
}
&:not(.can-scroll-bottom) .scroll-shadow-bottom {
&::before {
transform: translateY(100%);
}
}
.scroll-shadow-bottom {
top: 100%;
&::before {
bottom: 0;
background-position: top;
}
}
}
.scrollable:where(.panel-content) {
.scroll-shadow-top {
/* TODO convert this magic number to a token that explains what it is */
margin-bottom: -18px;
}
.scroll-shadow-bottom {
transform: translateY(var(--padding-bottom, var(--panel-spacing)));
}
}

View File

@@ -1,67 +0,0 @@
import { capitalize } from '../../scripts/util/string.js';
import InfoTip from './info-tip.js';
const template = `
<wa-radio-group :label class="swatch-select" :class="'swatch-shape-' + shape" orientation="horizontal" :value="modelValue" @input="handleInput">
<info-tip v-for="value in values">
<wa-radio-button :value :label="getLabel(value)" :style="{'--color': getColor(value)}"></wa-radio-button>
<template #content>
{{ getLabel(value) }}
</template>
</info-tip>
</wa-radio-group>
`;
export default {
props: {
modelValue: String,
name: String,
label: String,
shape: {
type: String,
default: 'rounded',
validator: value => ['circle', 'rounded'].includes(value),
},
getLabel: {
type: Function,
default: capitalize,
},
getColor: {
type: Function,
default: value => `var(--wa-color-${value})`,
},
values: {
type: Array,
default: [],
},
},
emits: ['update:modelValue', 'input'],
data() {
return {
value: this.modelValue,
};
},
computed: {},
methods: {
capitalize,
handleInput(e) {
this.value = e.target.value;
this.$emit('input', this.value);
},
},
watch: {
value() {
this.$emit('update:modelValue', this.value);
},
},
template,
components: {
InfoTip,
},
compilerOptions: {
isCustomElement: tag => tag.startsWith('wa-'),
},
};

View File

@@ -1,97 +0,0 @@
import PageCard from './page-card.js';
import { getThemeCode } from '/assets/scripts/tweak/code.js';
import themes from '/docs/themes/data.js';
const iconTemplates = {
colors: `
<div class="theme-icon theme-color-icon" role="presentation">
<div style="background: var(--wa-color-brand-fill-loud); border-color: var(--wa-color-brand-border-loud); color: var(--wa-color-brand-on-loud);">A</div>
<div style="background: var(--wa-color-brand-fill-normal); border-color: var(--wa-color-brand-border-normal); color: var(--wa-color-brand-on-normal);">A</div>
<div style="background: var(--wa-color-brand-fill-quiet); border-color: var(--wa-color-brand-border-quiet); color: var(--wa-color-brand-on-quiet);">A</div>
</div>
<div class="wa-invert theme-icon theme-color-icon" role="presentation">
<div style="background: var(--wa-color-brand-fill-loud); border-color: var(--wa-color-brand-border-loud); color: var(--wa-color-brand-on-loud);">A</div>
<div style="background: var(--wa-color-brand-fill-normal); border-color: var(--wa-color-brand-border-normal); color: var(--wa-color-brand-on-normal);">A</div>
<div style="background: var(--wa-color-brand-fill-quiet); border-color: var(--wa-color-brand-border-quiet); color: var(--wa-color-brand-on-quiet);">A</div>
</div>`,
theme: `
<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>
</div>
<div class="row row-2">
<wa-input value="Input" size="small"></wa-input>
<wa-button size="small" variant="brand">Go</wa-button>
</div>`,
};
const template = `
<page-card class="theme-card" :class="type + '-card'" :info="themeMeta">
<template #icon>
<wa-scoped slot="header" class="theme-icon-host" inert>
<template v-html="themeCode"></template>
<template>
<link rel="stylesheet" href="/dist/styles/utilities.css">
<link rel="stylesheet" href="/dist/styles/native/content.css">
<link rel="stylesheet" href="/assets/styles/theme-icons.css">
<template v-if="type == 'colors'" >
${iconTemplates.colors}
</template>
<div v-else class="theme-icon theme-overall-icon" :class="'wa-theme-' + theme" role="presentation">
${iconTemplates.theme}
</div>
</template>
</wa-scoped>
</template>
<slot></slot>
<template #extra>
<slot name="extra" />
</template>
</page-card>
`;
export default {
props: {
theme: String,
type: {
type: String,
validator(value) {
return !value || ['colors'].includes(value);
},
},
rest: Object,
},
data() {
return {};
},
computed: {
themeMeta() {
return themes[this.theme] ?? {};
},
themeCode() {
let theme = { ...(this.rest || {}), [this.type || 'base']: this.theme };
theme.base ||= 'default';
return getThemeCode(theme, { id: this.theme, language: 'html', cdnUrl: '/dist/' });
},
},
template,
components: {
PageCard,
},
compilerOptions: {
isCustomElement: tag => tag.startsWith('wa-'),
},
};

View File

@@ -1,120 +0,0 @@
const template = `
<section class="panel-container" ref="container" :style="{'--panel-step': step}" @open="handleOpen">
<slot ref="panels"></slot>
</section>
`;
export default {
props: {
/** Currently selected id */
modelValue: String,
},
emits: ['update:modelValue'],
data() {
return {
value: '',
previousValue: '',
step: 0,
trail: [],
};
},
mounted() {
let { container } = this.$refs;
let activePanel = container.querySelector(':scope > .open');
if (activePanel) {
let { step, value } = activePanel.dataset;
this.step = Number(step);
this.value = value;
this.$emit('update:modelValue', this.value);
}
},
computed: {
panels() {
if (!this.$refs.container) {
return new Map();
}
let { container } = this.$refs;
return new Map(
[...container.querySelectorAll(':scope > .panel')].map(panel => [
panel.dataset.value,
Number(panel.dataset.step),
]),
);
},
},
methods: {
handleOpen(e) {
let { value, step } = e.detail;
this.value = value;
this.step = step;
},
updatePanels() {
let { container } = this.$refs;
if (!container) {
return;
}
let { step, value } = this;
if (this.panels.get(value) !== step) {
// Hasn't stabilized yet
return;
}
let previousValue = this.trail.findLast(panel => this.panels.get(panel) === step - 1);
for (let panel of container.querySelectorAll(':scope > .panel')) {
let panelStep = Number(panel.dataset.step);
let panelValue = panel.dataset.value;
let isPrevious = previousValue ? panelValue === previousValue : panelStep === step - 1;
let isOpen = panelValue === value;
let isNext = panelStep === step + 1;
panel.classList.toggle('previous', isPrevious);
panel.classList.toggle('open', isOpen);
panel.classList.toggle('next', isNext);
}
},
},
watch: {
value() {
if (this.value !== this.modelValue) {
this.$emit('update:modelValue', this.value);
}
},
modelValue: {
immediate: true,
async handler(value, previousValue) {
if (this.value !== this.modelValue) {
this.value = this.modelValue;
}
if (previousValue) {
this.trail.push(previousValue);
}
this.updatePanels();
},
},
step() {
this.updatePanels();
},
},
template,
components: {},
compilerOptions: {
isCustomElement: tag => tag.startsWith('wa-'),
},
};

View File

@@ -1,73 +0,0 @@
import UiScrollable from './ui-scrollable.js';
const template = `
<ui-scrollable :disabled="!open" role="group" :name="name || 'panel'" :data-value="value" :data-step="step" class="panel" :class="{open}">
<h2 :inert="open" class="panel-header" @click="openPanel" ref="panelHeader">
<wa-icon name="chevron-left" class="back-icon" />
<slot name="title">{{ title }}</slot>
</h2>
<div class="panel-content">
<slot></slot>
</div>
</ui-scrollable>
`;
export default {
props: {
title: String,
name: String,
step: Number,
/** Id of this panel */
value: String,
/** Currently selected id */
modelValue: String,
},
emits: ['update:modelValue', 'open'],
data() {
return {};
},
mounted() {
if (this.open) {
this.$refs.panelHeader.dispatchEvent(
new CustomEvent('open', { detail: { value: this.value, step: this.step }, bubbles: true }),
);
}
},
computed: {
open() {
return this.value === this.modelValue;
},
},
methods: {
openPanel() {
let wasOpen = this.open;
this.$emit('update:modelValue', wasOpen ? '' : this.value);
},
},
watch: {
open: {
immediate: true,
handler(open) {
if (open && this.$refs.panelHeader) {
this.$refs.panelHeader.dispatchEvent(
new CustomEvent('open', { detail: { value: this.value, step: this.step }, bubbles: true }),
);
}
},
},
},
template,
components: {
UiScrollable,
},
compilerOptions: {
isCustomElement: tag => tag.startsWith('wa-'),
},
};

View File

@@ -1,77 +0,0 @@
const template = `
<div class="scrollable" :class="{'can-scroll-top': canScrollTop, 'can-scroll-bottom': canScrollBottom}" ref="container">
<div v-if="!disabled" class="scroll-shadow scroll-shadow-top"></div>
<slot></slot>
<div v-if="!disabled" class="scroll-shadow scroll-shadow-bottom"></div>
</div>
`;
export default {
props: {
disabled: Boolean,
},
data() {
return {
scrollTop: 0,
scrollHeight: 0,
height: 0,
};
},
mounted() {
let { container, content } = this.$refs;
container.addEventListener('scroll', this.handleScroll, { passive: true });
this.scrollHeight = container.scrollHeight;
this.height = container.clientHeight;
},
computed: {
canScrollTop() {
return !this.disabled && this.scrollTop > 1;
},
maxScrollTop() {
return this.scrollHeight - this.height;
},
canScrollBottom() {
return !this.disabled && this.scrollTop < this.maxScrollTop - 1;
},
scrollProgress() {
return this.scrollTop / this.maxScrollTop;
},
scrollProgressEnd() {
return this.scrollProgress + this.maxScrollTop / this.scrollHeight;
},
scrollBottom() {
return this.scrollHeight * this.scrollProgressEnd;
},
},
methods: {
handleScroll(event) {
let { container } = this.$refs;
this.scrollTop = container.scrollTop;
},
},
watch: {
scrollTop(value, oldValue) {
let { container } = this.$refs;
if (container && oldValue === 0) {
this.scrollHeight = container.scrollHeight;
this.height = container.clientHeight;
}
},
},
template,
components: {},
compilerOptions: {
isCustomElement: tag => tag.startsWith('wa-'),
},
};

View File

@@ -65,18 +65,6 @@ Use the `appearance` attribute to change the badge's visual appearance.
</div>
```
### Size
Badges are sized relative to the current font size. You can set `font-size` on any badge (or an ancestor element) to change it.
```html {.example}
<wa-badge variant="brand" style="font-size: var(--wa-font-size-xs);">Brand</wa-badge>
<wa-badge variant="brand" style="font-size: var(--wa-font-size-s);">Brand</wa-badge>
<wa-badge variant="brand" style="font-size: var(--wa-font-size-m);">Brand</wa-badge>
<wa-badge variant="brand" style="font-size: var(--wa-font-size-l);">Brand</wa-badge>
<wa-badge variant="brand" style="font-size: var(--wa-font-size-xl);">Brand</wa-badge>
```
### Pill Badges
Use the `pill` attribute to give badges rounded edges.

View File

@@ -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>
<wa-dropdown hoist>
<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">
<wa-dropdown placement="bottom-end" hoist>
<wa-button slot="trigger" variant="brand" caret>
<span class="wa-visually-hidden">More options</span>
</wa-button>

View File

@@ -6,7 +6,7 @@ icon: card
---
```html {.example}
<wa-card class="card-overview">
<wa-card with-image with-footer class="card-overview">
<img
slot="image"
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
@@ -54,7 +54,7 @@ Headers can be used to display titles and more.
If using SSR, you need to also use the `with-header` attribute to add a header to the card (if not, it is added automatically).
```html {.example}
<wa-card class="card-header">
<wa-card with-header class="card-header">
<div slot="header" class="wa-split">
Header Title
<wa-icon-button name="gear" variant="solid" label="Settings" class="wa-size-m"></wa-icon-button>
@@ -80,7 +80,7 @@ Footers can be used to display actions, summaries, or other relevant content.
If using SSR, you need to also use the `with-footer` attribute to add a footer to the card (if not, it is added automatically).
```html {.example}
<wa-card class="card-footer">
<wa-card with-footer class="card-footer">
This card has a footer. You can put all sorts of things in it!
<div slot="footer" class="wa-split">
@@ -102,7 +102,7 @@ Card images are displayed atop the card and will stretch to fit.
If using SSR, you need to also use the `with-image` attribute to add an image to the card (if not, it is added automatically).
```html {.example}
<wa-card class="card-image">
<wa-card with-image class="card-image">
<img
slot="image"
src="https://images.unsplash.com/photo-1547191783-94d5f8f6d8b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=80"
@@ -124,7 +124,7 @@ Use the `size` attribute to change a card's size.
```html {.example}
<div class="wa-stack">
<wa-card size="small">
<wa-card with-footer size="small">
This is a small card.
<footer slot="footer" class="wa-split">
@@ -133,7 +133,7 @@ Use the `size` attribute to change a card's size.
</footer>
</wa-card>
<wa-card size="medium">
<wa-card with-footer size="medium">
This is a medium card (default).
<footer slot="footer" class="wa-split">
@@ -142,7 +142,7 @@ Use the `size` attribute to change a card's size.
</footer>
</wa-card>
<wa-card size="large">
<wa-card with-footer size="large">
This is a large card.
<footer slot="footer" class="wa-split">

View File

@@ -1,7 +1,7 @@
---
title: Component Cheatsheet
layout: docs
unpublished: true
unlisted: true
---
<style>

View File

@@ -2,8 +2,7 @@
title: Code Demo
description: Code demos can be used to render code examples as inline live demos.
tags: component
isPro: true
unpublished: true
noAlpha: true
---
```html {.example}
@@ -210,4 +209,4 @@ It goes without saying that this list is a rough plan and subject to change.
- Tabbed layout
- Provide a way to display CSS and JS separately
- Provide a way to customize the playground used (currently it is hardcoded to CodePen)
- Provide a way to customize the buttons shown
- Provide a way to customize the buttons shown

View File

@@ -10,7 +10,7 @@ keywords: modal
<!-- cspell:dictionaries lorem-ipsum -->
```html {.example}
<wa-dialog label="Dialog" id="dialog-overview">
<wa-dialog label="Dialog" with-header with-footer id="dialog-overview">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand" data-dialog="close">Close</wa-button>
</wa-dialog>
@@ -27,20 +27,19 @@ keywords: modal
## Examples
### Dialog without Header
### Dialog with Header
Headers are enabled by default. To render a dialog without a header, add the `without-header` attribute.
Headers can be used to display titles and more. Use the `with-header` attribute to add a header to the dialog.
```html {.example}
<wa-dialog label="Dialog" without-header class="dialog-without-header">
<wa-dialog label="Dialog" with-header class="dialog-header">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand" data-dialog="close">Close</wa-button>
</wa-dialog>
<wa-button>Open Dialog</wa-button>
<script>
const dialog = document.querySelector('.dialog-without-header');
const dialog = document.querySelector('.dialog-header');
const openButton = dialog.nextElementSibling;
openButton.addEventListener('click', () => dialog.open = true);
@@ -49,10 +48,10 @@ Headers are enabled by default. To render a dialog without a header, add the `wi
### Dialog with Footer
Footers can be used to display titles and more. Use the `footer` slot to add a footer to the dialog.
Footers can be used to display titles and more. Use the `with-footer` attribute to add a footer to the dialog.
```html {.example}
<wa-dialog label="Dialog" class="dialog-footer">
<wa-dialog label="Dialog" with-footer class="dialog-footer">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand" data-dialog="close">Close</wa-button>
</wa-dialog>
@@ -72,7 +71,7 @@ Footers can be used to display titles and more. Use the `footer` slot to add a f
You can add the special `data-dialog="close"` attribute to a button inside the dialog to tell it to close without additional JavaScript. Alternatively, you can set the `open` property to `false` to close the dialog programmatically.
```html {.example}
<wa-dialog label="Dialog" class="dialog-dismiss">
<wa-dialog label="Dialog" with-header with-footer class="dialog-dismiss">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand" data-dialog="close">Close</wa-button>
</wa-dialog>
@@ -89,10 +88,10 @@ You can add the special `data-dialog="close"` attribute to a button inside the d
### Custom Width
Just use the `--width` custom property to set the dialog's width.
Just use the CSS `width` property to set the dialog's width.
```html {.example}
<wa-dialog label="Dialog" 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>
@@ -112,7 +111,7 @@ Just use the `--width` custom property to set the dialog's width.
By design, a dialog's height will never exceed that of the viewport. As such, dialogs will not scroll with the page ensuring the header and footer are always accessible to the user.
```html {.example}
<wa-dialog label="Dialog" class="dialog-scrolling">
<wa-dialog label="Dialog" with-header with-footer class="dialog-scrolling">
<div style="height: 150vh; border: dashed 2px var(--wa-color-surface-border); padding: 0 1rem;">
<p>Scroll down and give it a try! 👇</p>
</div>
@@ -134,7 +133,7 @@ By design, a dialog's height will never exceed that of the viewport. As such, di
The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/docs/components/icon-button) if needed.
```html {.example}
<wa-dialog label="Dialog" class="dialog-header-actions">
<wa-dialog label="Dialog" with-header with-footer class="dialog-header-actions">
<wa-icon-button class="new-window" slot="header-actions" name="arrow-up-right-from-square" variant="solid"></wa-icon-button>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand" data-dialog="close">Close</wa-button>
@@ -157,7 +156,7 @@ The header shows a functional close button by default. You can use the `header-a
If you want the dialog to close when the user clicks on the overlay, add the `light-dismiss` attribute.
```html {.example}
<wa-dialog label="Dialog" light-dismiss class="dialog-light-dismiss">
<wa-dialog label="Dialog" light-dismiss with-header with-footer class="dialog-light-dismiss">
This dialog will close when you click on the overlay.
<wa-button slot="footer" variant="brand" data-dialog="close">Close</wa-button>
</wa-dialog>
@@ -181,7 +180,7 @@ To keep the dialog open in such cases, you can cancel the `wa-hide` event. When
You can use `event.detail.source` to determine which element triggered the request to close. This example prevents the dialog from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it.
```html {.example}
<wa-dialog label="Dialog" class="dialog-deny-close">
<wa-dialog label="Dialog" with-header with-footer class="dialog-deny-close">
This dialog will only close when you click the button below.
<wa-button slot="footer" variant="brand" data-dialog="close">Only this button will close it</wa-button>
</wa-dialog>
@@ -209,7 +208,7 @@ You can use `event.detail.source` to determine which element triggered the reque
To give focus to a specific element when the dialog opens, use the `autofocus` attribute.
```html {.example}
<wa-dialog label="Dialog" class="dialog-focus">
<wa-dialog label="Dialog" with-header with-footer class="dialog-focus">
<wa-input autofocus placeholder="I will have focus when the dialog is opened"></wa-input>
<wa-button slot="footer" variant="brand" data-dialog="close">Close</wa-button>
</wa-dialog>

View File

@@ -8,7 +8,7 @@ icon: drawer
<!-- cspell:dictionaries lorem-ipsum -->
```html {.example}
<wa-drawer label="Drawer" id="drawer-overview">
<wa-drawer label="Drawer" with-header with-footer class="drawer-overview">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand" data-drawer="close">Close</wa-button>
</wa-drawer>
@@ -16,7 +16,7 @@ icon: drawer
<wa-button>Open Drawer</wa-button>
<script>
const drawer = document.querySelector('#drawer-overview');
const drawer = document.querySelector('.drawer-overview');
const openButton = drawer.nextElementSibling;
openButton.addEventListener('click', () => drawer.open = true);
@@ -25,20 +25,19 @@ icon: drawer
## Examples
### Drawer without Header
### Drawer with Header
Headers are enabled by default. To render a drawer without a header, add the `without-header` attribute.
Headers can be used to display titles and more. Use the `with-header` attribute to add a header to the drawer.
```html {.example}
<wa-drawer label="Drawer" without-header class="drawer-without-header">
<wa-drawer label="Drawer" with-header class="drawer-header">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand" data-drawer="close">Close</wa-button>
</wa-drawer>
<wa-button>Open Drawer</wa-button>
<script>
const drawer = document.querySelector('.drawer-without-header');
const drawer = document.querySelector('.drawer-header');
const openButton = drawer.nextElementSibling;
openButton.addEventListener('click', () => drawer.open = true);
@@ -47,10 +46,10 @@ Headers are enabled by default. To render a drawer without a header, add the `wi
### Drawer with Footer
Footers can be used to display titles and more. Use the `footer` slot to add a footer to the drawer.
Footers can be used to display titles and more. Use the `with-footer` attribute to add a footer to the drawer.
```html {.example}
<wa-drawer label="Drawer" class="drawer-footer">
<wa-drawer label="Drawer" with-footer class="drawer-footer">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand" data-drawer="close">Close</wa-button>
</wa-drawer>
@@ -70,7 +69,7 @@ Footers can be used to display titles and more. Use the `footer` slot to add a f
You can add the special `data-drawer="close"` attribute to a button inside the drawer to tell it to close without additional JavaScript. Alternatively, you can set the `open` property to `false` to close the drawer programmatically.
```html {.example}
<wa-drawer label="Drawer" class="drawer-dismiss">
<wa-drawer label="Drawer" with-header with-footer class="drawer-dismiss">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand" data-drawer="close">Close</wa-button>
</wa-drawer>
@@ -90,7 +89,7 @@ You can add the special `data-drawer="close"` attribute to a button inside the d
By default, drawers slide in from the end. To make the drawer slide in from the start, set the `placement` attribute to `start`.
```html {.example}
<wa-drawer label="Drawer" placement="start" class="drawer-placement-start">
<wa-drawer label="Drawer" with-header with-footer placement="start" class="drawer-placement-start">
This drawer slides in from the start.
<wa-button slot="footer" variant="brand" data-drawer="close">Close</wa-button>
</wa-drawer>
@@ -110,7 +109,7 @@ By default, drawers slide in from the end. To make the drawer slide in from the
To make the drawer slide in from the top, set the `placement` attribute to `top`.
```html {.example}
<wa-drawer label="Drawer" placement="top" class="drawer-placement-top">
<wa-drawer label="Drawer" with-header with-footer placement="top" class="drawer-placement-top">
This drawer slides in from the top.
<wa-button slot="footer" variant="brand" data-drawer="close">Close</wa-button>
</wa-drawer>
@@ -130,7 +129,7 @@ To make the drawer slide in from the top, set the `placement` attribute to `top`
To make the drawer slide in from the bottom, set the `placement` attribute to `bottom`.
```html {.example}
<wa-drawer label="Drawer" placement="bottom" class="drawer-placement-bottom">
<wa-drawer label="Drawer" with-header with-footer placement="bottom" class="drawer-placement-bottom">
This drawer slides in from the bottom.
<wa-button slot="footer" variant="brand" data-drawer="close">Close</wa-button>
</wa-drawer>
@@ -150,7 +149,7 @@ To make the drawer slide in from the bottom, set the `placement` attribute to `b
Use the `--size` custom property to set the drawer's size. This will be applied to the drawer's width or height depending on its `placement`.
```html {.example}
<wa-drawer label="Drawer" class="drawer-custom-size" style="--size: 50vw;">
<wa-drawer label="Drawer" with-header with-footer class="drawer-custom-size" style="--size: 50vw;">
This drawer is always 50% of the viewport.
<wa-button slot="footer" variant="brand" data-drawer="close">Close</wa-button>
</wa-drawer>
@@ -170,7 +169,7 @@ Use the `--size` custom property to set the drawer's size. This will be applied
By design, a drawer's height will never exceed 100% of its container. As such, drawers will not scroll with the page to ensure the header and footer are always accessible to the user.
```html {.example}
<wa-drawer label="Drawer" class="drawer-scrolling">
<wa-drawer label="Drawer" with-header with-footer class="drawer-scrolling">
<div style="height: 150vh; border: dashed 2px var(--wa-color-surface-border); padding: 0 1rem;">
<p>Scroll down and give it a try! 👇</p>
</div>
@@ -192,7 +191,7 @@ By design, a drawer's height will never exceed 100% of its container. As such, d
The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/docs/components/icon-button) if needed.
```html {.example}
<wa-drawer label="Drawer" class="drawer-header-actions">
<wa-drawer label="Drawer" with-header with-footer class="drawer-header-actions">
<wa-icon-button class="new-window" slot="header-actions" name="arrow-up-right-from-square" variant="solid"></wa-icon-button>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand" data-drawer="close">Close</wa-button>
@@ -215,7 +214,7 @@ The header shows a functional close button by default. You can use the `header-a
If you want the drawer to close when the user clicks on the overlay, add the `light-dismiss` attribute.
```html {.example}
<wa-drawer label="Drawer" light-dismiss class="drawer-light-dismiss">
<wa-drawer label="Drawer" light-dismiss with-header with-footer class="drawer-light-dismiss">
This drawer will close when you click on the overlay.
<wa-button slot="footer" variant="brand" data-drawer="close">Close</wa-button>
</wa-drawer>
@@ -239,7 +238,7 @@ To keep the drawer open in such cases, you can cancel the `wa-hide` event. When
You can use `event.detail.source` to determine what triggered the request to close. This example prevents the drawer from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it.
```html {.example}
<wa-drawer label="Drawer" class="drawer-deny-close">
<wa-drawer label="Drawer" with-header with-footer class="drawer-deny-close">
This drawer will only close when you click the button below.
<wa-button slot="footer" variant="brand" data-drawer="close">Close</wa-button>
</wa-drawer>
@@ -262,12 +261,12 @@ You can use `event.detail.source` to determine what triggered the request to clo
</script>
```
### Setting Initial Focus
### Customizing Initial Focus
To give focus to a specific element when the drawer opens, use the `autofocus` attribute.
By default, the drawer's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the drawer. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.
```html {.example}
<wa-drawer label="Drawer" class="drawer-focus">
<wa-drawer label="Drawer" with-header with-footer class="drawer-focus">
<wa-input autofocus placeholder="I will have focus when the drawer is opened"></wa-input>
<wa-button slot="footer" variant="brand" data-drawer="close">Close</wa-button>
</wa-drawer>

View File

@@ -180,3 +180,38 @@ 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>
```

View File

@@ -17,94 +17,11 @@ 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/#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.
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.
```html {.example}
<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>
<wa-icon family="brands" name="font-awesome"></wa-icon>
<wa-icon family="brands" name="web-awesome"></wa-icon>
```
### Colors
@@ -644,4 +561,4 @@ If you want to change the icons Web Awesome uses internally, you can register an
resolver: name => `/path/to/custom/icons/${name}.svg`
});
</script>
```
```

View File

@@ -1,16 +1,14 @@
---
title: Comparer
description: Compare visual differences between similar content with a sliding panel.
title: Image Comparer
description: Compare visual differences between similar photos with a sliding panel.
tags: [imagery, niche]
icon: comparer
icon: image-comparer
---
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.)
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.)
```html {.example}
<wa-comparer>
<wa-image-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"
@@ -21,7 +19,7 @@ The slider can be controlled by dragging or pressing the left and right arrow ke
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-comparer>
</wa-image-comparer>
```
## Examples
@@ -31,7 +29,7 @@ The slider can be controlled by dragging or pressing the left and right arrow ke
Use the `position` attribute to set the initial position of the slider. This is a percentage from `0` to `100`.
```html {.example}
<wa-comparer position="25">
<wa-image-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"
@@ -42,5 +40,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-comparer>
</wa-image-comparer>
```

View File

@@ -176,7 +176,7 @@ eleventyExcludeFromCollections: true
</footer>
<aside slot="aside">
<h2 class="wa-heading-m">Discover More Birds</h2>
<wa-card>
<wa-card with-image>
<div slot="image" class="wa-frame">
<img src="https://images.unsplash.com/photo-1635254859323-65b78408dcca?q=20" alt="" />
</div>
@@ -185,7 +185,7 @@ eleventyExcludeFromCollections: true
<span class="wa-caption-s" lang="la"><em>Asio otus</em></span>
</div>
</wa-card>
<wa-card>
<wa-card with-image>
<div slot="image" class="wa-frame">
<img src="https://images.unsplash.com/photo-1661350356618-f5915c7b6a3c?q=20" alt="" />
</div>
@@ -194,7 +194,7 @@ eleventyExcludeFromCollections: true
<span class="wa-caption-s" lang="la"><em>Surnia ulula</em></span>
</div>
</wa-card>
<wa-card>
<wa-card with-image>
<div slot="image" class="wa-frame">
<img src="https://images.unsplash.com/photo-1660307777355-f08bced145d3?q=20" alt="" />
</div>

View File

@@ -168,7 +168,7 @@ It can be opened using a button with `[data-toggle-nav]` that appears in the `su
</footer>
<aside slot="aside" class="wa-desktop-only">
<h2 class="wa-heading-m">Discover More Birds</h2>
<wa-card>
<wa-card with-image>
<div slot="image" class="wa-frame">
<img src="https://images.unsplash.com/photo-1635254859323-65b78408dcca?q=20" alt="" />
</div>
@@ -177,7 +177,7 @@ It can be opened using a button with `[data-toggle-nav]` that appears in the `su
<span class="wa-caption-s" lang="la"><em>Asio otus</em></span>
</div>
</wa-card>
<wa-card>
<wa-card with-image>
<div slot="image" class="wa-frame">
<img src="https://images.unsplash.com/photo-1661350356618-f5915c7b6a3c?q=20" alt="" />
</div>
@@ -186,7 +186,7 @@ It can be opened using a button with `[data-toggle-nav]` that appears in the `su
<span class="wa-caption-s" lang="la"><em>Surnia ulula</em></span>
</div>
</wa-card>
<wa-card>
<wa-card with-image>
<div slot="image" class="wa-frame">
<img src="https://images.unsplash.com/photo-1660307777355-f08bced145d3?q=20" alt="" />
</div>

View File

@@ -468,20 +468,75 @@ 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 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.
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.
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 boundary="scroll">
<wa-popup placement="top" flip active>
<span slot="anchor"></span>
<div class="box"></div>
</wa-popup>
@@ -537,7 +592,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 boundary="scroll">
<wa-popup placement="top" flip flip-fallback-placements="right bottom" flip-fallback-strategy="initial" active>
<span slot="anchor"></span>
<div class="box"></div>
</wa-popup>
@@ -571,18 +626,14 @@ 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 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.
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`.
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 boundary="scroll">
<wa-popup placement="top" shift shift-padding="10" active>
<span slot="anchor"></span>
<div class="box"></div>
</wa-popup>
@@ -625,11 +676,7 @@ 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 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.
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.
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.
@@ -638,7 +685,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 boundary="scroll">
<wa-popup placement="top" auto-size="both" auto-size-padding="10" active>
<span slot="anchor"></span>
<div class="box"></div>
</wa-popup>

View File

@@ -1,151 +0,0 @@
---
title: Scroller
description: Scrollers create an accessible container while providing visual cues that help users identify and navigate through content that scrolls.
layout: component
tags: [organization]
icon: scroller
---
```html {.example}
<wa-scroller id="scroller__overview">
<table>
<tr>
<th>Party Role</th>
<th>Combat Style</th>
<th>Group Size</th>
<th>Campaign Setting</th>
<th>Signature Traits</th>
</tr>
<tr>
<td>Warrior</td>
<td>Melee Tank</td>
<td>1-2</td>
<td>Forgotten Realms</td>
<td>Plate-armored swordmaster who taunts foes.</td>
</tr>
<tr>
<td>Rogue</td>
<td>Stealth Striker</td>
<td>1</td>
<td>Eberron</td>
<td>Shadowy lockpick with daggers and a secret gold stash.</td>
</tr>
<tr>
<td>Wizard</td>
<td>Spell Slinger</td>
<td>1</td>
<td>Greyhawk</td>
<td>Robe-clad mage hurling fireballs from a messy spellbook.</td>
</tr>
<tr>
<td>Cleric</td>
<td>Divine Support</td>
<td>1</td>
<td>Ravnica</td>
<td>Holy healer with a glowing amulet and sneaky ale habit.</td>
</tr>
<tr>
<td>Bard</td>
<td>Charisma King</td>
<td>1</td>
<td>Dragonlance</td>
<td>Lute-playing charmer with magical songs and bad puns.</td>
</tr>
</table>
</wa-scroller>
<style>
#scroller__overview {
table {
margin-block: 0;
}
th,
td {
white-space: nowrap;
}
th:nth-child(5),
td:nth-child(5) {
min-width: 50ch;
white-space: wrap;
}
}
</style>
```
## Examples
### Adding Content
The scroller component automatically provides a scrollable container for any content that exceeds the available space. Simply add your content as children of the `<wa-scroller>` element, and it will handle the rest.
```html {.example}
<wa-scroller>
<div style="width: 1200px; padding: 1rem;">
<h3>Superhero Team Roles Guide</h3>
<div class="wa-grid" style="--wa-grid-columns: 4; --wa-grid-gap: var(--wa-spacing-l);">
<div>
<h4>Team Leaders</h4>
<p>Charismatic captains like Captain America or Cyclops are the heart of any superteam, rallying everyone with epic speeches and killer strategies. Theyre the ones calling the shots in a cosmic showdown, keeping the squad focused when Thanos or Magneto crashes the party.</p>
</div>
<div>
<h4>Heavy Hitters</h4>
<p>Powerhouses like Thor or Hulk bring the boom, smashing through villain lairs or alien armadas. Their job is to land the big punches, but they gotta pace themselves to avoid stealing the spotlight from sneakier teammates.</p>
</div>
<div>
<h4>Tech Geniuses</h4>
<p>Brainiacs like Iron Man or Mr. Fantastic keep the team one step ahead with gadgets and gizmos. Theyre crafting quinjets or hacking evil AI, always ready with a snarky quip while saving the day from a computer terminal.</p>
</div>
<div>
<h4>Stealth Specialists</h4>
<p>Ninja-like heroes like Black Widow or Nightcrawler slip through the shadows, gathering intel or pulling off surprise attacks. Theyre the glue that makes risky plans work, coordinating with the team to flip a losing fight into a win.</p>
</div>
</div>
</div>
</wa-scroller>
```
### Orientation
Set the `orientation` attribute to `vertical` and provide a height to create a vertical scroller.
```html {.example}
<wa-scroller orientation="vertical" style="max-height: 300px;">
<p>Superhero movies are the ultimate popcorn-fueled thrill rides, turning comic book pages into cinematic rollercoasters. Back in the early 2000s, films like X-Men and Spider-Man kicked open the door, proving tights and teamwork could pack theaters. Those early flicks leaned on practical effects and heart—like Tobey Maguires earnest web-slinger saving a train—making us believe a guy in spandex could be a hero. They werent perfect, but they set the stage for the genre to become a cultural juggernaut.</p>
<p>By the 2010s, the Marvel Cinematic Universe turned superhero films into a shared saga, like a comic crossover event on steroids. The Avengers in 2012 was a game-changer, tossing Iron Mans snark, Thors hammer, and Caps shield into one epic brawl. Directors learned to balance humor, heart, and explosions, while studios figured out how to make every movie feel like a chapter in a bigger story. Even standalone hits like Wonder Woman brought fresh vibes, with Gal Gadots lasso-wielding warrior stealing hearts and smashing box office records.</p>
<p>Today, superhero flicks are a global obsession, from Deadpools chimichanga-fueled chaos to Black Panthers Wakandan pride. Theyre not just about powers—theyre about characters we root for, like Rocket Raccoons scrappy loyalty or Harley Quinns wild energy. Fans dissect trailers like detectives, theorizing about multiverses and cameos, while memes of sad Affleck or dancing Groot flood the internet. Whether its a gritty Joker origin or a cosmic Guardians adventure, these movies keep us glued to our seats, dreaming of heroism and one-liners thatd make even Tony Stark jealous.</p>
</wa-scroller>
```
### Without a Shadow
Use the `without-shadow` attribute to remove the fading shadow effect at the edges of the scroller, which typically indicates more content is available.
```html {.example}
<wa-scroller without-shadow>
<div style="width: 1500px;">
<p>Gaming consoles are like time machines for nerds, zapping us from pixelated 2D adventures to jaw-dropping cinematic worlds. Back in the 90s, the Super Nintendo was the cool kid on the block, using a 16-bit chip to pull off tricks like Mode 7, which made Mario Karts tracks feel like they were zooming right at you. It was like wizardry for a kid with a chunky controller, turning flat sprites into pseudo-3D races that had us yelling at our TVs when we got hit by a red shell.</p>
<p>Fast-forward to today, and consoles like the PlayStation 5 and Xbox Series X are basically supercomputers in sleek boxes. Theyre packing enough power to make games look like Hollywood blockbusters, with lighting so real you can practically feel the sun glare in Spider-Man: Miles Morales. These machines can handle massive open worlds, like the sprawling lands of Elden Ring, without breaking a sweat, letting you swing swords or race cars while your living room feels like a sci-fi movie set. Its a far cry from the SNES days, but the vibes the same: pure, controller-gripping fun.</p>
<p>What makes consoles the heart of gaming culture is how they bring everyone together, from casual players to hardcore speedrunners. Whether its your uncle fumbling through Super Mario World in 92 or your best friend screaming during a late-night Call of Duty match, consoles are the ultimate couch co-op machines. Modern systems even let you stream your clutch Fortnite wins to the world or jump into crossplay with PC pals. From the GameCubes quirky handle to the Switchs grab-and-go joy-cons, every consoles got its own personality, making every era of gaming feel like a legendary chapter in a never-ending quest.</p>
</div>
</wa-scroller>
```
### Without a Scrollbar
Use the `without-scrollbar` attribute to hide the scrollbar while maintaining scroll functionality. This creates a cleaner visual appearance but may reduce usability on content that needs a clear scrolling indicator.
```html {.example}
<wa-scroller without-scrollbar>
<div style="width: 1500px;">
<p>Dungeons & Dragons 5e is the blockbuster superhero flick of tabletop RPGs, turning every session into an epic tavern brawl or dragon-slaying saga. Unlike the old 3.5e days, where youd stack +30 bonuses like a mathlete on a mission, 5e keeps things chill with skill checks capping around +11—like a +5 from your slick Charisma and +6 from being a pro at persuasion. This means even a squad of scrappy kobolds can give your level 10 barbarian a bad day if you roll poorly. Its like the games saying, “Sure, youre a hero, but dont get cocky!”</p>
<p>The advantage and disadvantage system is 5es secret sauce, making every dice roll feel like a movie cliffhanger. Instead of juggling a dozen modifiers, you just roll two d20s and take the better (or worse) one, which shakes out to about a +5 or -5 vibe shift. Its like your rogues got a lucky charm when sneaking past guards or a cursed boot when dodging a fireball. This keeps the games flow snappy, so youre not stuck crunching numbers when you could be roleplaying a dramatic speech to charm a dragon or bluffing your way out of a bandit ambush.</p>
<p>5es world is built for storytelling, not just stat sheets, and thats why its the king of game nights. The rules are flexible enough for your DM to whip up a haunted forest crawl or a pirate ship heist without needing a PhD in game design. Classes like the warlock let you make shady pacts with cosmic entities, while feats like Tavern Brawler turn your monk into a bar-fighting legend who can knock out goblins with a chair. Whether youre a newbie rolling your first d20 or a veteran plotting a castle siege, 5es vibe is all about epic moments—like when your partys wizard crits on a fireball and you all cheer like you just won the Super Bowl.</p>
</div>
</wa-scroller>
```
:::warning
Hiding scrollbars can negatively impact accessibility. Users who rely on visible scrollbars to navigate content may have difficulty recognizing that content is scrollable or controlling their scrolling position. Consider the needs of all users when implementing this option.
:::

View File

@@ -425,3 +425,11 @@ 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>

View File

@@ -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-flex;
display: inline-block;
width: 3rem;
height: 3rem;
margin-right: 0.5rem;

View File

@@ -152,3 +152,25 @@ 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>
```

View File

@@ -2,8 +2,7 @@
title: Viewport Demo
description: Viewport demos can be used to display an iframe as a resizable, zoomable preview.
tags: component
isPro: true
unpublished: true
noAlpha: true
---
```html {.example}
@@ -68,4 +67,4 @@ It goes without saying that this list is a rough plan and subject to change.
- Non-linear zoom scale
- Extend to general content, not just iframes
- Styles for mobile and tablet frames and an attribute to switch between them
- Automatic iframe height
- Automatic iframe height

View File

@@ -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/#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/installation/#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);

View File

@@ -501,13 +501,13 @@ hasOutline: false
</div>
<wa-select name="theme" label="Pick a theme to start!" value="default">
<wa-option value="default">Default</wa-option>
<wa-option value="awesome">Awesome</wa-option>
<wa-option value="premium">Premium</wa-option>
<wa-option value="playful">Playful</wa-option>
<wa-option value="brutalist">Brutalist</wa-option>
<wa-option value="tailspin">Tailspin</wa-option>
<wa-option value="glossy">Glossy</wa-option>
<wa-option value="active">Active</wa-option>
<wa-option data-alpha="remove" value="awesome">Awesome</wa-option>
<wa-option data-alpha="remove" value="premium">Premium</wa-option>
<wa-option data-alpha="remove" value="playful">Playful</wa-option>
<wa-option data-alpha="remove" value="brutalist">Brutalist</wa-option>
<wa-option data-alpha="remove" value="tailspin">Tailspin</wa-option>
<wa-option data-alpha="remove" value="glossy">Glossy</wa-option>
<wa-option data-alpha="remove" value="active">Active</wa-option>
<wa-option value="classic">Classic</wa-option>
</wa-select>
</div>
@@ -667,7 +667,7 @@ hasOutline: false
</wa-details>
</form>
<wa-dialog id="icon-chooser" label="Browse Icons">
<wa-dialog id="icon-chooser" label="Browse Icons" with-header>
<div style="display: grid; grid-template-rows: minmax(0, auto) minmax(0, 1fr); height: 100%; gap: 1rem;">
<div style="display: flex; gap: 1.25rem;">
<wa-input name="icon-search" autofocus placeholder="Search Icons" clearable style="flex: 1 1 auto;">
@@ -2139,7 +2139,7 @@ hasOutline: false
</div>
</section>
<section class="strata message-composer">
<wa-card class="card-header card-footer">
<wa-card with-header with-footer class="card-header card-footer">
<div slot="header">
<div class="grouped-buttons">
<wa-icon-button id="bold" name="bold" label="Bold"></wa-icon-button>

View File

@@ -10,7 +10,7 @@ override:tags: []
{% markdown %}
## Installation
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 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 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:

View File

@@ -4,6 +4,7 @@ description: Callouts are used to display important messages inline.
component: callout
icon: callout
snippets: '.wa-callout'
noAlpha: true
file: styles/native/callout.css
---

View File

@@ -11,8 +11,8 @@ file: styles/native/radio.css
```html {.example}
<div class="wa-cluster">
<label><input type="radio" name="a" value="1" checked> Option 1</label>
<label><input type="radio" name="a" value="2"> Option 2</label>
<label><input type="radio" name="radio" value="1"> Option 1</label>
<label><input type="radio" name="radio" 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="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>
<label><input type="radio" name="radio" value="1"> Option 1</label>
<label><input type="radio" name="radio" value="2"> Option 2</label>
<label><input type="radio" name="radio" value="3" checked> Option 3</label>
</div>
```
@@ -36,9 +36,9 @@ Use the `disabled` attribute to disable a radio.
```html {.example}
<div class="wa-cluster">
<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>
<label><input type="radio" name="radio" value="1"> Option 1</label>
<label><input type="radio" name="radio" value="2" disabled> Option 2</label>
<label><input type="radio" name="radio" value="3"> Option 3</label>
</div>
```
@@ -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="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>
<label><input type="radio" name="radio" value="1"> Option 1</label>
<label><input type="radio" name="radio" value="2"> Option 2</label>
<label><input type="radio" name="radio" value="3"> Option 3</label>
</fieldset>
<br />
<fieldset class="wa-size-m wa-cluster">
<legend>Medium</legend>
<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>
<label><input type="radio" name="radio" value="1"> Option 1</label>
<label><input type="radio" name="radio" value="2"> Option 2</label>
<label><input type="radio" name="radio" value="3"> Option 3</label>
</fieldset>
<br />
<fieldset class="wa-size-l wa-cluster">
<legend>Large</legend>
<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>
<label><input type="radio" name="radio" value="1"> Option 1</label>
<label><input type="radio" name="radio" value="2"> Option 2</label>
<label><input type="radio" name="radio" value="3"> Option 3</label>
</fieldset>
```
@@ -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="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>
<label><input type="radio" name="radio" value="1"> Option 1</label>
<label><input type="radio" name="radio" value="2"> Option 2</label>
<label><input type="radio" name="radio" value="3" checked> Option 3</label>
</div>
```
```html {.example}
<div class="wa-stack">
<label><input type="radio" name="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>
<label><input type="radio" name="radio" value="1"> Option 1</label>
<label><input type="radio" name="radio" value="2"> Option 2</label>
<label><input type="radio" name="radio" value="3" checked> Option 3</label>
</div>
```

View File

@@ -1,32 +0,0 @@
---
layout: null
permalink: '/docs/palettes/data.js'
eleventyExcludeFromCollections: true
---
export default {
{%- for palette in collections.palette | sort %}
{%- if not palette.data.unlisted %}
{% set paletteId = palette.fileSlug -%}
{%- set colors = palettes[paletteId] -%}
'{{ paletteId }}': {
id: '{{ paletteId }}',
title: '{{ palette.data.title }}',
colors: {
{% for hue, tints in colors -%}
'{{ hue }}': {
{% for tint, value in tints -%}
{%- if tint != '05' -%}
'{{ '05' if tint == '5' else tint }}': '{{ value | safe }}',
{%- endif %}
{% endfor %}
get key() {
return this[this.maxChromaTint];
}
},
{% endfor -%} // end colors
}
}, // end palette
{%- endif -%}
{% endfor %}
};

View File

@@ -1,4 +1,6 @@
@import url('/assets/styles/ui.css');
:root {
--fa-sliders-simple: '\f1de';
}
.core-column {
position: relative;
@@ -84,6 +86,17 @@ wa-dropdown > .color.swatch {
margin-top: var(--wa-space-m);
}
.popup {
background: var(--wa-color-surface-default);
border: 1px solid var(--wa-color-surface-border);
padding: var(--wa-space-xl);
border-radius: var(--wa-border-radius-m);
code {
white-space: nowrap;
}
}
.color-scale {
th {
white-space: nowrap;
@@ -169,3 +182,24 @@ wa-dropdown > .color.swatch {
[v-if^='tweaked'] {
display: none;
}
.core-color {
wa-radio-button::part(base) {
width: 2em;
height: 2em;
padding: 0;
border-radius: var(--wa-border-radius-circle);
background: var(--color);
background-clip: border-box;
}
wa-radio-button:is([checked], :state(checked))::part(base) {
box-shadow:
inset 0 0 0 var(--indicator-width) var(--indicator-color),
inset 0 0 0 calc(var(--indicator-width) + 1.5px) var(--wa-color-surface-default);
}
&::part(form-control-input) {
gap: var(--wa-space-xs);
}
}

View File

@@ -1,14 +1,13 @@
// TODO move these to local imports
import Color from 'https://colorjs.io/dist/color.js';
import { createApp, nextTick } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
import { maxGrayChroma, moreHue, selectors, themeConfig } from '../../assets/data/index.js';
import { cdnUrl, hueRanges, hues, tints } from '../../assets/scripts/tweak.js';
import { cdnUrl, hueRanges, hues, Permalink, tints } from '../../assets/scripts/tweak.js';
import { cssImport, cssLiteral, cssRule } from '../../assets/scripts/tweak/code.js';
import { maxGrayChroma, moreHue, selectors, urls } from '../../assets/scripts/tweak/data.js';
import { subtractAngles } from '../../assets/scripts/tweak/util.js';
import Prism from '/assets/scripts/prism.js';
import { SwatchSelect } from '/assets/vue/components/index.js';
import content from '/assets/vue/directives/content.js';
import savedMixin from '/assets/vue/mixins/saved.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)));
@@ -65,7 +64,7 @@ let paletteAppSpec = {
created() {
// Non-reactive variables to expose
Object.assign(this, { moreHue, hues });
Object.assign(this, { moreHue });
this.grayChroma = this.originalGrayChroma;
this.grayColor = this.originalGrayColor;
@@ -122,11 +121,7 @@ let paletteAppSpec = {
code() {
let ret = {};
for (let language of ['html', 'css']) {
let code = getPaletteCode(this.id, this.colors, this.tweaked, {
language,
cdnUrl,
attributes: ' class="wa-palette',
});
let code = getPaletteCode(this.id, this.colors, this.tweaked, { language, cdnUrl });
ret[language] = {
raw: code,
highlighted: Prism.highlight(code, Prism.languages[language], language),
@@ -350,10 +345,6 @@ let paletteAppSpec = {
content,
},
components: {
SwatchSelect,
},
compilerOptions: {
isCustomElement: tag => tag.startsWith('wa-'),
},
@@ -377,7 +368,7 @@ export function getPaletteCode(paletteId, colors, tweaked, options) {
let imports = [];
if (paletteId) {
imports.push(themeConfig.palette.url(paletteId));
imports.push(urls.palette(paletteId));
}
let css = '';

View File

@@ -2,7 +2,6 @@
title: Action Panel
description: 'Help users complete tasks efficiently with quick access to key actions.'
icon: action-panel
isPro: true
---
## Simple
@@ -26,7 +25,7 @@ isPro: true
<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>
<wa-button variant="brand">New Query</wa-button>
</div>
</wa-card>
```
@@ -40,30 +39,21 @@ isPro: true
<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>
<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}
```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>
<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">
<div class="wa-stack wa-gap-0">
<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>
<span class="wa-caption-m">Online</span>
</div>
<div class="wa-cluster" style="font-size: var(--wa-font-size-l);">
<wa-icon-button name="microphone" label="audio-input"></wa-icon-button>
@@ -73,4 +63,4 @@ isPro: true
</div>
</div>
</wa-card>
```
```

View File

@@ -103,13 +103,15 @@ description: 'Track and organize recent user actions or events.'
```html {.example}
<wa-card style="max-width: 70ch; margin: auto">
<h3 class="wa-heading-m">Monthly Activity</h3>
<h3 class="wa-heading-l">Monthly Activity</h3>
<div class="wa-stack">
<wa-details>
<span class="wa-heading-m" slot="summary"> February </span>
<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>
<wa-icon style="font-size: var(--wa-font-size-xl)" 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>
@@ -122,11 +124,13 @@ description: 'Track and organize recent user actions or events.'
</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>
<wa-icon style="font-size: var(--wa-font-size-xl)" 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 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>
@@ -134,21 +138,25 @@ description: 'Track and organize recent user actions or events.'
</div>
</wa-details>
<wa-details>
<span class="wa-heading-m" slot="summary"> March </span>
<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>
<wa-icon style="font-size: var(--wa-font-size-xl)" 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 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>
<wa-icon style="font-size: var(--wa-font-size-xl)" 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>
@@ -162,21 +170,25 @@ description: 'Track and organize recent user actions or events.'
</div>
</wa-details>
<wa-details>
<span class="wa-heading-m" slot="summary"> April </span>
<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>
<wa-icon style="font-size: var(--wa-font-size-xl)" 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 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>
<wa-icon style="font-size: var(--wa-font-size-xl)" 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>
@@ -191,153 +203,4 @@ description: 'Track and organize recent user actions or events.'
</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 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>
```
```

View File

@@ -1,7 +1,6 @@
---
title: Comments
description: 'Enable users to engage in discussions, provide feedback, or record their thoughts.'
isPro: true
---
## Card with Header & Footer
@@ -14,11 +13,11 @@ isPro: true
</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-button appearance="filled">
<wa-icon slot="prefix" name="paperclip" variant="solid"></wa-icon>
Attach a file
</wa-button>
<wa-button variant="brand" size="small">Comment</wa-button>
<wa-button variant="brand">Comment</wa-button>
</div>
</wa-card>
</form>
@@ -39,16 +38,10 @@ isPro: true
<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>
<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>
<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>
@@ -58,15 +51,10 @@ isPro: true
<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>
<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>
<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">
@@ -76,10 +64,7 @@ isPro: true
<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>
<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>
@@ -96,10 +81,7 @@ isPro: true
```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>
<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">
@@ -109,7 +91,7 @@ isPro: true
<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>
<wa-button variant="brand">Comment</wa-button>
</div>
</div>
</div>
@@ -118,16 +100,16 @@ isPro: true
## Rich Card with Multiple Actions
```html {.example}
<wa-card style="max-width: 60ch; margin: auto">
<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"
<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=""
/>
</div>
<span class="wa-heading-l">Heretic</span>
@@ -136,14 +118,7 @@ isPro: true
<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>
<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>
@@ -160,12 +135,10 @@ isPro: true
</div>
</wa-card>
```
## With Preview Pane
```html{.example}
<div style="max-width: 60ch; margin: 0 auto;">
<wa-card class="wa-border-radius-square">
<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>
@@ -188,4 +161,4 @@ isPro: true
</wa-card>
</div>
```
```

View File

@@ -1,101 +1,52 @@
---
title: Data Display
description: 'Convey insights, metrics, and aggregate data at a glance.'
isPro: true
---
## Simple
## Simple
```html {.example}
<wa-card>
<div class="wa-grid wa-gap-3xl" style="--min-column-size: 24ch;">
<div class="wa-grid wa-gap-3xl">
<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>
<span>Revenue</span>
<span>+4.75%</span>
</div>
<wa-format-number
class="wa-heading-xl"
type="currency"
currency="USD"
value="175000000"
lang="en-US"
></wa-format-number>
<div class="wa-heading-2xl">$400,000</div>
</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>
<span>Revenue</span>
<span>+4.75%</span>
</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 class="wa-heading-2xl">$400,000</div>
</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>
<span>Revenue</span>
<span>+4.75%</span>
</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 class="wa-heading-2xl">$400,000</div>
</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>
<span>Revenue</span>
<span>+4.75%</span>
</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 class="wa-heading-2xl">$400,000</div>
</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-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>
@@ -112,7 +63,7 @@ isPro: true
<wa-card>
<div class="wa-flank wa-align-items-start">
<wa-avatar shape="rounded">
<wa-icon slot="icon" name="envelope-open"></wa-icon>
<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>
@@ -129,7 +80,7 @@ isPro: true
<wa-card>
<div class="wa-flank wa-align-items-start">
<wa-avatar shape="rounded">
<wa-icon slot="icon" name="arrow-pointer"></wa-icon>
<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>
@@ -163,16 +114,12 @@ isPro: true
<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
>
<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
>
<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>

View File

@@ -1,7 +1,6 @@
---
title: Description List
description: 'Help users digest detailed information in a structured, easy-to-scan format.'
isPro: true
---
## Left Aligned
@@ -30,10 +29,7 @@ isPro: true
</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>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;">
@@ -172,12 +168,7 @@ isPro: true
<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>
<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>

View File

@@ -1,7 +1,6 @@
---
title: Empty State
description: 'Guide users with helpful prompts and visuals when no content is available.'
isPro: true
---
## Simple
@@ -22,7 +21,7 @@ isPro: true
```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>
<wa-icon name="ufo-beam" class="wa-caption-l" 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>
@@ -31,91 +30,89 @@ isPro: true
## With Templates
```html {.example}
<wa-card style="max-width: 70ch; margin: auto">
<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 havent created a project yet. Get started by selecting a template or start with a blank canvas.
</p>
<p class="wa-caption-m">You havent 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-avatar shape="rounded">
<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>
<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-avatar shape="rounded">
<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>
<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);"
>
<a href=""class="wa-flank wa-align-items-start wa-link-plain">
<wa-avatar shape="rounded">
<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>
<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-avatar shape="rounded">
<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>
<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-avatar shape="rounded">
<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>
<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-avatar shape="rounded">
<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>
<p class="wa-caption-m">
Curate your best memories or most basic food pictures.
</p>
</div>
</a>
</div>
@@ -128,95 +125,83 @@ isPro: true
</div>
</wa-card>
```
## Add people
```html {.example}
## 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">
<wa-icon></wa-icon>
<h1>Add team members</h1>
<p>You havent added any team members to your project yet. As the owner of this project, you can manage team member permissions.</p>
<div class="wa-flank:end">
<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>
<span>Team members previously added to projects</span>
<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>
<wa-avatar></wa-avatar>
<div class="wa-split">
<div class="wa-stack wa-gap-0">
<span>lindsey Walton</span>
<span>Front end Developer</span>
</div>
<wa-button appearance="plain">
<wa-icon name="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>
<wa-avatar></wa-avatar>
<div class="wa-split">
<div class="wa-stack wa-gap-0">
<span>lindsey Walton</span>
<span>Front end Developer</span>
</div>
<wa-button appearance="plain">
<wa-icon name="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>
<wa-avatar></wa-avatar>
<div class="wa-split">
<div class="wa-stack wa-gap-0">
<span>lindsey Walton</span>
<span>Front end Developer</span>
</div>
<wa-button appearance="plain">
<wa-icon name="plus" slot="prefix"></wa-icon>
Invite
</wa-button>
</div>
</div>
<wa-divider></wa-divider>
</section>
<section>
</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>
<wa-avatar></wa-avatar>
<div class="wa-split">
<div class="wa-stack wa-gap-0">
<span>lindsey Walton</span>
<span>Front end Developer</span>
</div>
<wa-button appearance="plain">
<wa-icon name="plus" slot="prefix"></wa-icon>
Invite
</wa-button>
</div>
</div>
<wa-divider></wa-divider>
</section>
</div>
</div>
</wa-card>
```
```

View File

@@ -1,7 +1,6 @@
---
title: FAQ
description: 'Empower users to learn more with a structured list of questions and answers.'
isPro: true
---
## With Flanked Heading & Description
@@ -10,32 +9,20 @@ isPro: true
<div class="wa-flank wa-align-items-start wa-gap-2xl" style="--flank-size: 35ch">
<div>
<h2>Frequently Asked Questions</h2>
<p>
Cant find an answer? Reach out to your local <a href="">Operator</a>, or contact <a href="">the Oracle</a>, and
enjoy a cookie. &#127850;
</p>
<p>Cant find an answer? Reach out to your local <a href="">Operator</a>, or contact <a href="">the Oracle</a>, and enjoy a cookie. &#127850;</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 whos to say Zion isnt
just another layer of the simulation?
</dd>
<dd>Ah, the question that keeps redpills up at night. Sure, we escaped the first Matrix, but whos to say Zion isnt 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? Its terrifying.
</dd>
<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? Its terrifying.</dd>
</div>
<div class="wa-stack wa-gap-xs">
<dt>Can I go back into the Matrix once Im 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>
<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>
@@ -48,20 +35,17 @@ isPro: true
<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 whos to say Zion isnt
just another layer of the simulation?
Ah, the question that keeps redpills up at night. Sure, we escaped the first Matrix, but whos to say Zion isnt 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? Its terrifying.
Because nothing says "unstoppable digital enforcer" like a generic business professional aesthetic. Also, intimidation. You ever try fighting someone in sunglasses and a tie? Its 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 Im out?</h3>
Technically, yes—via hacking in. Emotionally? That depends on how well you handle the knowledge that nothing around
you is real.
Technically, yes—via hacking in. Emotionally? That depends on how well you handle the knowledge that nothing around you is real.
</wa-details>
</div>
```
@@ -74,85 +58,60 @@ isPro: true
<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 whos to say Zion isnt
just another layer of the simulation?
</dd>
<dd>Ah, the question that keeps redpills up at night. Sure, we escaped the first Matrix, but whos to say Zion isnt 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? Its terrifying.
</dd>
<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? Its 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 Im 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>
<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}
## 3 Column
```html{.example}
<div>
<h2>Frequently Asked Questions</h2>
<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 librarys 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>
<dd>A course is updated once there is a fundamental shift in the language or librarys 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 youve 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>
<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 youve 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 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>
<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>
<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>
<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>

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