Compare commits

...

51 Commits

Author SHA1 Message Date
konnorrogers
49bb6af154 3.0.0-alpha.13 2025-05-20 11:44:47 -04:00
konnorrogers
9b8d73a2ea update changelog / gitignore for 3.0.0-alpha.13 2025-05-20 11:44:42 -04:00
Lea Verou
0b4c1a5934 Themer 2nd slice: Look & Feel (#920)
* Exclude Create link from sidebar, for reals this time

* Fix bug

* Very rough prototype of look & feel

* a11y

* Clean up data files

* Automatically generate theme metadata

* Read look & feel params straight from theme

* First stab at dimensionality icons

* Fix rounding 0 bug

* Add border width slider

* [Image-comparer] Expose wrapper as part

* [Comparer] `pointer-events: none` while dragging

* Dark mode slider

* Adjust increments and ranges for look + feel sliders

* Fix preview

* Fix bug where dark mode was not inverted

* Ability to select panel from URL

* Create mixin for Vue form controls and use it in `<swatch-select>`

* Prototype of slider min/max icon buttons

* Nx tooltip

* Icons

* Prevent failed request

* info-tip: Support passing text as prop

* Clearable

* [Brutalist] Match `--wa-shadow-offset-x-scale` to `--wa-shadow-offset-y-scale`

* Add 'Blocky' dimension (derived from Awesome theme)

* Only show Reset button when `clearable` is set

* Remove `clearable` from Look & Feel sliders

* Add tooltips to min/max buttons

* Remove superfluous `aria-label`

* Do not assume that all hyphens in URLs mean nesting, make it explicit

* Formatting

* Fix bug where styles were not applied on page load

* Update Subtle dimension to maximize compatibility

* `<wa-scoped>`: Do not allow non-template children

* Workaround for card not updating

* Update Glossy dimension to maximize compatibility

* Sync scrolling between regular and inverted preview

* Fix bug

* Make changing the base theme reset customizations

* Fix palette page

* Remove cancel button from editable text

* Don't error in theme pages

* Update Playful dimension to maximize compatibility

* Rename 'Look and Feel' to 'Elements' for better parallel structure

* Hide dimensionality controls

* Make back icon motion more subtle

* Expand spacing slider bounds

* Add `tabindex="-1"` where missing in theme showcase

* Remove extraneous gap from theme headers

* fix edit button bug

* rename comparer => comparison; fix aria-controls

* Always save theme name on blur

* Add changelog for themer and new patterns category

---------

Co-authored-by: lindsaym-fa <dev@lindsaym.design>
Co-authored-by: Cory LaViska <cory@abeautifulsite.net>
2025-05-20 10:16:49 -04:00
Kelsey Jackson
8b17b3d3e0 Kj/blog news patterns (#916)
* initial commit to this pr

* updated patterns

* category lists

* post headers

* WIP

* made some content updates

* deleted rogue file

* updated descriptions

* ran prettier

* updated padding

* updated prettierignore

* updated element

* swtiched to token

* updated with category list feedback

* updated with category list feedback

* updated with feedback for featurd post

* updated with feedback for footer

* updated with newsletter feedback'

* updated with feedback to the paywall

* updated with updates for post footer

* more footer updates

* updated with feedback for the post header

* updated with feedback for post list

* updated social share

* updated sign up and login

* added logic to elements to prevent them from closing on escape

* Fix typo (incomplete `<h2>` tag)

* Add missing input labels, fix capitalization and punctuation

* Add description to Blog & News index

* ran prettier on markdown files

* ignored the post list file

* changed hierarchy

---------

Co-authored-by: Cory LaViska <cory@abeautifulsite.net>
Co-authored-by: lindsaym-fa <dev@lindsaym.design>
2025-05-19 10:32:13 -05:00
Cory LaViska
f5d1b74e00 Scroller component (#907)
* add term

* add scroller component

* update color in docs

* prettier

* add vertical example

* use CSS

* add word

* rework shadows and add opt outs

* add examples

* update docs

* Add missing closing tag in 'Adding Content' example

* fix jsdoc

* fix safari pixels

* add changelog entry

---------

Co-authored-by: lindsaym-fa <dev@lindsaym.design>
2025-05-15 20:12:36 +00:00
Cory LaViska
40ea444c48 add slot detection back to drawer; #928 (#931)
* add slot detection back to drawer; #928

* remove unneeded attribute
2025-05-15 12:10:49 -04:00
Cory LaViska
403927687e add slot detection back to dialog; fixes #926 (#927) 2025-05-14 17:53:48 -04:00
lindsaym-fa
6af4e849af Fix issue with button border colors 2025-05-14 10:28:11 -04:00
lindsaym-fa
2c839a4225 Remove Awesome's dimension.css, re-combine with overrides.css 2025-05-14 10:28:11 -04:00
Lea Verou
3703ef26da [Comparer] pointer-events: none while dragging 2025-05-13 17:00:49 -04:00
Lea Verou
3640b4c6e1 [Image-comparer] Expose wrapper as part 2025-05-13 17:00:49 -04:00
Lea Verou
27fc269a94 Have system icons fall back to known families/variants if not found 2025-05-13 14:38:07 -04:00
Konnor Rogers
a5b2fffb7a add unlisted and unpublished support (#919)
* add unlisted and excludeFromSearch filters

* rename unlisted to unreleased, rename excludeFromSearch to unlisted

* add notes for unlisted and unpublished

* prettier

* make unused patterns unpublished

* unreleased -> unpublished

* unreleased -> unpublished

* update contributing
2025-05-12 15:44:25 -04:00
Lea Verou
7de6a676b8 Drop base part from menu-item and menu-label (rel #207) (#884)
* [Menu-item] Drop `base` wrapper (rel #207)

Also add two states: `has-submenu` and `submenu-expanded`

* Add `checked-icon` and `submenu-icon` slots

* [Menu-label] Drop `base` part

* update changelog

---------

Co-authored-by: Cory LaViska <cory@abeautifulsite.net>
2025-05-12 11:26:47 -04:00
Lea Verou
3c77d400f8 [breadcrumb-item] Drop base part, move styling to host (#881)
* [breadcrumb-item] Drop `base` part, move styling to host

* update changelog

---------

Co-authored-by: Cory LaViska <cory@abeautifulsite.net>
2025-05-12 11:19:52 -04:00
Cory LaViska
6ee10f44f4 Add size example to <wa-badge> (#915)
* add size example

* Update docs/docs/components/badge.md

Co-authored-by: Lea Verou <lea@verou.me>

* Update docs/docs/components/badge.md

Co-authored-by: Lea Verou <lea@verou.me>

* Update docs/docs/components/badge.md

Co-authored-by: Lea Verou <lea@verou.me>

---------

Co-authored-by: Lea Verou <lea@verou.me>
2025-05-12 11:02:28 -04:00
Cory LaViska
6afc6e928c remove ssr attributes since we're not using ssr (#917) 2025-05-12 10:51:49 -04:00
lindsaym-fa
f8fcbd60ec Separate dimension overrides into new files 2025-05-12 09:27:27 -04:00
Lea Verou
bdd25571a8 Themer first slice (#857)
* Basic scaffolding

* Generate theme & palette data.js that other JS can import

* Make it possible to include page-card without links

* WIP

* Add `appearance` to details, closes #569

Except `accent` as that's a) far less useful and b) trickier due to the icon color

* Fix broken link

* WIP

* WIP

* Icons icon

* Unify styles for interactive cards

* Prevent focusing inside theme icons

* Fixes

* Action page cards

* Panel scrollables

* scrollable

* Scroll shadows

* Add renaming UI

* UI

* Move styling of heading icons to `ui.css`

* Support permalinks & CRUD

* Make clickable cards more accessible

* Style cards a little better

* Default to styles panel if theme is selected

* Update theme-icons.css

* Custom themes should be saved under Custom

* Get theme code

* Bigger title

* Fixes

* Use theme Vue app for remixing too

* Fix preview jank and make preview script more flexible

* Make radio groups scrollable

* Add affordance to button cards

* Sticky

* `<color-select>`

* Fix theme remixing

* Improve previewing logic

* Fix preview

* Move `domChange()` to separate module

`theme-picker.js` includes side-effects, which may not always be desirable everywhere we may want to import `domChange()`

* Update preview.js

* Panel animation

* Hide Save button if no changes and not saved

* Do not show blank code when no selection has been made

* Use theme slug in filename

* Remove unused component

* Better UI for editing title (and any other text)

* Tweak UI of renaming

* Better indicate default selection

* Fix preview reverting bug

* Fill out app preview with more examples

* Remove `zoom` from theme showcase (yields unexpected/painful results Safari), improve display in wider viewports

* Pending delete

* Make styles panel cards scrollable

* Fix some of the Safari issues

* Update search.css

* Update panel.css

* Select preview UI

* Fix typo

* Frame colors setting as color contrast

* Show dark mode in color mappings

* Brand color

* Swatch styling

* Fix caret icon

* Move Starting theme to the same level as other controls

* Rename typography to Fonts

* Fix bug: Swatch select should show swatches from the selected palette

* Move capitalize to shared utils

* Add utils for handling nested objects

* Icons panel

* Update code.js

* Move utils around

* Add fit and finish to sidebar panels

* Theme card: Move icons to separate data structure

* Move data to dedicated folder since we now have a lot more of it

* Add default icon families and variants to themes

* Data

* Add `deepEntries()`

* Add Duotone

* Spruce up icons preview

* Use theme's icon family in showcase

* Font cards

* Font cards

* Add `max-inline-size` to preview container

* Remove alternate preview options

* Remove theme subtitle

* Support FA kit codes

* Remove Pro badges from theme cards

* Use panagram preview for Fonts

* Consistent heading and label capitalization

* Classes for different icons-card types

* Update data.js.njk

* Variable style on icon family cards

* Fix Sharp Duotone

* Clean up FA kit code hint

* Hide non-functional Icon Library field

* Fix theme icon heights

* icon variant -> style in theme metadata

* Fix bug with icons defaults not being shown

* More convenient theme defaults

* Fix bug with non updating URL

* Fix bug

* Fix multiplying badges

* Custom docs pages

* Add Duotone icons to Mellow theme

* Fix 404

* Remove "Create" from sidebar

* Fix bug

* Move vue components to `/assets/`, move their CSS with them

* Safari/FF compatibility

* Make panels scrollable again

* Fix extra spacing

---------

Co-authored-by: lindsaym-fa <dev@lindsaym.design>
2025-05-09 17:04:06 -04:00
Lea Verou
38c13640fc [Tiny PR, 8 loc diff] Reduce build process noise (#766)
* Reduce build process noise

- Only run 11ty when something has changed within `docs/`
- Only run esbuild if a JS file has changed within

* Update scripts/build.js

Co-authored-by: Konnor Rogers <konnor5456@gmail.com>

---------

Co-authored-by: Cory LaViska <cory@abeautifulsite.net>
Co-authored-by: Konnor Rogers <konnor5456@gmail.com>
2025-05-08 16:05:52 -04:00
Cory LaViska
00a3b1aa8d fix select not opening after disabled (#910) 2025-05-08 14:47:50 -04:00
Cory LaViska
22298781c5 Remove alpha flags (#913)
* remove alpha flags

* revert isPro flag

* update scripts
2025-05-08 13:58:26 -04:00
Lindsay M
fe27710f57 Fix <wa-radio-button> overrides in themes (#914)
* Target `::part()` for `<wa-radio-button>` overrides

* Remove unapplied CSS properties from radio button docs
2025-05-08 13:09:56 -04:00
Cory LaViska
d94589d113 fix radio button validation (#912) 2025-05-07 18:46:24 +00:00
Cory LaViska
3508bf6339 Cloak improvement (#911)
* simplify cloak selector so it's 100% opt-in

* prevent the discovery event from bubbling

* update changelog

* whitespaec
2025-05-07 18:17:26 +00:00
Cory LaViska
61e65ffcb9 Improve native radio alignment (#904)
* show initial checks (feels less broken now)

* improve native radio dot alignment

* Update src/styles/native/radio.css

Co-authored-by: Lindsay M <126139086+lindsaym-fa@users.noreply.github.com>

* Update src/styles/native/radio.css

Co-authored-by: Lindsay M <126139086+lindsaym-fa@users.noreply.github.com>

* Update src/styles/native/radio.css

Co-authored-by: Lindsay M <126139086+lindsaym-fa@users.noreply.github.com>

---------

Co-authored-by: Lindsay M <126139086+lindsaym-fa@users.noreply.github.com>
2025-05-07 16:43:11 +00:00
Lindsay M
aed28adbe4 Update to FA 6.7.2 and add all Duotone styles (#906)
* Update to FA 6.7.2 and add all Duotone styles

* Add changelog
2025-05-02 10:30:51 -04:00
Cory LaViska
15b8bde81b fix matter form control label + focus bug (#897) 2025-05-01 16:20:03 -04:00
Cory LaViska
9ee3fb5d28 Remove old code util; fix color scheme shortcut; fix initial examples (#905)
* fix color scheme shortcut

* remove old unused examples script and styles

* don't open the first example's code _sometimes_
2025-05-01 20:17:27 +00:00
Cory LaViska
47aa376c08 fix width example (#899) 2025-05-01 16:15:21 -04:00
Cory LaViska
69ba974a50 fix select + icon + clear spacing (#903)
* fix select + icon + clear spacing

* update styles

* revert
2025-05-01 18:56:02 +00:00
Lea Verou
8dfb411e5e Add typography metadata to themes 2025-04-30 09:42:27 -04:00
Lea Verou
a35a8fd2ad Theme typography.css fixes
- Add `:where([class^='wa-theme-'], [class*=' wa-theme-'])` to default `typography.css` since we declare mappings that need to be inherited by other themes.
- Remove declarations that redeclare defaults
- Ensure consistent order
- [Awesome] Switch code font to ui-monospace
2025-04-30 09:42:27 -04:00
Cory LaViska
2503005bbd fix radio group margin (#898) 2025-04-29 19:58:08 +00:00
Cory LaViska
78027170ea fix feature request advice (#895) 2025-04-29 14:41:01 -04:00
Konnor Rogers
a20aa48992 fix select (#892)
* fix select

* prettier
2025-04-29 12:33:03 -04:00
Lindsay M
ac8accd664 Fix color contrast in Premium theme, closes #880 (#890)
* Fix `--wa-color-brand-fill-loud` in Premium theme

* jk. *Actually* fix contrast in Premium theme.
2025-04-29 09:52:04 -04:00
Lea Verou
c571573063 [Skeleton] Remove base part, rel #207 (#885) 2025-04-28 17:04:06 -04:00
Lea Verou
e813440315 [Image-comparer] Several fixes + rename to comparer (#883)
Co-authored-by: Cory LaViska <cory@abeautifulsite.net>
2025-04-28 16:58:24 -04:00
Lea Verou
cfc3f181a3 Fix broken demo, improve passthrough CSS (#879)
Fixes several issues for components using the `display: contents` technique:
- Introduce `--display` CSS property to make it possible to override internal display value of base part.
- Ensure `display: contents` is always applied with `!important` to avoid rendering glitches like the one here
- Ensure non-inherited custom properties are also inherited
- Ensure the `hidden` attribute still works.
2025-04-23 11:52:23 -04:00
Konnor Rogers
7545f04c46 remove all references to /docs/installation (#872) 2025-04-21 17:01:09 -04:00
Lea Verou
38bd6528fe Only apply wa-button-group-vertical class when <wa-radio-group> is a radio button group, fixes #870 2025-04-15 18:07:57 -04:00
Lea Verou
2202ea9642 CSS custom properties to set certain component attributes (#447) 2025-04-15 17:56:38 -04:00
Konnor Rogers
58fbcede51 update patterns to isPro (#869) 2025-04-14 14:59:27 -04:00
Lea Verou
971200cc88 Move domChange() to separate module (#868) 2025-04-14 12:52:00 -04:00
Konnor Rogers
b75d3b615c fix login media query and leaking variables with Turbo (#867)
* fix media queries to use wa-desktop-only

* fix errors in remix.js when changing pages

* prettier
2025-04-14 11:11:08 -04:00
Kelsey Jackson
9d1c47449e Kj/first pattern launch (#861)
* dividing up e-commerce patterns

* started invoice table

* building out the filter

* Initial Commit

* switching machines

* switching machines

* started split image pattern:

* commiting to merge in next

* commiting to switch machines

* upated the product detail patterns

* updated product list patterns

* updated some patterns

* filter work

* added utilities to order history

* added utilities to order-summary

* updated width of container

* switching machines

* switching machines

* fixing conflicts

* editing descriptions

* updated descriptions

* adding some polish

* more filter work

* updated prouct preview

* Revisions to "Product Features"

* "Category Filters" revisions + `wa-placeholder` utility

* cleaned up product list

* tweaked product overview

* tweaked order history

* tweak category preview

* "Checkout Form" revisions

* Re-add `navigation` to "Product Features" carousel

* cleaned up shopping cart

* tweaked order summary

* Add missing file extension

* Fix typo in file name

* Revert checkout form changes in attempt to improve diff

* Reimplement checkout form revisions

* cleaned up checkout form

* Add "What's a Pattern?" and "Using Patterns" to index

* Update category descriptions and headings

* Add docs layout for patterns with stylesheet

* tweaked customer review

* Add checkout form example

* a little more polish

* more tweaks

* switching branches to merge

* created e-commerce index

* unlisted links

* updtated content

* updated content for category preview

* updated order history

* committing to bring branches up to next

* inital commit

* Reorganize app patterns into separate pages

* switching machines

* Add link style utilities

* Refactor product list patterns

* more polish

* Refactor product overview patterns

* switching machines

* Refactor shopping cart patterns

* committing to pull down changes

* updated product preview

* updated image

* updated incentives

* Clean up utility usage and code formatting for category previews

* started updating with style utilities

* Clean up utilities and formatting, replace placeholder text in order history

* updated incentive images

* updated reviews

* added review variant

* more polish

* some heavy duty updates

* removed store navigation

* switching machines
:

* Clean up utility usage and code formatting for order summaries

* Clean up utility usage and code formatting for product previews

* big switchover

* removed old file

* working on sidebar

* updated sidebar

* created info category

* splitting up the old blog page

* a little cleanup and adding detail

* got three done

* updated social share

* Quick formatting adjustment

* initial paywalls

* 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>

* some newsletter signups

* added login patterns

* reworked feeds'

* a few more

* added a few more

* add more app patterns

* 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

* switching machines

* catching up with next

* added permissions pattern

* switching machines

* added post articles

* having local browset issues

* a few more app patterns

* took out info patterns

* spiffed up the action panels

* gave the action panels a once over

* added content to data display

* updated a few patterns

* clean up a few e-commerce patterns

* added time componen to reviews

---------

Co-authored-by: lindsaym-fa <dev@lindsaym.design>
Co-authored-by: Lea Verou <lea@verou.me>
Co-authored-by: Lindsay M <126139086+lindsaym-fa@users.noreply.github.com>
2025-04-14 11:10:54 -04:00
Lea Verou
003fd28cb0 Mark code-demo and viewport-demo as pro 2025-04-10 10:57:06 -05:00
Lea Verou
2f300d8930 [Popup] Use viewport as default boundary for flip too (#865) 2025-04-10 10:52:15 -05:00
Lea Verou
f13deb87bb Drop hoist from public API, use Popover API where supported (#351)
* First stab at using the Popover API for PE

* fix popup

* First stab at using the Popover API for PE

* fix popup

* Prettier

* Fix TS error

* Remove workaround

* Default to `strategy = fixed` if Popover API is not supported

* Clear out default UA styles for popovers

* Kill `hoist` with fire 🔥

* Refactor

* Update `@floating-ui/dom`

* Fix flipping and shofting

* Fix autosize

* Use `defaultBoundary` for `flip` too

That way we get the previous behavior for it.

* Remove `strategy`, just use `SUPPORTS_POPOVER` check where relevant

* Remove uses of `strategy`

* Use viewport as the default boundary for shifting and autosizing and add `boundary=scroll` to override

---------

Co-authored-by: konnorrogers <konnor5456@gmail.com>
2025-04-09 16:47:40 -05:00
Konnor Rogers
deb9fd70b3 fix pass through copying (#860)
* fix pass through copying

* prettier
2025-04-04 13:46:41 -04:00
316 changed files with 14214 additions and 5485 deletions

View File

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

View File

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

4
.gitignore vendored
View File

@@ -1,12 +1,8 @@
_site
.cache
.DS_Store
package.json
package-lock.json
dist/
dist-cdn/
docs/public/pagefind
node_modules
src/react
.astro
cdn/

View File

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

View File

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

View File

@@ -5,7 +5,6 @@ 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';
@@ -22,12 +21,10 @@ 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: '',
@@ -36,25 +33,21 @@ 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]);
@@ -109,9 +102,6 @@ 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);
@@ -163,6 +153,15 @@ 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/scripts/tweak/data.js';
export { hueRanges as default } from '../assets/data/index.js';

61
docs/_data/themes.js Normal file
View File

@@ -0,0 +1,61 @@
import fs from 'fs';
import path from 'path';
// import { inlined } from '../../dist/components/icon/library.wa.js';
const __dirname = path.resolve();
const THEME_DIR = path.join(__dirname, 'dist/styles/themes/');
const themeFiles = fs.readdirSync(THEME_DIR).filter(file => file.endsWith('.css') && !file.endsWith('base.css'));
const declarationRegex = /^\s*--wa-(?<property>[a-z-]+)?:\s*(?<value>.+?)\s*(\/\*.+?\*\/)?\s*;$/gm;
const importRegex = /^\s*@import\s+url\(['"](?<path>.+?)['"]\);$/gm;
const themes = {};
for (const file of themeFiles) {
const id = file.replace('.css', '');
const { imports, declarations } = readCSSFile(file);
let theme = { palette: 'default', declarations, imports };
for (const url of imports) {
if (url.endsWith('/color.css')) {
// Color settings
const color = readCSSFile(url);
for (const colorUrl of color.imports) {
if (colorUrl.startsWith('../../color/')) {
// Color palette
theme.palette = getFileSlug(colorUrl);
} else if (colorUrl.startsWith('../../brand/')) {
// Brand color
theme.brand = getFileSlug(colorUrl);
}
}
} else if (url.endsWith('/dimension.css')) {
theme.dimension = true;
}
}
let icon = {};
icon.family = theme.declarations['icon-family'] ?? theme.default?.iconFamily ?? 'classic';
icon.variant = theme.declarations['icon-variant'] ?? theme.default?.iconVariant ?? 'solid';
theme.icons = icon;
theme.rounding = Number(theme.declarations['border-radius-scale'] ?? theme.default?.rounding ?? 1);
theme.spacing = Number(theme.declarations['space-scale'] ?? theme.default?.spacing ?? 1);
theme.borderWidth = Number(theme.declarations['border-width-scale'] ?? theme.default?.borderWidth ?? 1);
themes[id] = theme;
}
export default themes;
function readCSSFile(url) {
const contents = fs.readFileSync(path.join(THEME_DIR, url), 'utf8');
const imports = [...contents.matchAll(importRegex)].map(match => match.groups.path);
const declarations = Object.fromEntries(
[...contents.matchAll(declarationRegex)].map(match => [match.groups.property, match.groups.value]),
);
return { imports, declarations };
}
function getFileSlug(url) {
return url.split('/').pop().replace('.css', '');
}

View File

@@ -4,7 +4,6 @@
{% include 'head.njk' %}
<meta name="theme-color" content="#f36944">
<script type="module" src="/assets/scripts/code-examples.js"></script>
<script type="module" src="/assets/scripts/scroll.js"></script>
<script type="module" src="/assets/scripts/turbo.js"></script>
@@ -20,7 +19,7 @@
</head>
<body class="layout-{{ layout | stripExtension }}{{ ' page-wide' if wide }}">
<!-- use view="desktop" as default to reduce layout jank on desktop site. -->
<wa-page view="desktop" disable-navigation-toggle="">
<wa-page view="desktop" disable-navigation-toggle="" mobile-breakpoint="1140">
<header slot="header" class="wa-split">
{# Logo #}
<div id="docs-branding">
@@ -33,13 +32,13 @@
<span class="wa-desktop-only">{% include "logo.njk" %}</span>
<span class="wa-mobile-only">{% include "logo-simple.njk" %}</span>
</a>
<small id="version-number" class="only-desktop">{{ package.version }}</small>
<wa-badge variant="warning" appearance="filled" class="only-desktop">Alpha</wa-badge>
<small id="version-number" class="wa-desktop-only">{{ package.version }}</small>
<wa-badge variant="warning" appearance="filled" class="wa-desktop-only">Alpha</wa-badge>
</div>
<div id="docs-toolbar" class="wa-cluster wa-gap-xs">
{# Desktop selectors #}
<div class="only-desktop wa-cluster wa-gap-xs">
<div class="wa-desktop-only wa-cluster wa-gap-xs">
{% include "preset-theme-selector.njk" %}
{% include "color-scheme-selector.njk" %}
</div>
@@ -48,7 +47,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="only-desktop">/</kbd>
<kbd slot="suffix" class="wa-desktop-only">/</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 %}<meta name="robots" content="noindex">{% endif %}
{% if noindex or unlisted %}<meta name="robots" content="noindex">{% endif %}
<title>{{ title }}</title>
@@ -32,7 +32,8 @@
<script type="module" src="/assets/scripts/theme-picker.js"></script>
{# Preset Theme #}
{% if forceTheme %}
{% if noTheme %}
{% elif 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 -%}
<a href="{{ page.url }}"{{ page.data.keywords | attr('data-keywords') }}>
{% if page.url %}<a href="{{ page.url }}"{{ page.data.keywords | attr('data-keywords') }}>{% endif %}
<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>
</a>
{% if page.url %}</a>{% endif %}
{% 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 }}"{{ ' data-alpha="remove"' if theme.noAlpha }}>
<wa-option value="{{ theme.page.fileSlug }}">
{{ theme.data.title }}
</wa-option>
{% endfor %}

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
<svg width="96" height="57" viewBox="0 0 96 57" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 1H84C90.0751 1 95 5.92487 95 12V45C95 51.0751 90.0751 56 84 56H12C5.92487 56 1 51.0751 1 45V12C1 5.92487 5.92487 1 12 1Z" fill="white" stroke="#E4E5E9" stroke-width="2"/>
<rect x="7" y="39" width="50" height="11" rx="4" fill="#4895FD"/>
<rect x="7" y="8" width="41" height="5" fill="#616D8A"/>
<rect x="7" y="19.75" width="76" height="3" fill="#D9D9D9"/>
<rect x="7" y="25" width="76" height="3" fill="#D9D9D9"/>
<rect x="7" y="32" width="76" height="3" fill="#D9D9D9"/>
</svg>

After

Width:  |  Height:  |  Size: 587 B

View File

@@ -0,0 +1,3 @@
<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>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,10 @@
<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>

After

Width:  |  Height:  |  Size: 986 B

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,10 @@
<svg width="120" height="87" viewBox="0 0 240 178" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="39" width="67" height="63" rx="12" fill="#D9D9D9"/>
<rect y="115" width="67" height="63" rx="12" fill="#D9D9D9"/>
<rect width="67" height="19" rx="6" fill="#D9D9D9"/>
<rect x="87" y="39" width="67" height="63" rx="12" fill="#D9D9D9"/>
<rect x="174" y="39" width="67" height="63" rx="12" fill="#D9D9D9"/>
<rect x="174" y="7" width="67" height="4.75" rx="2.375" fill="#D9D9D9"/>
<rect x="87" y="115" width="67" height="63" rx="12" fill="#D9D9D9"/>
<rect x="174" y="115" width="67" height="63" rx="12" fill="#D9D9D9"/>
</svg>

After

Width:  |  Height:  |  Size: 631 B

View File

@@ -0,0 +1,31 @@
<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>

After

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 with-header with-footer>
<wa-card>
<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="--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 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>
<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="--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 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>
<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" family="duotone" style="font-size: 1.75em;"></wa-icon>
<wa-icon slot="icon" name="hat-wizard" 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 with-footer>
<wa-card>
<div class="wa-stack">
<div class="wa-split">
<h3 class="wa-heading-m">To-Do</h3>
@@ -118,7 +118,7 @@
<div class="wa-stack">
<h3 class="wa-heading-m">Chalmun's Spaceport Cantina</h3>
<div class="wa-cluster wa-gap-xs">
<wa-rating value="4.6" read-only></wa-rating>
<wa-rating value="4.6" readonly tabindex="-1"></wa-rating>
<strong>4.6</strong>
<span>(419 reviews)</span>
</div>
@@ -140,5 +140,196 @@
</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" tabindex="-1"></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" tabindex="-1">
<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" tabindex="-1">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" tabindex="-1">
<wa-icon slot="prefix" name="at"></wa-icon>
Email
</wa-button>
<wa-button appearance="outlined" tabindex="-1">
<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" tabindex="-1">
<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" tabindex="-1"></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" tabindex="-1">
<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" tabindex="-1">
<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" tabindex="-1">
<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" tabindex="-1">
<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" tabindex="-1">
<span>Or start a deck from scratch</span>
<wa-icon name="arrow-right"></wa-icon>
</a>
</div>
</wa-card>
</div>
</div>

View File

@@ -2,6 +2,7 @@
<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/installation/#quick-start-autoloading-via-cdn">autoloader</a> is the recommended way to import components. If you prefer to do it manually, use one of the following code snippets.
The <a href="/docs/#quick-start-autoloading-via-cdn">autoloader</a> is the recommended way to import components. If you prefer to do it manually, use one of the following code snippets.
</p>
<wa-tab-group label="How would you like to import this component?">

View File

@@ -122,19 +122,8 @@
</div>
<div class="popup">
{% if hue === 'gray' %}
<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>
<swatch-select label="Gray undertone" shape="circle" :values="hues" v-model="grayColor"></swatch-select>
<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

@@ -0,0 +1,5 @@
{% extends '../_layouts/block.njk' %}
{% block head %}
<link href="/docs/patterns/patterns.css" rel="stylesheet">
{% endblock %}

View File

@@ -5,35 +5,22 @@
{% extends '../_includes/base.njk' %}
{% block head %}
<script>
globalThis.wa_data ??= {};
wa_data.baseTheme = "{{ page.fileSlug }}";
wa_data.themes = {
{% for theme in collections.theme -%}
"{{ theme.fileSlug }}": {
"title": "{{ theme.data.title }}",
"palette": "{{ theme.data.palette }}",
"brand": "{{ theme.data.brand }}"
},
{% endfor %}
};
wa_data.palettes = {
{% for palette in collections.palette -%}
"{{ palette.fileSlug }}": {
"title": "{{ palette.data.title }}",
},
{% endfor %}
};
</script>
<link href="{{ page.url }}../remix.css" rel="stylesheet">
<script src="{{ page.url }}../remix.js" type="module"></script>
<script type="module" src="{{ page.url }}../edit/index.js"></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 src='{{ page.url }}demo.html' id="demo"></iframe>
<iframe ref="preview" :src="'{{ page.url }}demo.html' + urlParams" src='{{ page.url }}demo.html' id="demo"></iframe>
<wa-details id="mix_and_match" class="wa-gap-m" >
{% if page.fileSlug !== 'custom' %}
<wa-details id="mix_and_match" class="wa-gap-m" :open="saved || unsavedChanges">
<h4 slot="summary" data-no-anchor data-no-outline id="remix">
<wa-icon name="arrows-rotate"></wa-icon>
Remix this theme
@@ -41,92 +28,64 @@ wa_data.palettes = {
<wa-tooltip for="what-is-remixing">Customize this theme by changing its colors and/or remixing it with design elements from other themes!</wa-tooltip>
</h4>
<wa-select name="colors" label="Colors from…" value="" clearable>
<wa-icon name="palette" slot="prefix" variant="regular"></wa-icon>
{% 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>
{% endfor %}
</wa-select>
<wa-select name="palette" label="Palette" clearable>
<wa-select name="palette" label="Color palette" clearable v-model="theme.palette">
<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-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>
<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 %}
<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-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>
</wa-option>
{% endfor %}
</template>
</wa-select>
<wa-select name="typography" label="Typography from…" clearable>
<wa-select name="typography" label="Typography from…" clearable v-model="theme.typography">
<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-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>
</wa-details>
{% endif %}
<h2>Color</h2>
{% set paletteURL = '/docs/palettes/' + palette + '/' %}
<div class="index-grid">
{% set themePage = page %}
{% set page = paletteURL | getCollectionItemFromUrl %}
{% set pageSubtitle = "Default color palette" %}
{% include 'page-card.njk' %}
{% set page = themePage %}
<wa-card style="--header-background: var(--wa-color-{{ brand }})" class="wa-palette-{{ palette }}">
{% 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}">
<div slot="header"></div>
<div class="page-name">{{ brand | capitalize }}</div>
<div class="wa-caption-s">Default brand color</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>
</wa-card>
</div>
{% endblock %}
@@ -139,7 +98,25 @@ wa_data.palettes = {
You can import this theme from the Web Awesome CDN.
{% set stylesheet = 'styles/themes/' + page.fileSlug + '.css' %}
{% include 'import-stylesheet-code.md.njk' %}
<wa-tab-group class="import-stylesheet-code">
<wa-tab panel="html">In HTML</wa-tab>
<wa-tab panel="css">In CSS</wa-tab>
<wa-tab-panel name="html">
Add the following code to the `<head>` of your page:
```html { v-content:html="code.html.highlighted" }
<link rel="stylesheet" href="{% cdnUrl stylesheet %}" />
```
</wa-tab-panel>
<wa-tab-panel name="css">
Add the following code at the top of your CSS file:
```css { v-content:html="code.css.highlighted" }
@import url('{% cdnUrl stylesheet %}');
```
</wa-tab-panel>
</wa-tab-group>
## Dark mode
@@ -203,5 +180,6 @@ systemDark.addEventListener('change', applyDark);
applyDark();
```
</div> {# end theme app #}
{% endmarkdown %}
{% endblock %}

View File

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

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

View File

@@ -178,10 +178,6 @@ 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
@@ -202,7 +198,7 @@ export function groupPages(collection, options = {}, page) {
options = { tags: options };
}
let { tags, groups, titles = {}, other = 'Other', filter = show } = options;
let { tags, groups, titles = {}, other = 'Other' } = options;
if (groups === undefined && Array.isArray(tags)) {
groups = tags;
@@ -241,10 +237,6 @@ 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;
@@ -407,3 +399,12 @@ export function attr(value, name) {
return safe(ret);
}
/**
* Format an object as JSON, with formatting & indentation (unlike the default `dump` filter)
* @param {*} value
* @returns {string}
*/
export function json(value) {
return JSON.stringify(value, null, 2);
}

View File

@@ -1,23 +0,0 @@
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,9 +24,23 @@ export function searchPlugin(options = {}) {
};
return function (eleventyConfig) {
const pagesToIndex = [];
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;
});
eleventyConfig.addTransform('search', function (content) {
if (!pagesToIndex.has(this.page.inputPath)) {
return content;
}
const doc = parse(content, {
blockTextElements: {
script: false,
@@ -42,7 +56,7 @@ export function searchPlugin(options = {}) {
doc.querySelectorAll(selector).forEach(el => el.remove());
});
pagesToIndex.push({
pagesToIndex.set(this.page.inputPath, {
title: collapseWhitespace(options.getTitle(doc)),
description: collapseWhitespace(options.getDescription(doc)),
headings: options.getHeadings(doc).map(collapseWhitespace),
@@ -65,7 +79,7 @@ export function searchPlugin(options = {}) {
this.field('h', { boost: 10 });
this.field('c');
for (const page of pagesToIndex) {
for (const [_inputPath, 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

@@ -12,7 +12,7 @@ export default class WaScoped extends HTMLElement {
super();
this.attachShadow({ mode: 'open' });
this.observer = new MutationObserver(() => this.render());
this.observer = new MutationObserver(records => this.render(records));
this.observer.observe(this, { childList: true, subtree: true, characterData: true });
}
@@ -23,7 +23,7 @@ export default class WaScoped extends HTMLElement {
);
}
render() {
render(records) {
this.observer.takeRecords();
this.observer.disconnect();
@@ -33,17 +33,18 @@ export default class WaScoped extends HTMLElement {
let nodes = [];
for (let template of this.childNodes) {
// Other solutions we can try if needed: <script type="text/html">, or comment nodes
if (template instanceof HTMLTemplateElement) {
if (template.content.childNodes.length > 0) {
nodes.push(template.content.cloneNode(true));
} else if (template.childNodes.length > 0) {
// Fake template, suck its children out of the light DOM
nodes.push(...template.childNodes);
if (!(template instanceof HTMLTemplateElement)) {
if (template.nodeType === Node.ELEMENT_NODE) {
console.warn('<wa-scoped> can only contain <template> elements');
}
} else {
// Regular child, suck it out of the light DOM
nodes.push(template);
continue;
}
if (template.content.childNodes.length > 0) {
nodes.push(template.content.cloneNode(true));
} else if (template.childNodes.length > 0) {
// Fake template, suck its children out of the light DOM
nodes.push(...template.childNodes);
}
}

View File

@@ -1,21 +1,9 @@
/**
* Data related to theme remixing and palette tweaking
* Data related to palettes and colors.
* Must work in both browser and Node.js
*/
export const cdnUrl = globalThis.document ? document.documentElement.dataset.cdnUrl : '/dist/';
export const urls = {
theme: id => `styles/themes/${id}.css`,
colors: id => `styles/themes/${id}/color.css`,
palette: id => `styles/color/${id}.css`,
brand: id => `styles/brand/${id}.css`,
typography: id => `styles/themes/${id}/typography.css`,
};
export const selectors = {
palette: id =>
[':where(:root)', ':host', ":where([class^='wa-theme-'], [class*=' wa-theme-'])", `.wa-palette-${id}`].join(',\n'),
};
export const tints = ['05', '10', '20', '30', '40', '50', '60', '70', '80', '90', '95'];
export const hueRanges = {
red: { min: 5, max: 35 }, // 30
@@ -29,6 +17,9 @@ 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/
@@ -54,20 +45,3 @@ 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'];

65
docs/assets/data/fonts.js Normal file
View File

@@ -0,0 +1,65 @@
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

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

View File

@@ -0,0 +1,6 @@
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

@@ -0,0 +1,57 @@
---
layout: null
permalink: '/assets/data/palettes.js'
eleventyExcludeFromCollections: true
---
import Color from 'https://colorjs.io/dist/color.js';
const palettes = {
{%- 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 %}
};
// Create Color instances for each color
for (let palette in palettes) {
for (let hue in palettes[palette].colors) {
let scale = palettes[palette].colors[hue];
for (let tint in scale) {
let color = scale[tint];
try {
if (Array.isArray(color)) {
scale[tint] = new Color('oklch', color);
}
else if (typeof color === 'string' && isNaN(color)) {
scale[tint] = new Color(color);
}
} catch (e) {
console.error(e);
}
}
}
}
export default palettes;

View File

@@ -0,0 +1,27 @@
---
layout: null
permalink: '/assets/data/themes.js'
eleventyExcludeFromCollections: true
---
export default {
{%- for theme in collections.theme | sort %}
{%- if not theme.data.unlisted and theme.fileSlug !== 'edit' and theme.fileSlug !== 'custom' %}
{% set themeId = theme.fileSlug -%}
{%- set themeMeta = themes[themeId] -%}
'{{ themeId }}': {
id: '{{ themeId }}',
title: '{{ theme.data.title }}',
palette: '{{ themeMeta.palette }}',
brand: '{{ themeMeta.brand }}',
isPro: {{ theme.data.isPro or 'pro' in theme.data.tags }},
fonts: {{ (theme.data.fonts | json or 'null') | safe }},
icons: {{ (themeMeta.icons | json or 'null') | safe }},
rounding: {{ themeMeta.rounding }},
spacing: {{ themeMeta.spacing }},
borderWidth: {{ themeMeta.borderWidth }},
dimension: {{ (theme.data.dimension or themeMeta.dimension or false) | json | safe }},
},
{%- endif %}
{% endfor %}
};

118
docs/assets/data/theming.js Normal file
View File

@@ -0,0 +1,118 @@
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';
},
},
},
rounding: {
cssProperty: '--wa-border-radius-scale',
default(baseTheme) {
return baseTheme?.rounding ?? 1;
},
},
spacing: {
cssProperty: '--wa-space-scale',
default(baseTheme) {
return baseTheme?.spacing ?? 1;
},
},
borderWidth: {
cssProperty: '--wa-border-width-scale',
default(baseTheme) {
return baseTheme?.borderWidth ?? 1;
},
},
dimensionality: {
url: id => `styles/themes/${id}/dimension.css`,
docs: '/docs/themes/',
icon: 'cube',
default() {
return this.base;
},
},
};
export function getPath(key) {
if (key.startsWith('icon-')) {
// TODO detect what the nested prefixes are from theme config metadata
return ['icon', ...key.slice(5)];
}
}
// 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

@@ -1,59 +0,0 @@
document.addEventListener('click', event => {
const toggle = event.target?.closest('.code-example-toggle');
const pen = event.target?.closest('.code-example-pen');
// Toggle source
if (toggle) {
const codeExample = toggle.closest('.code-example');
const isOpen = !codeExample.classList.contains('open');
toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
codeExample.classList.toggle('open', isOpen);
}
// Edit in CodePen
if (pen) {
const codeExample = pen.closest('.code-example');
const code = codeExample.querySelector('code');
const cdnUrl = document.documentElement.dataset.cdnUrl;
const html =
`<script 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" with-header light-dismiss>
<wa-dialog class="diff-dialog" light-dismiss>
<div class="diff-grid">
<div>
<div>Server</div>

View File

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

View File

@@ -1,4 +1,5 @@
const IDENTITY = x => x;
import { deepEach, deepGet, deepSet } from './util/deep.js';
import { camelCase, kebabCase } from './util/string.js';
export default class Permalink extends URLSearchParams {
/** Params changed since last URL I/O */
@@ -13,6 +14,79 @@ 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.map(kebabCase).join('-');
if (typeof value === 'object') {
// We'll handle this when we descend into it
return;
}
let defaultValue = deepGet(defaults, fullPath);
if (equals(value, defaultValue)) {
// Remove the param from the URL
this.delete(param);
return;
}
this.set(param, value);
});
}
/**
* Convert the URL params to a (potentially nested) object.
* @param {object} options - Options object.
* @param {(key: string, value: string) => string[]} options.getPath - Function to get the path of a param.
* @returns {object} The nested object.
*/
toObject(options = {}) {
// Default getPath() assumes hyphens always mean nesting
let { ignoreKeys = [], getPath = param => param.split('-') } = options;
// Get all values as a nested object
let obj = {};
for (let [key, value] of this.entries()) {
let path = getPath(key, value);
if (path === null || ignoreKeys.includes(key)) {
// Skip this param
continue;
}
// Default to key if `getPath()` returns undefined
path ??= key;
path = Array.isArray(path) ? path : [path];
// Camel case any remaining hyphens
path = path.map(camelCase);
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

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

View File

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

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,7 +1,8 @@
/**
* Get import code for remixed themes and tweaked palettes.
*/
import { urls } from './data.js';
import { selectors, themeConfig } from '../../data/theming.js';
import { deepEach, deepGet } from '/assets/scripts/util/deep.js';
export function cssImport(url, options = {}) {
let { language = 'html', cdnUrl = '/dist/', attributes } = options;
@@ -21,29 +22,65 @@ export function cssLiteral(value, options = {}) {
if (language === 'css') {
return value;
} else {
return `<style>\n${value}\n</style>`;
return `<style${options.attributes ?? ''}>\n${value}\n</style>`;
}
}
// Params in correct order
export const themeParams = ['colors', 'palette', 'brand', 'typography'];
/**
* 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';
export function getThemeCode(base, params, options) {
let ret = [];
deepEach(themeConfig, (config, aspect, obj, path) => {
if (!config?.default) {
// We're not in a config object
return;
}
if (base) {
ret.push(urls.theme(base));
}
let value = deepGet(theme, [...path, aspect]);
for (let aspect of themeParams) {
let value = params[aspect];
if (!value && value !== 0) {
return;
}
if (value) {
ret.push(urls[aspect](value));
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);
if (theme.icon?.kit) {
let faKitAttribute = ` data-fa-kit-code="${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;
}
}
return ret.map(url => cssImport(url, options)).join('\n');
return ret;
}
export function cssRule(selector, declarations, { indent = ' ' } = {}) {

View File

@@ -0,0 +1,17 @@
/**
* 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

@@ -0,0 +1,180 @@
/**
* @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

@@ -0,0 +1,39 @@
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

@@ -0,0 +1,42 @@
/**
* 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();
}
/**
* Convert a string to camel case.
* @param {string} str - The string to convert.
* @returns {string} The camel case string.
*/
export function camelCase(str) {
return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
}
/**
* Convert a string to kebab case.
* @param {string} str - The string to convert.
* @returns {string} The kebab case string.
*/
export function kebabCase(str) {
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
}

View File

@@ -1,87 +0,0 @@
.code-example {
border: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet);
border-radius: var(--wa-border-radius-l);
color: var(--wa-color-text-normal);
margin-block-end: var(--wa-flow-spacing);
}
.code-example-preview {
padding: 2rem;
border-bottom: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet);
> :first-child {
margin-block-start: 0;
}
> :last-child {
margin-block-end: 0;
}
}
.code-example-source {
border-bottom: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet);
}
.code-example:not(.open) .code-example-source {
display: none;
}
.code-example.open .code-example-toggle wa-icon {
rotate: 180deg;
}
.code-example-source pre {
position: relative;
border-radius: 0;
margin: 0;
white-space: normal;
}
.code-example-source:not(:has(+ .code-example-buttons)) {
border-bottom: none;
pre {
border-bottom-right-radius: var(--wa-border-radius-l);
border-bottom-left-radius: var(--wa-border-radius-l);
}
}
.code-example-buttons {
display: flex;
align-items: stretch;
button {
all: unset;
flex: 1 0 auto;
font-size: 0.875rem;
color: var(--wa-color-text-quiet);
border-left: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet);
text-align: center;
padding: 0.5rem;
cursor: pointer;
&:first-of-type {
border-left: none;
border-bottom-left-radius: var(--wa-border-radius-l);
}
&:last-of-type {
border-bottom-right-radius: var(--wa-border-radius-l);
}
&:focus-visible {
outline: var(--wa-focus-ring);
}
}
.code-example-pen {
flex: 0 0 100px;
white-space: nowrap;
}
wa-icon {
width: 1em;
height: 1em;
vertical-align: -2px;
}
}

View File

@@ -1,4 +1,3 @@
@import 'code-examples.css';
@import 'code-highlighter.css';
@import 'copy-code.css';
@import 'outline.css';
@@ -257,15 +256,6 @@ 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;
@@ -393,19 +383,7 @@ 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;
@@ -419,11 +397,11 @@ wa-page > main:has(> .index-grid) {
min-block-size: calc(6rem + var(--spacing));
}
}
}
.page-name {
font-size: var(--wa-font-size-s);
font-weight: var(--wa-font-weight-action);
}
wa-card .page-name {
font-size: var(--wa-font-size-s);
font-weight: var(--wa-font-weight-action);
}
.index-category {
@@ -432,6 +410,146 @@ wa-page > main:has(> .index-grid) {
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;
@@ -608,13 +726,6 @@ table.colors {
margin-block-end: var(--wa-flow-spacing);
}
/** mobile */
@media screen and (max-width: 768px) {
wa-page .only-desktop {
display: none;
}
}
/** desktop */
@media screen and not (max-width: 768px) {
/* Navigation sidebar */

View File

@@ -41,9 +41,9 @@
}
/* Header */
header {
#site-search-container header {
flex: 0 0 auto;
align-items: middle;
align-items: center;
/* 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,4 +1,9 @@
wa-card:has(> .theme-icon-host, > [slot='header'] > .theme-icon-host) {
wa-card:has(
> .theme-icon-host,
> [slot='header'] > .theme-icon-host,
> .fonts-icon-host,
> [slot='header'] > .fonts-icon-host
) {
&::part(header) {
/* We want to add a background color, so any spacing needs to go on .theme-icon */
flex: 1;
@@ -12,6 +17,7 @@ wa-card:has(> .theme-icon-host, > [slot='header'] > .theme-icon-host) {
}
.theme-icon-host,
.fonts-icon-host,
.palette-icon-host {
flex: 1;
border-radius: inherit;
@@ -23,12 +29,17 @@ wa-card:has(> .theme-icon-host, > [slot='header'] > .theme-icon-host) {
}
}
.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 {
@@ -42,7 +53,8 @@ wa-card:has(> .theme-icon-host, > [slot='header'] > .theme-icon-host) {
}
}
.theme-icon {
.theme-icon,
.fonts-icon {
min-width: 18ch;
padding: var(--wa-space-xs) var(--wa-space-m);
border-radius: inherit;
@@ -57,11 +69,18 @@ wa-card:has(> .theme-icon-host, > [slot='header'] > .theme-icon-host) {
}
.theme-color-icon {
display: grid;
display: flex;
gap: var(--wa-space-xs);
grid-template-columns: repeat(4, auto);
min-width: 15ch;
background: var(--wa-color-surface-lowered);
& + & {
border-start-start-radius: 0;
border-start-end-radius: 0;
}
div {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
@@ -72,26 +91,17 @@ wa-card:has(> .theme-icon-host, > [slot='header'] > .theme-icon-host) {
padding: var(--wa-space-2xs) var(--wa-space-xs);
color: var(--text-color);
font-weight: var(--wa-font-weight-semibold);
&.plain {
font-weight: var(--wa-font-weight-bold);
}
}
}
.theme-typography-icon {
display: flex;
flex-direction: column;
gap: var(--wa-space-xs);
}
.theme-overall-icon {
.theme-icon.theme-overall-icon,
.fonts-icon {
display: flex;
flex-flow: column;
gap: var(--wa-space-xs);
gap: var(--wa-space-2xs);
justify-content: center;
width: 100%;
min-height: 7.5rem;
min-height: 6.75rem;
box-sizing: border-box;
background: var(--wa-color-surface-lowered);
@@ -130,3 +140,78 @@ wa-card:has(> .theme-icon-host, > [slot='header'] > .theme-icon-host) {
}
}
}
.theme-icon.theme-dimensionality-icon {
display: flex;
flex-flow: column;
gap: var(--wa-space-2xs);
justify-content: center;
width: 100%;
min-height: 6.75rem;
box-sizing: border-box;
wa-card {
display: block;
&::part(body) {
display: flex;
gap: var(--wa-space-xs);
}
wa-input {
flex: 4;
min-width: 1em;
}
wa-button {
flex: 1;
}
}
}
.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);
}

145
docs/assets/styles/ui.css Normal file
View File

@@ -0,0 +1,145 @@
/* 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

@@ -0,0 +1,78 @@
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

@@ -0,0 +1,86 @@
import inputMixin from '../mixins/input.js';
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" @blur="handleBlur" />
<wa-icon-button v-if="blur !== 'done'" name="check" label="Done editing" @click="done"></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 {
mixins: [inputMixin],
props: {
label: {
type: String,
default: 'Rename',
},
blur: {
type: String,
validator(value) {
return ['', 'done', 'cancel'].includes(value);
},
},
},
emits: ['update:modelValue', 'submit'],
data() {
return {
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;
},
handleBlur(event) {
this.done(event);
},
},
template,
compilerOptions: {
isCustomElement: tag => tag.startsWith('wa-'),
},
};

View File

@@ -0,0 +1,132 @@
import themes from '../../data/themes.js';
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';
const template = `
<page-card class="fonts-card" :info="computedPairing">
<template #icon>
<wa-scoped slot="header" class="fonts-icon-host" inert :key="html">
<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

@@ -0,0 +1,175 @@
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

@@ -0,0 +1,13 @@
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';
export { default as UiSlider } from './ui-slider.js';

View File

@@ -0,0 +1,39 @@
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">{{ text }}</slot></wa-tooltip>
`;
let maxUid = 0;
export default {
props: {
slot: String,
text: 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

@@ -0,0 +1,83 @@
/**
* 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

@@ -0,0 +1,63 @@
import palettes from '../../data/palettes.js';
import PageCard from './page-card.js';
import { hues } from '/assets/data/index.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

@@ -0,0 +1,167 @@
.sidebar.panel-container {
position: relative;
display: flex;
flex-flow: column;
gap: 0;
padding: 0;
width: 32ch;
overflow: hidden;
scrollbar-width: thin;
}
.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) 50ms 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) var(--wa-transition-easing);
}
&:hover .back-icon {
transform: translateX(-0.25em);
}
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

@@ -0,0 +1,89 @@
/**
* 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

@@ -0,0 +1,54 @@
import { capitalize } from '../../scripts/util/string.js';
import inputMixin from '../mixins/input.js';
import InfoTip from './info-tip.js';
const template = `
<wa-radio-group :label class="swatch-select" :class="'swatch-shape-' + shape" orientation="horizontal" :value @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 {
mixins: [inputMixin],
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: [],
},
},
computed: {},
methods: {
capitalize,
},
template,
components: {
InfoTip,
},
compilerOptions: {
isCustomElement: tag => tag.startsWith('wa-'),
},
};

View File

@@ -0,0 +1,119 @@
import themes from '../../data/themes.js';
import { capitalize } from '../../scripts/util/string.js';
import PageCard from './page-card.js';
import { getThemeCode } from '/assets/scripts/tweak/code.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>`,
dimensionality: `
<wa-card size="small">
<wa-input value="Input" size="small"></wa-input>
<wa-button size="small" variant="brand">Go</wa-button>
</wa-card>
`,
overall: `
<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" :data-theme="theme">
<template #icon>
<wa-scoped slot="header" class="theme-icon-host" inert :key="themeCode">
<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-if="type in iconTemplates && type !== 'overall'" class="theme-icon" :class="'theme-' + type + '-icon'" v-html="iconTemplates[type]" role="presentation">
</div>
<div v-else class="theme-icon theme-overall-icon" :class="'wa-theme-' + theme" role="presentation">
${iconTemplates.overall}
</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 || value in iconTemplates;
},
},
rest: Object,
},
data() {
return {};
},
created() {
this.iconTemplates = iconTemplates;
},
computed: {
themeMeta() {
let ret = themes[this.theme] ? { ...themes[this.theme] } : {};
// if (this.type === 'dimensionality' && typeof ret.dimension === 'string') {
// ret.title = capitalize(ret.dimension);
// }
return ret;
},
themeCode() {
let theme = { ...(this.rest || {}), [this.type || 'base']: this.theme };
theme.base ||= 'default';
// if (theme.dimensionality) {
// if (!themes[theme.dimensionality]?.dimension || theme.dimensionality === theme.base) {
// theme.dimensionality = '';
// }
// }
return getThemeCode(theme, { id: this.theme, language: 'html', cdnUrl: '/dist/' });
},
},
template,
components: {
PageCard,
},
compilerOptions: {
isCustomElement: tag => tag.startsWith('wa-'),
},
};

View File

@@ -0,0 +1,120 @@
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

@@ -0,0 +1,73 @@
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

@@ -0,0 +1,77 @@
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

@@ -0,0 +1,46 @@
.ui-slider {
display: grid;
grid-template:
'label label label'
'min slider max';
grid-template-columns: auto 1fr auto;
align-items: center;
gap: var(--wa-space-2xs);
wa-slider {
display: block;
grid-area: slider;
width: 100%;
}
&:has(.ui-slider-min) wa-slider {
&::part(label) {
margin-inline-start: calc(-1 * (var(--wa-space-s) + 1rem + 2 * var(--wa-border-width-m)));
}
}
.clear-button {
vertical-align: middle;
margin-inline-start: var(--wa-space-xs);
font-size: var(--wa-font-size-xs);
}
}
.ui-slider-header {
grid-area: label;
}
.ui-slider-min,
.ui-slider-max {
width: min-content;
}
.ui-slider-min {
grid-area: min;
margin-inline-start: calc(-1 * var(--wa-space-s));
}
.ui-slider-max {
grid-area: max;
margin-inline-end: calc(-1 * var(--wa-space-s));
}

View File

@@ -0,0 +1,86 @@
import inputMixin from '../mixins/input.js';
import InfoTip from './info-tip.js';
let maxUid = 0;
const template = `
<div class="ui-slider">
<div class="ui-slider-header">
<label :for="sliderId">{{ label }}</label>
<info-tip v-if="clearable && (value !== defaultValue ?? initialValue)" :text="'Reset to ' + valueFormatter(defaultValue ?? initialValue)">
<wa-icon-button @click="value = defaultValue ?? initialValue" class="clear-button" name="circle-xmark" library="system" variant="regular" :label="'Reset to ' + tooltipFormatter(defaultValue ?? initialValue)"></wa-icon-button>
</info-tip>
</div>
<info-tip v-if="$slots.min" :text="'Set to min (' + valueFormatter(min) + ')'">
<wa-button class="ui-slider-min" appearance="plain" size="small" @click="value = min"><slot name="min"></slot></wa-button>
</info-tip>
<wa-slider ref="slider" :id="sliderId" class="ui-slider" :value @input="handleInput"
:min="min" :max="max" :step="step">
</wa-slider>
<info-tip v-if="$slots.max" :text="'Set to max (' + valueFormatter(max) + ')'">
<wa-button class="ui-slider-max" appearance="plain" size="small" @click="value = max"><slot name="max"></slot></wa-button>
</info-tip>
</div>
`;
export default {
mixins: [inputMixin],
props: {
label: String,
id: String,
defaultValue: Number,
min: {
type: Number,
default: 0,
},
max: {
type: Number,
default: 100,
},
step: {
type: Number,
default(rawProps) {
return (rawProps.max - rawProps.min) / 100;
},
},
format: [Function, String],
clearable: Boolean,
},
data() {
let uid = ++maxUid;
return { uid, value: this.modelValue };
},
mounted() {
if (this.format) {
this.$refs.slider.tooltipFormatter = this.valueFormatter;
}
},
computed: {
sliderId() {
return this.id || `ui-slider-${this.uid}`;
},
valueFormatter() {
if (typeof this.format === 'string') {
return v => this.format.replaceAll('{value}', v);
}
return this.format;
},
},
watch: {
tooltip() {
if (this.$refs.slider) {
this.$refs.slider.tooltipFormatter = this.tooltipFormatter;
}
},
},
template,
components: {
InfoTip,
},
compilerOptions: {
isCustomElement: tag => tag.startsWith('wa-'),
},
};

View File

@@ -0,0 +1,32 @@
/**
* Mixin for components that behave like form controls.
*/
export default {
props: {
modelValue: {
type: [String, Number, Boolean],
},
},
data() {
return {
initialValue: this.modelValue,
value: this.modelValue,
};
},
emits: ['update:modelValue', 'input'],
methods: {
handleInput(e) {
this.value = e.target.value;
this.$emit('input', this.value);
},
},
watch: {
value(value) {
this.$emit('update:modelValue', value);
},
modelValue(value) {
this.value = value;
},
},
};

View File

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

View File

@@ -65,6 +65,18 @@ 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 hoist>
<wa-dropdown>
<wa-button slot="trigger" caret>Dropdown</wa-button>
<wa-menu>
<wa-menu-item>Item 1</wa-menu-item>
@@ -185,7 +185,7 @@ Create a split button using a button and a dropdown. Use a [visually hidden](/do
```html {.example}
<wa-button-group label="Example Button Group">
<wa-button variant="brand">Save</wa-button>
<wa-dropdown placement="bottom-end" hoist>
<wa-dropdown placement="bottom-end">
<wa-button slot="trigger" variant="brand" caret>
<span class="wa-visually-hidden">More options</span>
</wa-button>

View File

@@ -6,7 +6,7 @@ icon: card
---
```html {.example}
<wa-card with-image with-footer class="card-overview">
<wa-card 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 with-header class="card-header">
<wa-card 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 with-footer class="card-footer">
<wa-card 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 with-image class="card-image">
<wa-card 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 with-footer size="small">
<wa-card 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 with-footer size="medium">
<wa-card 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 with-footer size="large">
<wa-card size="large">
This is a large card.
<footer slot="footer" class="wa-split">

View File

@@ -2,7 +2,8 @@
title: Code Demo
description: Code demos can be used to render code examples as inline live demos.
tags: component
noAlpha: true
isPro: true
unpublished: true
---
```html {.example}
@@ -209,4 +210,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

@@ -1,14 +1,16 @@
---
title: Image Comparer
description: Compare visual differences between similar photos with a sliding panel.
title: Comparison
description: Compare visual differences between similar content with a sliding panel.
tags: [imagery, niche]
icon: image-comparer
icon: comparison
---
For best results, use images that share the same dimensions. The slider can be controlled by dragging or pressing the left and right arrow keys. (Tip: press shift + arrows to move the slider in larger intervals, or home + end to jump to the beginning or end.)
This is especially useful for comparing images, but can be used for comparing any type of content (for an example of using it to compare entire UIs, check out our [theme pages](/docs/themes/default/)).
For best results, use content that shares the same dimensions.
The slider can be controlled by dragging or pressing the left and right arrow keys. (Tip: press shift + arrows to move the slider in larger intervals, or home + end to jump to the beginning or end.)
```html {.example}
<wa-image-comparer>
<wa-comparison>
<img
slot="before"
src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80&sat=-100&bri=-5"
@@ -19,7 +21,7 @@ For best results, use images that share the same dimensions. The slider can be c
src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80"
alt="Color version of kittens in a basket looking around."
/>
</wa-image-comparer>
</wa-comparison>
```
## Examples
@@ -29,7 +31,7 @@ For best results, use images that share the same dimensions. The slider can be c
Use the `position` attribute to set the initial position of the slider. This is a percentage from `0` to `100`.
```html {.example}
<wa-image-comparer position="25">
<wa-comparison position="25">
<img
slot="before"
src="https://images.unsplash.com/photo-1520903074185-8eca362b3dce?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1200&q=80"
@@ -40,5 +42,5 @@ Use the `position` attribute to set the initial position of the slider. This is
src="https://images.unsplash.com/photo-1520640023173-50a135e35804?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80"
alt="A person sitting on a yellow curb tying shoelaces on a boot."
/>
</wa-image-comparer>
</wa-comparison>
```

View File

@@ -10,7 +10,7 @@ keywords: modal
<!-- cspell:dictionaries lorem-ipsum -->
```html {.example}
<wa-dialog label="Dialog" with-header with-footer id="dialog-overview">
<wa-dialog label="Dialog" 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,19 +27,20 @@ keywords: modal
## Examples
### Dialog with Header
### Dialog without Header
Headers can be used to display titles and more. Use the `with-header` attribute to add a header to the dialog.
Headers are enabled by default. To render a dialog without a header, add the `without-header` attribute.
```html {.example}
<wa-dialog label="Dialog" with-header class="dialog-header">
<wa-dialog label="Dialog" without-header class="dialog-without-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-header');
const dialog = document.querySelector('.dialog-without-header');
const openButton = dialog.nextElementSibling;
openButton.addEventListener('click', () => dialog.open = true);
@@ -48,10 +49,10 @@ Headers can be used to display titles and more. Use the `with-header` attribute
### Dialog with Footer
Footers can be used to display titles and more. Use the `with-footer` attribute to add a footer to the dialog.
Footers can be used to display titles and more. Use the `footer` slot to add a footer to the dialog.
```html {.example}
<wa-dialog label="Dialog" with-footer class="dialog-footer">
<wa-dialog label="Dialog" 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>
@@ -71,7 +72,7 @@ Footers can be used to display titles and more. Use the `with-footer` attribute
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" with-header with-footer class="dialog-dismiss">
<wa-dialog label="Dialog" 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>
@@ -88,10 +89,10 @@ You can add the special `data-dialog="close"` attribute to a button inside the d
### Custom Width
Just use the CSS `width` property to set the dialog's width.
Just use the `--width` custom property to set the dialog's width.
```html {.example}
<wa-dialog label="Dialog" with-header with-footer class="dialog-width" style="width: 50vw;">
<wa-dialog label="Dialog" 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>
@@ -111,7 +112,7 @@ Just use the CSS `width` 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" with-header with-footer class="dialog-scrolling">
<wa-dialog label="Dialog" 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>
@@ -133,7 +134,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" with-header with-footer class="dialog-header-actions">
<wa-dialog label="Dialog" 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>
@@ -156,7 +157,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 with-header with-footer class="dialog-light-dismiss">
<wa-dialog label="Dialog" light-dismiss 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>
@@ -180,7 +181,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" with-header with-footer class="dialog-deny-close">
<wa-dialog label="Dialog" 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>
@@ -208,7 +209,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" with-header with-footer class="dialog-focus">
<wa-dialog label="Dialog" 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" with-header with-footer class="drawer-overview">
<wa-drawer label="Drawer" id="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,19 +25,20 @@ icon: drawer
## Examples
### Drawer with Header
### Drawer without Header
Headers can be used to display titles and more. Use the `with-header` attribute to add a header to the drawer.
Headers are enabled by default. To render a drawer without a header, add the `without-header` attribute.
```html {.example}
<wa-drawer label="Drawer" with-header class="drawer-header">
<wa-drawer label="Drawer" without-header class="drawer-without-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-header');
const drawer = document.querySelector('.drawer-without-header');
const openButton = drawer.nextElementSibling;
openButton.addEventListener('click', () => drawer.open = true);
@@ -46,10 +47,10 @@ Headers can be used to display titles and more. Use the `with-header` attribute
### Drawer with Footer
Footers can be used to display titles and more. Use the `with-footer` attribute to add a footer to the drawer.
Footers can be used to display titles and more. Use the `footer` slot to add a footer to the drawer.
```html {.example}
<wa-drawer label="Drawer" with-footer class="drawer-footer">
<wa-drawer label="Drawer" 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>
@@ -69,7 +70,7 @@ Footers can be used to display titles and more. Use the `with-footer` attribute
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" with-header with-footer class="drawer-dismiss">
<wa-drawer label="Drawer" 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>
@@ -89,7 +90,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" with-header with-footer placement="start" class="drawer-placement-start">
<wa-drawer label="Drawer" 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>
@@ -109,7 +110,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" with-header with-footer placement="top" class="drawer-placement-top">
<wa-drawer label="Drawer" 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>
@@ -129,7 +130,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" with-header with-footer placement="bottom" class="drawer-placement-bottom">
<wa-drawer label="Drawer" 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>
@@ -149,7 +150,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" with-header with-footer class="drawer-custom-size" style="--size: 50vw;">
<wa-drawer label="Drawer" 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>
@@ -169,7 +170,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" with-header with-footer class="drawer-scrolling">
<wa-drawer label="Drawer" 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>
@@ -191,7 +192,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" with-header with-footer class="drawer-header-actions">
<wa-drawer label="Drawer" 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>
@@ -214,7 +215,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 with-header with-footer class="drawer-light-dismiss">
<wa-drawer label="Drawer" light-dismiss 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>
@@ -238,7 +239,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" with-header with-footer class="drawer-deny-close">
<wa-drawer label="Drawer" 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>
@@ -261,12 +262,12 @@ You can use `event.detail.source` to determine what triggered the request to clo
</script>
```
### Customizing Initial Focus
### Setting Initial Focus
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.
To give focus to a specific element when the drawer opens, use the `autofocus` attribute.
```html {.example}
<wa-drawer label="Drawer" with-header with-footer class="drawer-focus">
<wa-drawer label="Drawer" 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,38 +180,3 @@ To create a submenu, nest an `<wa-menu slot="submenu">` element in a [menu item]
:::warning
As a UX best practice, avoid using more than one level of submenu when possible.
:::
### Hoisting
Dropdown panels will be clipped if they're inside a container that has `overflow: auto|hidden`. The `hoist` attribute forces the panel to use a fixed positioning strategy, allowing it to break out of the container. In this case, the panel will be positioned relative to its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.
```html {.example}
<div class="dropdown-hoist">
<wa-dropdown>
<wa-button slot="trigger" caret>No Hoist</wa-button>
<wa-menu>
<wa-menu-item>Item 1</wa-menu-item>
<wa-menu-item>Item 2</wa-menu-item>
<wa-menu-item>Item 3</wa-menu-item>
</wa-menu>
</wa-dropdown>
<wa-dropdown hoist>
<wa-button slot="trigger" caret>Hoist</wa-button>
<wa-menu>
<wa-menu-item>Item 1</wa-menu-item>
<wa-menu-item>Item 2</wa-menu-item>
<wa-menu-item>Item 3</wa-menu-item>
</wa-menu>
</wa-dropdown>
</div>
<style>
.dropdown-hoist {
position: relative;
border: solid 2px var(--wa-color-surface-border);
padding: var(--wa-space-m);
overflow: hidden;
}
</style>
```

View File

@@ -17,11 +17,94 @@ Not sure which icon to use? [Find the perfect icon over at Font Awesome!](https:
The default icon library is Font Awesome Free, which comes with two icon families: `classic` and `brands`. Use the `family` attribute to set the icon family.
Many Font Awesome Pro icon families have variants such as `thin`, `light`, `regular`, and `solid`. Font Awesome Pro users can [provide their kit code](/docs/installation/#using-font-awesome-kit-codes) to unlock additional families, including `sharp` and `duotone`. For these icon families, use the `variant` attribute to set the variant.
Many Font Awesome Pro icon families have variants such as `thin`, `light`, `regular`, and `solid`. Font Awesome Pro users can [provide their kit code](/docs/#using-font-awesome-kit-codes) to unlock additional families, including `sharp`, `duotone`, and `sharp-duotone`. For these icon families, use the `variant` attribute to set the variant.
```html {.example}
<wa-icon family="brands" name="font-awesome"></wa-icon>
<wa-icon family="brands" name="web-awesome"></wa-icon>
<wa-icon family="brands" name="font-awesome"></wa-icon>
<wa-icon family="brands" name="web-awesome"></wa-icon>
<wa-icon family="classic" variant="light" name="sparkles"></wa-icon>
<wa-icon family="sharp" variant="solid" name="fire"></wa-icon>
<wa-icon family="duotone" variant="regular" name="cake-slice"></wa-icon>
```
### Setting defaults via CSS
You can use certain CSS custom properties to set icon defaults, not just on the icon itself, but any ancestor.
This can be useful when you want certain parameters to vary based on context, e.g. icons inside callouts or all icons for a given theme.
:::warning
These CSS properties are intended to set **defaults**, and thus only make a difference when the corresponding attributes are not set.
In future versions of Web Awesome, we may change this behavior to allow CSS properties to override attributes if `!important` is used.
:::
For example, here is how you can use CSS custom properties to set a default icon for each type of callout:
```html {.example}
<wa-callout>
<!-- Look ma, no attributes! -->
<wa-icon slot="icon"></wa-icon>
This is a normal callout.
</wa-callout>
<wa-callout variant="danger">
<wa-icon slot="icon" name="dumpster-fire" variant="solid"></wa-icon>
This is a callout with an explicit icon, which overrides these defaults.
</wa-callout>
<wa-callout variant="warning">
<!-- Look ma, no attributes! -->
<wa-icon slot="icon"></wa-icon>
Here be dragons.
</wa-callout>
<wa-callout variant="danger">
<!-- Look ma, no attributes! -->
<wa-icon slot="icon"></wa-icon>
Here be more dragons.
</wa-callout>
<wa-callout variant="success">
<!-- Look ma, no attributes! -->
<wa-icon slot="icon"></wa-icon>
Success!
</wa-callout>
<style>
wa-callout {
--wa-icon-variant: regular;
--wa-icon-name: info-circle;
&[variant="warning"] {
--wa-icon-name: triangle-exclamation;
}
&[variant="danger"] {
--wa-icon-name: circle-exclamation;
}
&[variant="success"] {
--wa-icon-name: circle-check;
}
}
</style>
```
You can even set icons dynamically, as a response to user interaction or media queries.
For example, here's how we can change the icon on hover:
```html {.example}
<wa-button class="github" href="https://github.com/webawesome/webawesome"><wa-icon slot="prefix" fixed-width></wa-icon> GitHub Repo</wa-button>
<style>
.github {
--wa-icon-name: github;
--wa-icon-family: brands;
&:hover {
--wa-icon-name: arrow-up-right-from-square;
--wa-icon-family: classic;
}
}
</style>
```
### Colors
@@ -561,4 +644,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

@@ -176,7 +176,7 @@ eleventyExcludeFromCollections: true
</footer>
<aside slot="aside">
<h2 class="wa-heading-m">Discover More Birds</h2>
<wa-card with-image>
<wa-card>
<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 with-image>
<wa-card>
<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 with-image>
<wa-card>
<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 with-image>
<wa-card>
<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 with-image>
<wa-card>
<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 with-image>
<wa-card>
<div slot="image" class="wa-frame">
<img src="https://images.unsplash.com/photo-1660307777355-f08bced145d3?q=20" alt="" />
</div>

View File

@@ -468,75 +468,20 @@ Use the `sync` attribute to make the popup the same width or height as the ancho
</script>
```
### Positioning Strategy
By default, the popup is positioned using an absolute positioning strategy. However, if your anchor is fixed or exists within a container that has `overflow: auto|hidden`, the popup risks being clipped. To work around this, you can use a fixed positioning strategy by setting the `strategy` attribute to `fixed`.
The fixed positioning strategy reduces jumpiness when the anchor is fixed and allows the popup to break out containers that clip. When using this strategy, it's important to note that the content will be positioned relative to its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.
In this example, you can see how the popup breaks out of the overflow container when it's fixed. The fixed positioning strategy tends to be less performant than absolute, so avoid using it unnecessarily.
Toggle the switch and scroll the container to see the difference.
```html {.example}
<div class="popup-strategy">
<div class="overflow">
<wa-popup placement="top" strategy="fixed" active>
<span slot="anchor"></span>
<div class="box"></div>
</wa-popup>
</div>
<wa-switch checked>Fixed</wa-switch>
</div>
<style>
.popup-strategy .overflow {
position: relative;
height: 300px;
border: solid 2px var(--wa-color-surface-border);
overflow: auto;
}
.popup-strategy span[slot='anchor'] {
display: inline-block;
width: 150px;
height: 150px;
border: dashed 2px var(--wa-color-neutral-fill-loud);
margin: 150px 50px;
}
.popup-strategy .box {
width: 100px;
height: 50px;
background: var(--wa-color-brand-fill-loud);
border-radius: var(--wa-border-radius-m);
}
.popup-strategy wa-switch {
margin-top: 1rem;
}
</style>
<script>
const container = document.querySelector('.popup-strategy');
const popup = container.querySelector('wa-popup');
const fixed = container.querySelector('wa-switch');
fixed.addEventListener('change', () => (popup.strategy = fixed.checked ? 'fixed' : 'absolute'));
</script>
```
### Flip
When the popup doesn't have enough room in its preferred placement, it can automatically flip to keep it in view. To enable this, use the `flip` attribute. By default, the popup will flip to the opposite placement, but you can configure preferred fallback placements using `flip-fallback-placement` and `flip-fallback-strategy`. Additional options are available to control the flip behavior's boundary and padding.
When the popup doesn't have enough room in its preferred placement, it can automatically flip to keep it in view and visually connected to its anchor.
To enable this, use the `flip` attribute. By default, the popup will flip to the opposite placement, but you can configure preferred fallback placements using `flip-fallback-placement` and `flip-fallback-strategy`. Additional options are available to control the flip behavior's boundary and padding.
By default, flip takes effect when the popup would overflow the viewport.
You can use `boundary="scroll"` to make the popup resize when it overflows its nearest scrollable container instead.
Scroll the container to see how the popup flips to prevent clipping.
```html {.example}
<div class="popup-flip">
<div class="overflow">
<wa-popup placement="top" flip active>
<wa-popup placement="top" flip active boundary="scroll">
<span slot="anchor"></span>
<div class="box"></div>
</wa-popup>
@@ -592,7 +537,7 @@ Scroll the container to see how the popup changes it's fallback placement to pre
```html {.example}
<div class="popup-flip-fallbacks">
<div class="overflow">
<wa-popup placement="top" flip flip-fallback-placements="right bottom" flip-fallback-strategy="initial" active>
<wa-popup placement="top" flip flip-fallback-placements="right bottom" flip-fallback-strategy="initial" active boundary="scroll">
<span slot="anchor"></span>
<div class="box"></div>
</wa-popup>
@@ -626,14 +571,18 @@ Scroll the container to see how the popup changes it's fallback placement to pre
### Shift
When a popup is longer than its anchor, it risks being clipped by an overflowing container. In this case, use the `shift` attribute to shift the popup along its axis and back into view. You can customize the shift behavior using `shiftBoundary` and `shift-padding`.
When a popup is longer than its anchor, it risks overflowing.
In this case, use the `shift` attribute to shift the popup along its axis and back into view. You can customize the shift behavior using `shiftBoundary` and `shift-padding`.
By default, auto-size takes effect when the popup would overflow the viewport.
You can use `boundary="scroll"` to make the popup resize when it overflows its nearest scrollable container instead.
Toggle the switch to see the difference.
```html {.example}
<div class="popup-shift">
<div class="overflow">
<wa-popup placement="top" shift shift-padding="10" active>
<wa-popup placement="top" shift shift-padding="10" active boundary="scroll">
<span slot="anchor"></span>
<div class="box"></div>
</wa-popup>
@@ -676,7 +625,11 @@ Toggle the switch to see the difference.
### Auto-size
Use the `auto-size` attribute to tell the popup to resize when necessary to prevent it from getting clipped. Possible values are `horizontal`, `vertical`, and `both`. You can use `autoSizeBoundary` and `auto-size-padding` to customize the behavior of this option. Auto-size works well with `flip`, but if you're using `auto-size-padding` make sure `flip-padding` is the same value.
Use the `auto-size` attribute to tell the popup to resize when necessary to prevent it from overflowing.
Possible values are `horizontal`, `vertical`, and `both`. You can use `autoSizeBoundary` and `auto-size-padding` to customize the behavior of this option. Auto-size works well with `flip`, but if you're using `auto-size-padding` make sure `flip-padding` is the same value.
By default, auto-size takes effect when the popup would overflow the viewport.
You can use `boundary="scroll"` to make the popup resize when it overflows its nearest scrollable container instead.
When using `auto-size`, one or both of `--auto-size-available-width` and `--auto-size-available-height` will be applied to the host element. These values determine the available space the popover has before clipping will occur. Since they cascade, you can use them to set a max-width/height on your popup's content and easily control its overflow.
@@ -685,7 +638,7 @@ Scroll the container to see the popup resize as its available space changes.
```html {.example}
<div class="popup-auto-size">
<div class="overflow">
<wa-popup placement="top" auto-size="both" auto-size-padding="10" active>
<wa-popup placement="top" auto-size="both" auto-size-padding="10" active boundary="scroll">
<span slot="anchor"></span>
<div class="box"></div>
</wa-popup>

View File

@@ -0,0 +1,151 @@
---
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,11 +425,3 @@ This can be hard to conceptualize, so heres a fairly large example showing how l
</script>
```
<script type="module">
//
// TODO - remove once we switch to the Popover API
//
customElements.whenDefined('wa-select').then(() => {
document.querySelectorAll('wa-code-demo [slot="preview"] wa-select').forEach(select => select.hoist = true);
});
</script>

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

View File

@@ -152,25 +152,3 @@ Use the `--max-width` custom property to change the width the tooltip can grow t
<wa-button id="wrapping-tooltip">Hover me</wa-button>
```
### Hoisting
Tooltips will be clipped if they're inside a container that has `overflow: auto|hidden|scroll`. The `hoist` attribute forces the tooltip to use a fixed positioning strategy, allowing it to break out of the container. In this case, the tooltip will be positioned relative to its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.
```html {.example}
<div class="tooltip-hoist">
<wa-tooltip for="no-hoist">This is a tooltip</wa-tooltip>
<wa-button id="no-hoist">No Hoist</wa-button>
<wa-tooltip for="hoist" hoist>This is a tooltip</wa-tooltip>
<wa-button id="hoist">Hoist</wa-button>
</div>
<style>
.tooltip-hoist {
position: relative;
overflow: hidden;
border: solid 2px var(--wa-color-surface-border);
padding: var(--wa-space-m);
}
</style>
```

View File

@@ -2,7 +2,8 @@
title: Viewport Demo
description: Viewport demos can be used to display an iframe as a resizable, zoomable preview.
tags: component
noAlpha: true
isPro: true
unpublished: true
---
```html {.example}
@@ -67,4 +68,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/installation/#quick-start-autoloading-via-cdn) with this snippet to use the *Awesome* theme:
To use a theme, simply add a link to the theme's stylesheet to the `<head>` of your page. For example, you can replace the link to `default.css` in the [installation code](/docs/#quick-start-autoloading-via-cdn) with this snippet to use the *Awesome* theme:
```html
<link rel="stylesheet" href="{% cdnUrl 'styles/themes/awesome.css' %}" />
@@ -197,7 +197,7 @@ For example, we can give all outlined callouts a thick left border, regardless o
<style>
wa-callout:is(
[appearance~="outlined"],
[appearance~="outlined"],
.wa-outlined
) {
border-left-width: var(--wa-panel-border-radius);

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 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="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 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" with-header>
<wa-dialog id="icon-chooser" label="Browse Icons">
<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 with-header with-footer class="card-header card-footer">
<wa-card 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/installation/#quick-start-autoloading-via-cdn). You can also import them individually via [cherry picking](/docs/installation/#cherry-picking).
Layout components are included in Web Awesome's [autoloader](/docs/#quick-start-autoloading-via-cdn). You can also import them individually via [cherry picking](/docs/#cherry-picking).
Layout utilities are bundled with all [style utilities](/docs/utilities). You can import all Web Awesome page styles (including [native styles](/docs/native/)) by including the following stylesheet in your project:

View File

@@ -4,7 +4,6 @@ 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="radio" value="1"> Option 1</label>
<label><input type="radio" name="radio" value="2"> Option 2</label>
<label><input type="radio" name="a" value="1" checked> Option 1</label>
<label><input type="radio" name="a" value="2"> Option 2</label>
</div>
```
@@ -24,9 +24,9 @@ To set the initial value and checked state, use the `checked` attribute on the c
```html {.example}
<div class="wa-cluster">
<label><input type="radio" name="radio" value="1"> Option 1</label>
<label><input type="radio" name="radio" value="2"> Option 2</label>
<label><input type="radio" name="radio" value="3" checked> Option 3</label>
<label><input type="radio" name="b" value="1" checked> Option 1</label>
<label><input type="radio" name="b" value="2"> Option 2</label>
<label><input type="radio" name="b" value="3"> Option 3</label>
</div>
```
@@ -36,9 +36,9 @@ Use the `disabled` attribute to disable a radio.
```html {.example}
<div class="wa-cluster">
<label><input type="radio" name="radio" value="1"> Option 1</label>
<label><input type="radio" name="radio" value="2" disabled> Option 2</label>
<label><input type="radio" name="radio" value="3"> Option 3</label>
<label><input type="radio" name="c" value="1" checked> Option 1</label>
<label><input type="radio" name="c" value="2" disabled> Option 2</label>
<label><input type="radio" name="c" value="3"> Option 3</label>
</div>
```
@@ -49,26 +49,26 @@ Use the [size utilities](/docs/utilities/size) to change the radios' size.
```html {.example}
<fieldset class="wa-size-s wa-cluster">
<legend>Small</legend>
<label><input type="radio" name="radio" value="1"> Option 1</label>
<label><input type="radio" name="radio" value="2"> Option 2</label>
<label><input type="radio" name="radio" value="3"> Option 3</label>
<label><input type="radio" name="d" value="1" checked> Option 1</label>
<label><input type="radio" name="d" value="2"> Option 2</label>
<label><input type="radio" name="d" value="3"> Option 3</label>
</fieldset>
<br />
<fieldset class="wa-size-m wa-cluster">
<legend>Medium</legend>
<label><input type="radio" name="radio" value="1"> Option 1</label>
<label><input type="radio" name="radio" value="2"> Option 2</label>
<label><input type="radio" name="radio" value="3"> Option 3</label>
<label><input type="radio" name="e" value="1" checked> Option 1</label>
<label><input type="radio" name="e" value="2"> Option 2</label>
<label><input type="radio" name="e" value="3"> Option 3</label>
</fieldset>
<br />
<fieldset class="wa-size-l wa-cluster">
<legend>Large</legend>
<label><input type="radio" name="radio" value="1"> Option 1</label>
<label><input type="radio" name="radio" value="2"> Option 2</label>
<label><input type="radio" name="radio" value="3"> Option 3</label>
<label><input type="radio" name="f" value="1" checked> Option 1</label>
<label><input type="radio" name="f" value="2"> Option 2</label>
<label><input type="radio" name="f" value="3"> Option 3</label>
</fieldset>
```
@@ -78,16 +78,16 @@ You can wrap native radios in a flex container to give them a horizontal or vert
```html {.example}
<div class="wa-cluster">
<label><input type="radio" name="radio" value="1"> Option 1</label>
<label><input type="radio" name="radio" value="2"> Option 2</label>
<label><input type="radio" name="radio" value="3" checked> Option 3</label>
<label><input type="radio" name="g" value="1" checked> Option 1</label>
<label><input type="radio" name="g" value="2"> Option 2</label>
<label><input type="radio" name="g" value="3"> Option 3</label>
</div>
```
```html {.example}
<div class="wa-stack">
<label><input type="radio" name="radio" value="1"> Option 1</label>
<label><input type="radio" name="radio" value="2"> Option 2</label>
<label><input type="radio" name="radio" value="3" checked> Option 3</label>
<label><input type="radio" name="h" value="1" checked> Option 1</label>
<label><input type="radio" name="h" value="2"> Option 2</label>
<label><input type="radio" name="h" value="3"> Option 3</label>
</div>
```

View File

@@ -1,27 +0,0 @@
---
layout: null
permalink: "/docs/palettes/data.json"
eleventyExcludeFromCollections: true
---
{
{% for palette in collections.palettes %}
{% set paletteId = palette.fileSlug -%}
{% set colors = palettes[paletteId] -%}
"{{ paletteId }}": {
"title": "{{ palette.data.title }}",
"colors": {
{% for hue, tints in colors -%}
"{{ hue }}": {
{% for tint, value in tints -%}
{% if tint != "05" -%}
{% set value = value.coords or value -%}
{% set key = "05" if tint == "5" else tint -%}
"{{ key }}": {{ value | dump | safe }}{{ ', ' if not loop.last }}
{%- endif %}
{% endfor -%}
}{{ ', ' if not loop.last }}
{% endfor %} {# end of hue #}
}
}{{ ', ' if not loop.last }} {# end of palette #}
{% endfor %}
}

View File

@@ -1,6 +1,4 @@
:root {
--fa-sliders-simple: '\f1de';
}
@import url('/assets/styles/ui.css');
.core-column {
position: relative;
@@ -86,17 +84,6 @@ 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;
@@ -182,24 +169,3 @@ 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);
}
}

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