mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-19 15:34:15 +00:00
Compare commits
143 Commits
applied-st
...
layouts-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1329d4475a | ||
|
|
f5448f3f09 | ||
|
|
b33efe697b | ||
|
|
306911d663 | ||
|
|
c0e49cc4ab | ||
|
|
64ac34447c | ||
|
|
667e78243c | ||
|
|
93883537a0 | ||
|
|
900d4b74e3 | ||
|
|
c371dae683 | ||
|
|
ecf2da5201 | ||
|
|
932e2e7566 | ||
|
|
e76a1dc1f6 | ||
|
|
5383411572 | ||
|
|
af3958cc63 | ||
|
|
30561d565b | ||
|
|
38302a7c28 | ||
|
|
71e5b10f3b | ||
|
|
4340f89830 | ||
|
|
9a46c29072 | ||
|
|
55bc5f0432 | ||
|
|
dc7c68d6c5 | ||
|
|
213bde0f7f | ||
|
|
c195b9f444 | ||
|
|
58a9f04623 | ||
|
|
b134911703 | ||
|
|
3eda5510c3 | ||
|
|
32494e783c | ||
|
|
0fa4810ddb | ||
|
|
cdf38fe147 | ||
|
|
302c174055 | ||
|
|
fb044aae89 | ||
|
|
1f09a53fab | ||
|
|
2d95eedbac | ||
|
|
26d83f37d6 | ||
|
|
a751053803 | ||
|
|
44a7dd5089 | ||
|
|
902ab85f32 | ||
|
|
21647001a4 | ||
|
|
a0ee2256c4 | ||
|
|
51f5c30526 | ||
|
|
bf299d8234 | ||
|
|
9e0aa9c2ec | ||
|
|
15a9c63040 | ||
|
|
30a3164a96 | ||
|
|
2a22fb683c | ||
|
|
325ddafb13 | ||
|
|
a1a09a2b2b | ||
|
|
1df6afa541 | ||
|
|
fcb2c7868c | ||
|
|
46b198866d | ||
|
|
d4e2abe218 | ||
|
|
ac94d838f0 | ||
|
|
9a4da9b763 | ||
|
|
7869144f5e | ||
|
|
2d7d400040 | ||
|
|
6f5e5a2433 | ||
|
|
e59a4659d8 | ||
|
|
23a0f4afdc | ||
|
|
2f9732fc3d | ||
|
|
fdede79155 | ||
|
|
00f23c485d | ||
|
|
3277284473 | ||
|
|
b2b8d0d941 | ||
|
|
91bfd38a9a | ||
|
|
7a31446162 | ||
|
|
11893fe80c | ||
|
|
f4971456d0 | ||
|
|
e23adf4d11 | ||
|
|
cc18a90a86 | ||
|
|
60b6803437 | ||
|
|
2b57157502 | ||
|
|
739a6033af | ||
|
|
ed43baa459 | ||
|
|
9cbc27a6ef | ||
|
|
b017c4df1e | ||
|
|
27aaa82a9c | ||
|
|
57194ed12d | ||
|
|
74d5b4c3f4 | ||
|
|
967208d69b | ||
|
|
3bd13cd7cb | ||
|
|
e95bfab77b | ||
|
|
78785b872d | ||
|
|
ebe1904479 | ||
|
|
6f0c41cddf | ||
|
|
52bda73657 | ||
|
|
23356f6e39 | ||
|
|
4958ee41ae | ||
|
|
9784faa32a | ||
|
|
a913c22200 | ||
|
|
95dce95183 | ||
|
|
53f9230354 | ||
|
|
946f08db4b | ||
|
|
4b0ee8907f | ||
|
|
62bb58dc09 | ||
|
|
1d903fab38 | ||
|
|
a458f2a6f0 | ||
|
|
f66e8cec69 | ||
|
|
9fd070639c | ||
|
|
528748155a | ||
|
|
474ffb98d6 | ||
|
|
cb2d5e4eb4 | ||
|
|
3c51262a37 | ||
|
|
7e4dba7af1 | ||
|
|
319705106b | ||
|
|
2416f93a79 | ||
|
|
aaf845c72a | ||
|
|
fc66179dc0 | ||
|
|
67702b8d89 | ||
|
|
e398091a36 | ||
|
|
d836bcebbc | ||
|
|
81ff1422e8 | ||
|
|
d08f928818 | ||
|
|
27984299e0 | ||
|
|
0cac319988 | ||
|
|
c3e74ada39 | ||
|
|
0509fb041f | ||
|
|
a2e9a3de96 | ||
|
|
32bc7f2207 | ||
|
|
b2a99c83e3 | ||
|
|
a4185bc926 | ||
|
|
7c73b6a458 | ||
|
|
6b579a6946 | ||
|
|
896fe76a8d | ||
|
|
ed1621410b | ||
|
|
29e591e69b | ||
|
|
396e632679 | ||
|
|
316f8eb16d | ||
|
|
131b1ee57e | ||
|
|
75004768bb | ||
|
|
c8067674f6 | ||
|
|
caf4dc5526 | ||
|
|
93841348e1 | ||
|
|
8ad392a5ac | ||
|
|
6fc8a5166e | ||
|
|
0e27e1dd3d | ||
|
|
1b33f38280 | ||
|
|
340869fa91 | ||
|
|
06ff11114a | ||
|
|
ebe30a5ce8 | ||
|
|
4c84dec601 | ||
|
|
ece156de0b | ||
|
|
191f7d708c |
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,4 +1,7 @@
|
||||
contact_links:
|
||||
- name: Feature Requests
|
||||
url: https://github.com/shoelace-style/shoelace/discussions/categories/ideas
|
||||
about: All requests for new features should go here.
|
||||
- name: Help & Support
|
||||
url: https://github.com/shoelace-style/shoelace/discussions/categories/help
|
||||
about: Please don't create issues for personal help requests. Instead, ask your question on the discussion forum.
|
||||
|
||||
15
.github/ISSUE_TEMPLATE/feature_request.md
vendored
15
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,15 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for this project.
|
||||
title: ''
|
||||
labels: feature
|
||||
---
|
||||
|
||||
### What issue are you having?
|
||||
Provide a clear and concise description of the problem you're facing.
|
||||
|
||||
### Describe the solution you'd like
|
||||
How would you like to see the library solve it?
|
||||
|
||||
### Describe alternatives you've considered
|
||||
In what ways have you tried to solve this with the current version?
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,8 @@
|
||||
_site
|
||||
.cache
|
||||
.DS_Store
|
||||
package.json
|
||||
package-lock.json
|
||||
dist
|
||||
docs/assets/images/sprite.svg
|
||||
node_modules
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
"Numberish",
|
||||
"oklab",
|
||||
"oklch",
|
||||
"onscrollend",
|
||||
"outdir",
|
||||
"ParamagicDev",
|
||||
"peta",
|
||||
@@ -170,10 +171,11 @@
|
||||
"valpha",
|
||||
"valuenow",
|
||||
"valuetext",
|
||||
"viewports",
|
||||
"WCAG",
|
||||
"webawesome",
|
||||
"WEBP",
|
||||
"Webpacker",
|
||||
"wordmark"
|
||||
"Webpacker"
|
||||
],
|
||||
"ignorePaths": [
|
||||
"package.json",
|
||||
|
||||
@@ -205,6 +205,7 @@ export default {
|
||||
customElementJetBrainsPlugin({
|
||||
outdir: './dist',
|
||||
excludeCss: true,
|
||||
packageJson: false,
|
||||
referencesTemplate: (_, tag) => {
|
||||
return {
|
||||
name: 'Documentation',
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<link rel="stylesheet" href="{{ assetUrl('styles/search.css') }}" />
|
||||
|
||||
{# Favicons #}
|
||||
<link rel="icon" href="{{ assetUrl('images/logo.svg') }}" type="image/x-icon" />
|
||||
<link rel="icon" href="{{ assetUrl('images/favicon.svg') }}" type="image/x-icon" />
|
||||
|
||||
{# Twitter Cards #}
|
||||
<meta name="twitter:card" content="summary" />
|
||||
@@ -35,7 +35,8 @@
|
||||
|
||||
{# Web Awesome #}
|
||||
<link rel="stylesheet" href="/dist/themes/applied.css" />
|
||||
<link rel="stylesheet" href="/dist/themes/default.css" />
|
||||
<link rel="stylesheet" href="/dist/themes/forms.css" />
|
||||
<link id="theme-stylesheet" rel="stylesheet" href="/dist/themes/default.css" />
|
||||
<script type="module" src="/dist/autoloader.js"></script>
|
||||
|
||||
{# Set the initial theme and menu states here to prevent flashing #}
|
||||
@@ -60,7 +61,7 @@
|
||||
<script src="{{ assetUrl('scripts/search.js') }}" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<a id="skip-to-main" class="wa-visually-hidden" href="#main-content" data-smooth-link="false">
|
||||
<a id="skip-to-content" class="wa-visually-hidden" href="#main-content" data-smooth-link="false">
|
||||
Skip to main content
|
||||
</a>
|
||||
|
||||
@@ -76,7 +77,7 @@
|
||||
<aside id="sidebar" data-preserve-scroll>
|
||||
<header>
|
||||
<a href="/">
|
||||
{% include 'workmark.njk' %}
|
||||
{% include 'logo.njk' %}
|
||||
</a>
|
||||
<div class="sidebar-version">
|
||||
{{ meta.version }}
|
||||
@@ -84,13 +85,13 @@
|
||||
</header>
|
||||
|
||||
<div class="sidebar-buttons">
|
||||
<wa-button outline size="small" class="repo-button repo-button--github" href="https://github.com/shoelace-style/shoelace" target="_blank">
|
||||
<wa-button size="small" class="repo-button repo-button--github" href="https://github.com/shoelace-style/shoelace" target="_blank">
|
||||
<wa-icon slot="prefix" name="github"></wa-icon> Code
|
||||
</wa-button>
|
||||
<wa-button outline size="small" class="repo-button repo-button--star" href="https://github.com/shoelace-style/shoelace/stargazers" target="_blank">
|
||||
<wa-button size="small" class="repo-button repo-button--star" href="https://github.com/shoelace-style/shoelace/stargazers" target="_blank">
|
||||
<wa-icon slot="prefix" name="star-fill"></wa-icon> Star
|
||||
</wa-button>
|
||||
<wa-button outline size="small" class="repo-button repo-button--twitter" href="https://twitter.com/shoelace_style" target="_blank">
|
||||
<wa-button size="small" class="repo-button repo-button--twitter" href="https://twitter.com/shoelace_style" target="_blank">
|
||||
<wa-icon slot="prefix" name="twitter"></wa-icon> Follow
|
||||
</wa-button>
|
||||
</div>
|
||||
|
||||
75
docs/_includes/layout-example.njk
Normal file
75
docs/_includes/layout-example.njk
Normal file
@@ -0,0 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html
|
||||
lang="en"
|
||||
data-layout="{{ layout }}"
|
||||
data-shoelace-version="{{ meta.version }}"
|
||||
>
|
||||
<head>
|
||||
{# Metadata #}
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="{{ meta.description }}" />
|
||||
<title>{{ meta.title }}</title>
|
||||
|
||||
{# Opt out of Turbo caching #}
|
||||
<meta name="turbo-cache-control" content="no-cache">
|
||||
<meta name="turbo-cache-control" content="no-preview">
|
||||
|
||||
{# Favicons #}
|
||||
<link rel="icon" href="{{ assetUrl('images/favicon.svg') }}" type="image/x-icon" />
|
||||
|
||||
{# Twitter Cards #}
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:creator" content="shoelace_style" />
|
||||
<meta name="twitter:image" content="{{ assetUrl(meta.image, true) }}" />
|
||||
|
||||
{# OpenGraph #}
|
||||
<meta property="og:url" content="{{ rootUrl(page.url, true) }}" />
|
||||
<meta property="og:title" content="{{ meta.title }}" />
|
||||
<meta property="og:description" content="{{ meta.description }}" />
|
||||
<meta property="og:image" content="{{ assetUrl(meta.image, true) }}" />
|
||||
|
||||
{# WebAwesome #}
|
||||
<link rel="stylesheet" href="/dist/themes/default.css" />
|
||||
<link rel="stylesheet" href="/dist/themes/applied.css" />
|
||||
<script type="module" src="/dist/webawesome.js"></script>
|
||||
|
||||
{# Set the initial theme and menu states here to prevent flashing #}
|
||||
<script>
|
||||
(() => {
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const theme = localStorage.getItem('theme') || 'auto';
|
||||
document.documentElement.classList.toggle('wa-theme-dark', theme === 'dark' || (theme === 'auto' && prefersDark));
|
||||
|
||||
if (window.Turbo) {
|
||||
window.Turbo.session.drive = false
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
*, *:before, *:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}
|
||||
{{ content | safe }}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
73
docs/_includes/layout-templates/advanced-example.css
Normal file
73
docs/_includes/layout-templates/advanced-example.css
Normal file
@@ -0,0 +1,73 @@
|
||||
html {
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.grid {
|
||||
font-size: 1.35rem;
|
||||
text-align: center;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: var(--wa-color-blue-90);
|
||||
}
|
||||
|
||||
aside {
|
||||
min-width: 250px;
|
||||
max-width: 250px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
main {
|
||||
background-color: var(--wa-color-green-90);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--wa-color-blue-80);
|
||||
}
|
||||
|
||||
.banner {
|
||||
background-color: var(--wa-color-yellow-90);
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: var(--wa-color-blue-90);
|
||||
}
|
||||
|
||||
.banner,
|
||||
.header {
|
||||
min-width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
[slot='header'] {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
[slot='aside'] {
|
||||
background-color: var(--wa-color-yellow-90);
|
||||
}
|
||||
|
||||
[slot='menu'] {
|
||||
background-color: var(--wa-color-red-80);
|
||||
}
|
||||
|
||||
[slot='main-header'] {
|
||||
background-color: var(--wa-color-red-90);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
[slot='main-footer'] {
|
||||
background-color: var(--wa-color-red-70);
|
||||
}
|
||||
18
docs/_includes/layout-templates/advanced-example.html
Normal file
18
docs/_includes/layout-templates/advanced-example.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<wa-layout main-id="main-content" class="wa-theme-light">
|
||||
<header slot="banner" class="grid banner">banner</header>
|
||||
|
||||
<header slot="header" class="grid header">header</header>
|
||||
|
||||
<aside class="grid" slot="menu">menu</aside>
|
||||
|
||||
<header class="grid" slot="main-header">main-header</header>
|
||||
|
||||
<main class="grid" id="main-content">main</main>
|
||||
|
||||
<footer class="grid" slot="main-footer">main-footer</footer>
|
||||
|
||||
<aside class="grid" slot="aside">aside</aside>
|
||||
<footer class="grid" slot="footer">footer</footer>
|
||||
</wa-layout>
|
||||
|
||||
{% include "layout-widget.njk" %}
|
||||
216
docs/_includes/layout-templates/app.css
Normal file
216
docs/_includes/layout-templates/app.css
Normal file
@@ -0,0 +1,216 @@
|
||||
html {
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
main {
|
||||
min-height: 100%;
|
||||
padding: 1rem 2rem;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
wa-layout {
|
||||
background-color: var(--wa-color-neutral-95);
|
||||
color: var(--wa-color-neutral-20);
|
||||
}
|
||||
|
||||
wa-card :is(p, h3) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
wa-layout::part(header) {
|
||||
/** Because headers are sticky, this keeps text from leaking through. */
|
||||
background-color: var(--wa-color-white);
|
||||
}
|
||||
|
||||
wa-layout::part(drawer__panel) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
wa-layout[view='mobile'] {
|
||||
background-color: var(--wa-color-white);
|
||||
--menu-width: 0px;
|
||||
}
|
||||
|
||||
wa-layout[view='mobile']::part(header) {
|
||||
padding: 0.25rem;
|
||||
border-bottom: 1px solid var(--wa-color-neutral-70);
|
||||
}
|
||||
|
||||
wa-layout[view='mobile']::part(navigation) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
wa-layout[view='desktop']::part(main) {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
wa-layout[view='desktop'] {
|
||||
--menu-width: 250px;
|
||||
}
|
||||
|
||||
wa-layout[view='desktop']::part(navigation) {
|
||||
padding-top: 1.9rem;
|
||||
}
|
||||
|
||||
wa-layout[view='desktop'] > [slot='header'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
wa-layout[view='desktop']::part(header) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
wa-layout[view='desktop'] main {
|
||||
background-color: var(--wa-color-white);
|
||||
box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid var(--wa-color-neutral-80);
|
||||
border-top-left-radius: 8px;
|
||||
}
|
||||
|
||||
/* Navigation / Lists */
|
||||
|
||||
/* Highlights */
|
||||
.highlight {
|
||||
font-size: 0.85em;
|
||||
padding: 0.4em 0.6em;
|
||||
border-radius: 6px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.highlight--success {
|
||||
background-color: var(--wa-color-success-fill-muted);
|
||||
color: var(--wa-color-success-text-on-muted);
|
||||
}
|
||||
|
||||
.highlight--danger {
|
||||
background-color: var(--wa-color-red-90);
|
||||
color: var(--wa-color-red-30);
|
||||
}
|
||||
|
||||
/* Text */
|
||||
.text--light {
|
||||
color: var(--wa-color-neutral-40);
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.wa-card--muted::part(base) {
|
||||
--border-color: transparent;
|
||||
background-color: transparent;
|
||||
display: grid;
|
||||
height: 100%;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.wa-card--muted::part(body) {
|
||||
display: grid;
|
||||
align-content: flex-end;
|
||||
gap: var(--padding);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.wa-button--card {
|
||||
--border-radius: 8px;
|
||||
--padding: 1rem 0px;
|
||||
}
|
||||
|
||||
.wa-button--card.wa-button--muted {
|
||||
--background-color: var(--wa-color-neutral-95);
|
||||
}
|
||||
|
||||
.wa-button--card::part(base) {
|
||||
height: 100%;
|
||||
border-radius: var(--border-radius);
|
||||
padding: var(--padding);
|
||||
}
|
||||
|
||||
.wa-button--card::part(label) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wa-button--muted {
|
||||
--text-color: var(--wa-color-neutral-30);
|
||||
--text-color-active: var(--wa-color-neutral-30);
|
||||
--background-color: transparent;
|
||||
--background-color-active: var(--wa-color-neutral-90);
|
||||
--border-color: transparent;
|
||||
--border-color-active: var(--wa-color-neutral-80);
|
||||
}
|
||||
|
||||
.wa-button--muted::part(base) {
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.wa-button--muted:is(:focus-within)::part(base) {
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color-active);
|
||||
border-color: var(--border-color-active);
|
||||
}
|
||||
|
||||
.wa-button--muted:is(:hover)::part(base) {
|
||||
background-color: var(--background-color-active);
|
||||
color: var(--text-color-active);
|
||||
border-color: var(--border-color-active);
|
||||
}
|
||||
|
||||
.wa-button--logo::part(base) {
|
||||
font-size: 1.5rem;
|
||||
color: var(--wa-color-neutral-30);
|
||||
}
|
||||
|
||||
.wa-button--square::part(base) {
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.wa-button--stretch {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wa-button--stretch::part(label) {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.wa-button--nav-footer {
|
||||
--border-color: var(--wa-color-neutral-70);
|
||||
--wa-spacing-large: 8px;
|
||||
}
|
||||
|
||||
wa-layout[view='desktop'] .wa-button--nav-footer::part(base) {
|
||||
--border-color: transparent;
|
||||
border-top-color: var(--wa-color-neutral-70);
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
table {
|
||||
max-width: 100%;
|
||||
border: none;
|
||||
border-collapse: collapse;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
table tr {
|
||||
border-bottom: 1px solid var(--wa-color-neutral-70);
|
||||
}
|
||||
|
||||
table th {
|
||||
font-weight: var(--wa-font-weight-semibold);
|
||||
text-align: left;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
table td {
|
||||
line-height: var(--wa-line-height-normal);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
* > table {
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
428
docs/_includes/layout-templates/app.html
Normal file
428
docs/_includes/layout-templates/app.html
Normal file
@@ -0,0 +1,428 @@
|
||||
<wa-layout main-id="main-content" class="wa-theme-light">
|
||||
<header slot="header">
|
||||
<wa-icon-button name="list" style="font-size: 1.5rem" data-toggle-nav></wa-icon-button>
|
||||
</header>
|
||||
<wa-button
|
||||
href="#"
|
||||
variant="text"
|
||||
style="padding: 0 0.4rem"
|
||||
class="wa-button--logo wa-button--stretch wa-button--muted"
|
||||
size="large"
|
||||
slot="navigation-header"
|
||||
>
|
||||
<wa-icon name="music-note" slot="prefix" style="font-size: 2rem"></wa-icon>
|
||||
Musicify
|
||||
</wa-button>
|
||||
|
||||
<nav style="padding: 1rem" slot="navigation">
|
||||
<wa-nav-group style="height: 100%">
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="search" slot="prefix"></wa-icon>
|
||||
Search
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="bell" slot="prefix"></wa-icon>
|
||||
Notifications
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<wa-nav-item href="#" current="page">
|
||||
<wa-icon name="house-door" slot="prefix"></wa-icon>
|
||||
Home
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="music-note-list" slot="prefix"></wa-icon>
|
||||
Playlists
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="file-earmark-music" slot="prefix"></wa-icon>
|
||||
Tracks
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="gear" slot="prefix"></wa-icon>
|
||||
Settings
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#" style="margin-top: auto">
|
||||
<wa-icon name="question-circle" slot="prefix"></wa-icon>
|
||||
Help
|
||||
</wa-nav-item>
|
||||
</wa-nav-group>
|
||||
</nav>
|
||||
|
||||
<!-- Hacky override to make padding 8px -->
|
||||
<wa-button
|
||||
slot="navigation-footer"
|
||||
outline
|
||||
class="wa-button--square wa-button--stretch wa-button--muted wa-button--nav-footer"
|
||||
size="large"
|
||||
href="#"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
display: grid;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
gap: 8px;
|
||||
grid-template-columns: minmax(0, auto) minmax(0, 1fr) minmax(0, auto);
|
||||
"
|
||||
>
|
||||
<wa-avatar shape="rounded" style="--size: 36px"></wa-avatar>
|
||||
<div style="text-overflow: ellipsis; max-width: 100%; overflow: hidden; text-align: start; font-size: 1rem">
|
||||
Really really really long name
|
||||
</div>
|
||||
<wa-icon name="chevron-right"></wa-icon>
|
||||
</div>
|
||||
</wa-button>
|
||||
|
||||
<main id="main-content" class="main">
|
||||
<h1 style="margin: 0.5rem 0 2rem 0">Good Evening, Konnor Rogers</h1>
|
||||
|
||||
<section>
|
||||
<div style="display: flex; justify-content: space-between; flex-wrap: wrap; align-items: flex-end; gap: 8px">
|
||||
<h2 style="">Overview</h2>
|
||||
|
||||
<wa-select value="monthly">
|
||||
<wa-option value="daily">Daily</wa-option>
|
||||
<wa-option value="weekly">Weekly</wa-option>
|
||||
<wa-option value="monthly">Monthly</wa-option>
|
||||
<wa-option value="yearly">Yearly</wa-option>
|
||||
</wa-select>
|
||||
</div>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<div
|
||||
style="
|
||||
margin-top: 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
grid-auto-rows: 1fr;
|
||||
gap: var(--wa-spacing-large);
|
||||
text-align: center;
|
||||
"
|
||||
>
|
||||
<wa-card class="wa-card--muted" style="--padding: 8px">
|
||||
<h3 slot="header">Total listening time</h3>
|
||||
|
||||
<p>
|
||||
<strong><wa-format-number value="35000"></wa-format-number></strong> minutes
|
||||
</p>
|
||||
<p>
|
||||
<mark class="highlight highlight--success"> +16% </mark>
|
||||
|
||||
<small class="text--light">from last month</small>
|
||||
</p>
|
||||
</wa-card>
|
||||
|
||||
<wa-card class="wa-card--muted" style="--padding: 8px">
|
||||
<h3 slot="header">Total songs played</h3>
|
||||
|
||||
<p>
|
||||
<strong><wa-format-number value="302"></wa-format-number></strong> songs
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<mark class="highlight highlight--danger"> -0.3% </mark>
|
||||
|
||||
<small class="text--light">from last month</small>
|
||||
</p>
|
||||
</wa-card>
|
||||
|
||||
<wa-card class="wa-card--muted" style="--padding: 8px">
|
||||
<h3 slot="header">Average listening session</h3>
|
||||
|
||||
<p>
|
||||
<strong><wa-format-number value="36"></wa-format-number></strong> minutes
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<mark class="highlight highlight--success"> +11.4% </mark>
|
||||
|
||||
<small class="text--light">from last month</small>
|
||||
</p>
|
||||
</wa-card>
|
||||
|
||||
<wa-card class="wa-card--muted" style="--padding: 8px">
|
||||
<h3 slot="header">Average track listening time</h3>
|
||||
|
||||
<p>
|
||||
<strong><wa-format-number value="2"></wa-format-number></strong> minutes,
|
||||
<strong><wa-format-number value="42"></wa-format-number></strong> seconds
|
||||
</p>
|
||||
<p>
|
||||
<mark class="highlight highlight--success"> -6.2% </mark>
|
||||
|
||||
<small class="text--light">from last month</small>
|
||||
</p>
|
||||
</wa-card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section style="margin-top: 3rem">
|
||||
<h2>Recent playlists</h2>
|
||||
|
||||
<div
|
||||
style="
|
||||
margin-top: 1rem;
|
||||
--card-width: clamp(200px, 100%, 350px);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(var(--card-width), 1fr));
|
||||
gap: 16px;
|
||||
"
|
||||
>
|
||||
<wa-button variant="neutral" class="wa-button--card wa-button--muted" href="#">
|
||||
<div style="display: flex; gap: 1rem">
|
||||
<img
|
||||
src="https://via.placeholder.com/100x100"
|
||||
height="100"
|
||||
width="100"
|
||||
style="align-self: center; border-radius: 8px; display: inline-block; max-width: 100%; flex: 0 1 auto"
|
||||
/>
|
||||
|
||||
<article
|
||||
style="
|
||||
display: grid;
|
||||
align-content: center;
|
||||
color: var(--wa-color-neutral-700);
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
"
|
||||
>
|
||||
<h2 style="max-width: 100%; text-overflow: ellipsis; overflow: hidden">Punk Rock Anthems</h2>
|
||||
|
||||
<p style="max-width: 100%; text-overflow: ellipsis; overflow: hidden">
|
||||
For when you just wanna rock out, have a good time, and feel angsty.
|
||||
</p>
|
||||
|
||||
<wa-icon name="chevron-right" style="justify-self: flex-end"></wa-icon>
|
||||
</article>
|
||||
</div>
|
||||
</wa-button>
|
||||
|
||||
<wa-button variant="neutral" class="wa-button--card wa-button--muted" href="#">
|
||||
<div style="display: flex; gap: 1rem">
|
||||
<img
|
||||
src="https://via.placeholder.com/100x100"
|
||||
height="100"
|
||||
width="100"
|
||||
style="align-self: center; border-radius: 8px; display: inline-block; max-width: 100%"
|
||||
/>
|
||||
|
||||
<article
|
||||
style="
|
||||
display: grid;
|
||||
align-content: center;
|
||||
color: var(--wa-color-neutral-700);
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
"
|
||||
>
|
||||
<h2 style="max-width: 100%; text-overflow: ellipsis; overflow: hidden">Random</h2>
|
||||
|
||||
<p style="max-width: 100%; text-overflow: ellipsis; overflow: hidden">
|
||||
Throw it on shuffle, and embrace the chaos.
|
||||
</p>
|
||||
|
||||
<wa-icon name="chevron-right" style="justify-self: flex-end"></wa-icon>
|
||||
</article>
|
||||
</div>
|
||||
</wa-button>
|
||||
|
||||
<wa-button variant="neutral" class="wa-button--card wa-button--muted" href="#">
|
||||
<div style="display: flex; gap: 1rem">
|
||||
<img
|
||||
src="https://via.placeholder.com/100x100"
|
||||
height="100"
|
||||
width="100"
|
||||
style="align-self: center; border-radius: 8px; display: inline-block"
|
||||
/>
|
||||
|
||||
<article
|
||||
style="
|
||||
display: grid;
|
||||
align-content: center;
|
||||
color: var(--wa-color-neutral-700);
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
"
|
||||
>
|
||||
<h2 style="max-width: 100%; text-overflow: ellipsis; overflow: hidden">Classics</h2>
|
||||
|
||||
<p style="max-width: 100%; text-overflow: ellipsis; overflow: hidden">
|
||||
Timeless songs that you love to relive.
|
||||
</p>
|
||||
|
||||
<wa-icon name="chevron-right" style="justify-self: flex-end"></wa-icon>
|
||||
</article>
|
||||
</div>
|
||||
</wa-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section style="margin-top: 3rem">
|
||||
<h2>Recent tracks</h2>
|
||||
|
||||
<div style="margin-top: 1rem; max-width: 100%; overflow: auto">
|
||||
<table style="width: 100%; min-width: 500px; border-spacing: 2px">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Release Date</th>
|
||||
|
||||
<th>Name</th>
|
||||
|
||||
<th>Album</th>
|
||||
|
||||
<th>Artist</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<wa-format-date date="2020-07-15T09:17:00-04:00"></wa-format-date>
|
||||
</td>
|
||||
|
||||
<td>No Strangers to Love</td>
|
||||
|
||||
<td>You Know the Rules</td>
|
||||
|
||||
<td>Rick Barry</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<wa-format-date date="2020-07-15T09:17:00-04:00"></wa-format-date>
|
||||
</td>
|
||||
|
||||
<td>No Strangers to Love</td>
|
||||
|
||||
<td>You Know the Rules</td>
|
||||
|
||||
<td>Rick Barry</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<wa-format-date date="2020-07-15T09:17:00-04:00"></wa-format-date>
|
||||
</td>
|
||||
|
||||
<td>No Strangers to Love</td>
|
||||
|
||||
<td>You Know the Rules</td>
|
||||
|
||||
<td>Rick Barry</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<wa-format-date date="2020-07-15T09:17:00-04:00"></wa-format-date>
|
||||
</td>
|
||||
|
||||
<td>No Strangers to Love</td>
|
||||
|
||||
<td>You Know the Rules</td>
|
||||
|
||||
<td>Rick Barry</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<wa-format-date date="2020-07-15T09:17:00-04:00"></wa-format-date>
|
||||
</td>
|
||||
|
||||
<td>No Strangers to Love</td>
|
||||
|
||||
<td>You Know the Rules</td>
|
||||
|
||||
<td>Rick Barry</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<wa-format-date date="2020-07-15T09:17:00-04:00"></wa-format-date>
|
||||
</td>
|
||||
|
||||
<td>No Strangers to Love</td>
|
||||
|
||||
<td>You Know the Rules</td>
|
||||
|
||||
<td>Rick Barry</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<wa-format-date date="2020-07-15T09:17:00-04:00"></wa-format-date>
|
||||
</td>
|
||||
|
||||
<td>No Strangers to Love</td>
|
||||
|
||||
<td>You Know the Rules</td>
|
||||
|
||||
<td>Rick Barry</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<wa-format-date date="2020-07-15T09:17:00-04:00"></wa-format-date>
|
||||
</td>
|
||||
|
||||
<td>No Strangers to Love</td>
|
||||
|
||||
<td>You Know the Rules</td>
|
||||
|
||||
<td>Rick Barry</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<wa-format-date date="2020-07-15T09:17:00-04:00"></wa-format-date>
|
||||
</td>
|
||||
|
||||
<td>No Strangers to Love</td>
|
||||
|
||||
<td>You Know the Rules</td>
|
||||
|
||||
<td>Rick Barry</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<wa-format-date date="2020-07-15T09:17:00-04:00"></wa-format-date>
|
||||
</td>
|
||||
|
||||
<td>No Strangers to Love</td>
|
||||
|
||||
<td>You Know the Rules</td>
|
||||
|
||||
<td>Rick Barry</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<wa-format-date date="2020-07-15T09:17:00-04:00"></wa-format-date>
|
||||
</td>
|
||||
|
||||
<td>No Strangers to Love</td>
|
||||
|
||||
<td>You Know the Rules</td>
|
||||
|
||||
<td>Rick Barry</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</wa-layout>
|
||||
74
docs/_includes/layout-templates/example.css
Normal file
74
docs/_includes/layout-templates/example.css
Normal file
@@ -0,0 +1,74 @@
|
||||
html {
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.grid {
|
||||
font-size: 1.35rem;
|
||||
text-align: center;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: var(--wa-color-blue-90);
|
||||
}
|
||||
|
||||
aside {
|
||||
min-width: 250px;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
main {
|
||||
background-color: var(--wa-color-green-90);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--wa-color-blue-80);
|
||||
}
|
||||
|
||||
.banner {
|
||||
background-color: var(--wa-color-yellow-90);
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: var(--wa-color-blue-90);
|
||||
}
|
||||
|
||||
.banner,
|
||||
.header {
|
||||
min-width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
[slot='header'] {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
[slot='aside'] {
|
||||
height: 100%;
|
||||
background-color: var(--wa-color-yellow-90);
|
||||
}
|
||||
|
||||
[slot='menu'] {
|
||||
height: 100%;
|
||||
background-color: var(--wa-color-red-90);
|
||||
}
|
||||
|
||||
[slot='main-header'] {
|
||||
background-color: var(--wa-color-red-80);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
[slot='main-footer'] {
|
||||
background-color: var(--wa-color-green-80);
|
||||
}
|
||||
47
docs/_includes/layout-templates/example.html
Normal file
47
docs/_includes/layout-templates/example.html
Normal file
@@ -0,0 +1,47 @@
|
||||
<wa-layout>
|
||||
<div slot="navigation">
|
||||
<div style="padding: 2rem">
|
||||
<a href="#">Option 1</a><br />
|
||||
<a href="#">Option 2</a><br />
|
||||
<a href="#">Option 3</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button data-toggle-nav>Menu</button>
|
||||
|
||||
<p>I'm just a lowly page.</p>
|
||||
<p>
|
||||
I think I'll put a <a href="#">link right here</a> for you to click. And maybe <a href="#">another one here</a> for
|
||||
fun.
|
||||
</p>
|
||||
|
||||
<wa-dialog id="dialog"> I'm just a lowly dialog. </wa-dialog>
|
||||
|
||||
<wa-button>Open Dialog</wa-button>
|
||||
</wa-layout>
|
||||
|
||||
<style>
|
||||
wa-layout {
|
||||
--menu-width: 260px;
|
||||
|
||||
outline: dashed 1px dodgerblue;
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
wa-layout::part(menu) {
|
||||
border-right: solid 1px #ececec;
|
||||
}
|
||||
|
||||
wa-layout::part(main-content) {
|
||||
padding: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const dialog = document.getElementById('dialog');
|
||||
|
||||
dialog.nextElementSibling.addEventListener('click', () => {
|
||||
dialog.open = true;
|
||||
});
|
||||
</script>
|
||||
9
docs/_includes/layout-templates/hero.css
Normal file
9
docs/_includes/layout-templates/hero.css
Normal file
@@ -0,0 +1,9 @@
|
||||
html {
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
height: auto;
|
||||
}
|
||||
1
docs/_includes/layout-templates/hero.html
Normal file
1
docs/_includes/layout-templates/hero.html
Normal file
@@ -0,0 +1 @@
|
||||
<wa-layout main-id="main-content" class="wa-theme-light"> </wa-layout>
|
||||
182
docs/_includes/layout-templates/sport-awesome.css
Normal file
182
docs/_includes/layout-templates/sport-awesome.css
Normal file
@@ -0,0 +1,182 @@
|
||||
html {
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0 !important;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/** https://andy-bell.co.uk/my-favourite-3-lines-of-css/ */
|
||||
.flow > * + * {
|
||||
margin-block-start: var(--wa-flow-spacing);
|
||||
}
|
||||
|
||||
img {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.navigation--desktop::part(nav-items) {
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.navigation--top::part(nav-items) {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.navigation--top wa-nav-item {
|
||||
font-size: 1.4rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.navigation--top wa-nav-item::part(content) {
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.navigation--top wa-nav-item {
|
||||
--text-color: var(--wa-color-brand-text-on-vivid);
|
||||
--text-color-hover: var(--wa-color-text-normal);
|
||||
--background-color: transparent;
|
||||
--background-color-hover: var(--wa-color-neutral-fill-muted-alt);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
border-bottom: var(--wa-panel-border-width) var(--wa-panel-border-style) var(--wa-color-brand-fill-vivid-alt);
|
||||
background-color: var(--wa-color-white);
|
||||
}
|
||||
|
||||
.header > * {
|
||||
padding-top: var(--wa-space-m);
|
||||
padding-bottom: var(--wa-space-m);
|
||||
}
|
||||
|
||||
.header__navigation {
|
||||
display: flex;
|
||||
clip-path: polygon(var(--wa-space-2xl) 0, 100% 0, 100% 100%, 0 100%);
|
||||
padding-inline-start: calc(var(--wa-space-2xl) + var(--wa-space-xs));
|
||||
padding-inline-end: var(--wa-space-m);
|
||||
background-color: var(--wa-color-brand-fill-vivid-alt);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header > .logo {
|
||||
padding-inline-start: var(--wa-space-m);
|
||||
/** Responsive font size for the top header to make it flow nicer */
|
||||
font-size: clamp(1rem, 4vw, 1.4rem);
|
||||
}
|
||||
|
||||
a.logo {
|
||||
flex-shrink: 0;
|
||||
font-size: 1.4rem;
|
||||
font-weight: bold;
|
||||
color: var(--wa-color-text-normal);
|
||||
text-decoration: none;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
a.logo:is(:hover, :focus) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.logo__accent {
|
||||
color: var(--wa-color-yellow-70);
|
||||
}
|
||||
|
||||
.navigation--desktop {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navigation--desktop wa-nav-item[current='page'] {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 8px;
|
||||
text-decoration-thickness: 4px;
|
||||
text-decoration-color: var(--wa-color-brand-outline-muted-alt);
|
||||
}
|
||||
|
||||
.navigation--desktop wa-nav-item[current='page']:hover {
|
||||
text-decoration-color: var(--wa-color-brand-outline-vivid);
|
||||
}
|
||||
|
||||
.navigation--extra {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
|
||||
wa-layout[view='desktop'] [data-toggle-nav],
|
||||
wa-layout[view='desktop']::part(navigation) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
wa-layout[view='mobile'] .navigation--desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-banner {
|
||||
padding: var(--wa-space-m);
|
||||
text-align: center;
|
||||
background-color: var(--wa-color-yellow-80);
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
/** 30vw ensures we never show more than 3 tables in the viewport at any given time. */
|
||||
grid-template-columns: repeat(auto-fit, minmax(clamp(225px, 30vw, 100%), 1fr));
|
||||
gap: var(--wa-space-m);
|
||||
grid-template-rows: 1fr;
|
||||
align-items: start;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.stats-grid table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
border-radius: var(--wa-panel-corners);
|
||||
border: var(--wa-panel-border-width) var(--wa-panel-border-style) var(--wa-color-surface-outline);
|
||||
}
|
||||
|
||||
.table-scroll {
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.stats-grid table * {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.stats-grid table th {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats-grid table td:nth-child(2) {
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.navigation--top.navigation--social::part(nav-items) {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.navigation--social {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.navigation--top .social-link {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 415px) {
|
||||
.navigation--top .social-link {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
343
docs/_includes/layout-templates/sport-awesome.html
Normal file
343
docs/_includes/layout-templates/sport-awesome.html
Normal file
@@ -0,0 +1,343 @@
|
||||
<wa-layout main-id="main-content" class="wa-theme-light" mobile-breakpoint="925" disable-sticky="banner">
|
||||
<header class="layout-banner" slot="banner">Reminder! Get your insurance paperwork in by Oct 12!</header>
|
||||
|
||||
<header class="header" slot="header">
|
||||
<a href="#" class="logo"> <span>Sport</span> <span class="logo__accent">Awesome</span> </a>
|
||||
|
||||
<div class="header__navigation">
|
||||
<wa-nav-group class="navigation navigation--top navigation--desktop">
|
||||
<wa-nav-item href="#" current="page">Home</wa-nav-item>
|
||||
<wa-nav-item href="#">Schedule</wa-nav-item>
|
||||
<wa-nav-item href="#">Roster</wa-nav-item>
|
||||
<wa-nav-item href="#">Stats</wa-nav-item>
|
||||
<wa-nav-item href="#">Videos</wa-nav-item>
|
||||
</wa-nav-group>
|
||||
|
||||
<wa-nav-group class="navigation navigation--top navigation--social">
|
||||
<wa-nav-item class="social-link" href="#"><wa-icon name="instagram"></wa-icon></wa-nav-item>
|
||||
<wa-nav-item class="social-link" href="#"><wa-icon name="facebook"></wa-icon></wa-nav-item>
|
||||
<wa-nav-item data-toggle-nav href="#"><wa-icon name="list"></wa-icon></wa-nav-item>
|
||||
</wa-nav-group>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<a href="#" class="logo" slot="navigation-header"> Sport <span class="logo__accent">Awesome</span> </a>
|
||||
|
||||
<wa-nav-group slot="navigation">
|
||||
<wa-nav-item href="#" current="page">
|
||||
<wa-icon name="house-door" slot="prefix"></wa-icon>
|
||||
Home
|
||||
</wa-nav-item>
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="calendar" slot="prefix"></wa-icon>
|
||||
Schedule
|
||||
</wa-nav-item>
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="people" slot="prefix"></wa-icon>
|
||||
Roster
|
||||
</wa-nav-item>
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="graph-up-arrow" slot="prefix"></wa-icon>
|
||||
Stats
|
||||
</wa-nav-item>
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="camera-video" slot="prefix"></wa-icon>
|
||||
Videos
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<wa-nav-group>
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="instagram" slot="prefix"></wa-icon>
|
||||
Instagram
|
||||
</wa-nav-item>
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="facebook" slot="prefix"></wa-icon>
|
||||
Facebook
|
||||
</wa-nav-item>
|
||||
</wa-nav-group>
|
||||
</wa-nav-group>
|
||||
|
||||
<main id="main-content" class="flow" style="padding: var(--wa-space-m)">
|
||||
<div style="display: flex; flex-wrap: wrap; gap: var(--wa-space-m)">
|
||||
<figure style="min-width: 75%; display: flex; flex-direction: column; margin: 0 auto">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1562552052-c72ceddf93dc?auto=format&fit=crop&q=80&w=3540&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
|
||||
loading="lazy"
|
||||
alt="Picture of people playing volleyball"
|
||||
height="512"
|
||||
width="300"
|
||||
style="aspect-ratio: 16/9; min-width: 100%"
|
||||
/>
|
||||
<figcaption>
|
||||
Photo by
|
||||
<cite><a href="https://unsplash.com/@stevenabraham">Steven Abraham</a></cite>
|
||||
courtesy of
|
||||
<cite><a href="https://unsplash.com/">Unsplash</a></cite>
|
||||
</figcaption>
|
||||
</figure>
|
||||
<aside
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: var(--wa-panel-corners);
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
max-width: 75vw;
|
||||
margin: 0 auto;
|
||||
"
|
||||
>
|
||||
<header
|
||||
style="
|
||||
font-size: 1.4rem;
|
||||
font-weight: bold;
|
||||
color: var(--wa-color-brand-text-on-vivid);
|
||||
background-color: var(--wa-color-brand-fill-vivid-alt);
|
||||
padding: var(--wa-space-m);
|
||||
text-align: center;
|
||||
border-top-left-radius: inherit;
|
||||
border-top-right-radius: inherit;
|
||||
"
|
||||
>
|
||||
Upcoming
|
||||
</header>
|
||||
|
||||
<div
|
||||
style="
|
||||
color: var(--wa-color-brand-text-on-vivid);
|
||||
background-color: var(--wa-color-danger-fill-vivid-alt);
|
||||
padding: var(--wa-space-s);
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
"
|
||||
>
|
||||
Tryouts!
|
||||
</div>
|
||||
|
||||
<div
|
||||
style="
|
||||
background-color: var(--wa-color-neutral-90);
|
||||
padding: var(--wa-space-m);
|
||||
border-bottom-left-radius: inherit;
|
||||
border-bottom-right-radius: inherit;
|
||||
text-align: center;
|
||||
"
|
||||
>
|
||||
<span style="font-weight: bold; font-size: 1.2rem">Barclay's Center</span>
|
||||
|
||||
<br />
|
||||
|
||||
<time>Sat, Jul 3rd • 11:30am</time>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h1>Welcome to Sport <span class="logo__accent">Awesome</span></h1>
|
||||
|
||||
<p>
|
||||
Dolor quam voluptate nostrum neque eius. Quo nemo corporis repellat quia sunt molestiae! Dolorem labore
|
||||
laudantium nobis numquam reprehenderit? Voluptatibus odio animi nemo maiores accusamus eaque Assumenda
|
||||
perferendis omnis quae. Adipisicing beatae lorem nisi aliquid similique Voluptas doloremque pariatur tempore
|
||||
omnis maiores explicabo. Provident iste vel explicabo corporis quaerat! Necessitatibus minus quas iusto ducimus
|
||||
consequatur illo Cum eos adipisci ut!
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 style="text-align: center; font-weight: bold; font-size: 1.5em">Stats</h2>
|
||||
<div class="stats-grid">
|
||||
<div class="table-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">Serve</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Attempts</td>
|
||||
<td>2936</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Serve %</td>
|
||||
<td>93.6%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Aces</td>
|
||||
<td>268</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Errors</td>
|
||||
<td>189</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">Serve Receive</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Attempts</td>
|
||||
<td>2428</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pass Rating</td>
|
||||
<td>1.72</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pass Error %</td>
|
||||
<td>13.3%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3-pass %</td>
|
||||
<td>28.5%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">Attack</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Attempts</td>
|
||||
<td>3624</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kills</td>
|
||||
<td>1431</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Errors</td>
|
||||
<td>268</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hitting Efficiency</td>
|
||||
<td>0.254</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Kill %</td>
|
||||
<td>39.5%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">Dig</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Attempts</td>
|
||||
<td>3124</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Digs</td>
|
||||
<td>2235</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Errors</td>
|
||||
<td>889</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Dig %</td>
|
||||
<td>71.5%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">Block</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Blocks</td>
|
||||
<td>348</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Errors</td>
|
||||
<td>414</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Block %</td>
|
||||
<td>31.6%</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Error %</td>
|
||||
<td>24.6%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">Set</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Assists</td>
|
||||
<td>1364</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Errors</td>
|
||||
<td>81</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer
|
||||
slot="main-footer"
|
||||
style="
|
||||
background-color: var(--wa-color-brand-fill-vivid);
|
||||
color: var(--wa-color-text-inverse);
|
||||
padding: var(--wa-space-m);
|
||||
text-align: center;
|
||||
"
|
||||
>
|
||||
© 2023 - Sport Awesome
|
||||
</footer>
|
||||
</wa-layout>
|
||||
92
docs/_includes/layout-widget.njk
Normal file
92
docs/_includes/layout-widget.njk
Normal file
@@ -0,0 +1,92 @@
|
||||
<!-- playground-hide -->
|
||||
<style>
|
||||
.layout-widget {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
background-color: white;
|
||||
bottom: 4rem;
|
||||
left: 4rem;
|
||||
}
|
||||
|
||||
.layout-widget:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<wa-dropdown id="js-layout-widget" class="layout-widget" stay-open-on-select>
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-menu></wa-menu>
|
||||
</wa-dropdown>
|
||||
|
||||
<script type="module">
|
||||
const layoutWidget = document.querySelector("#js-layout-widget")
|
||||
const docFrag = new DocumentFragment()
|
||||
|
||||
function makeMenuItem (type, slot) {
|
||||
const menuItem = Object.assign(document.createElement("wa-menu-item"), {
|
||||
type: "checkbox",
|
||||
textContent: `${type} ${slot}`
|
||||
})
|
||||
|
||||
menuItem.setAttribute("value", `${type}-${slot}`)
|
||||
return menuItem
|
||||
}
|
||||
|
||||
document.querySelectorAll("wa-layout > [slot]").forEach((el) => {
|
||||
const slot = el.getAttribute("slot");
|
||||
docFrag.append(makeMenuItem("toggle", slot), makeMenuItem("overflow", slot), document.createElement("wa-divider"))
|
||||
})
|
||||
|
||||
docFrag.append(makeMenuItem("toggle", "main"), makeMenuItem("overflow", "main"))
|
||||
layoutWidget.querySelector("wa-menu").append(docFrag)
|
||||
|
||||
function capitalize(string) {
|
||||
return string.split(/\s+/).map((str) => str[0].toUppercase() + str.slice(1)).join(" ")
|
||||
}
|
||||
|
||||
function handleSelect (e) {
|
||||
const item = e.detail.item
|
||||
const val = item.getAttribute("value")
|
||||
|
||||
if (val === "footer-0") {
|
||||
|
||||
}
|
||||
|
||||
const slot = val.split("-").slice(1).join("-")
|
||||
|
||||
let el
|
||||
|
||||
if (slot === "main") {
|
||||
el = document.querySelector(`main`)
|
||||
} else {
|
||||
el = document.querySelector(`wa-layout > [slot='${slot}']`)
|
||||
}
|
||||
|
||||
if (val.startsWith("overflow")) {
|
||||
if (item.checked) {
|
||||
el.textContent = "lorem ".repeat(1_000)
|
||||
return
|
||||
}
|
||||
|
||||
el.textContent = slot
|
||||
return
|
||||
}
|
||||
|
||||
if (val.startsWith("toggle")) {
|
||||
if (item.checked) {
|
||||
el.setAttribute("hidden", "")
|
||||
return
|
||||
}
|
||||
|
||||
el.removeAttribute("hidden")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
layoutWidget.addEventListener("wa-select", handleSelect);
|
||||
{% if in_playground %}
|
||||
</script>
|
||||
{% else %}
|
||||
</script>
|
||||
{% endif %}
|
||||
<!-- playground-hide-end -->
|
||||
1
docs/_includes/logo.njk
Normal file
1
docs/_includes/logo.njk
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.3 KiB |
124
docs/_includes/playground.njk
Normal file
124
docs/_includes/playground.njk
Normal file
@@ -0,0 +1,124 @@
|
||||
<script type="module">
|
||||
import "https://cdn.jsdelivr.net/npm/light-pen@1.1.3/+esm"
|
||||
</script>
|
||||
<div style="display: grid; grid-template-columns: minmax(0, auto) minmax(0, 1fr); min-height: 100%; grid-template-rows: minmax(0, 1fr); gap: 8px; ">
|
||||
<!-- Knobs -->
|
||||
<div id="knobs">
|
||||
<div class="space-vertically">
|
||||
<a href="/">{% include 'logo.njk' %}</a>
|
||||
<wa-select name="theme" label="Theme" value="default">
|
||||
<wa-option value="default">Default</wa-option>
|
||||
<wa-option value="glassy">Glassy</wa-option>
|
||||
<wa-option value="mellow">Mellow</wa-option>
|
||||
<wa-option value="playful">Playful</wa-option>
|
||||
</wa-select>
|
||||
<wa-select name="heading-text" label="Heading" value="">
|
||||
<wa-option value="">Theme default</wa-option>
|
||||
<wa-option value="serif">Serif</wa-option>
|
||||
<wa-option value="sans-serif">Sans-serif</wa-option>
|
||||
<wa-option value="monospace">Monospace</wa-option>
|
||||
<wa-option value="cursive">Cursive</wa-option>
|
||||
</wa-select>
|
||||
<wa-select name="body-text" label="Body" value="">
|
||||
<wa-option value="">Theme default</wa-option>
|
||||
<wa-option value="serif">Serif</wa-option>
|
||||
<wa-option value="sans-serif">Sans-serif</wa-option>
|
||||
<wa-option value="monospace">Monospace</wa-option>
|
||||
<wa-option value="cursive">Cursive</wa-option>
|
||||
</wa-select>
|
||||
<wa-select name="border-style" label="Border Style" value="solid">
|
||||
<wa-option value="solid">Solid</wa-option>
|
||||
<wa-option value="dashed">Dashed</wa-option>
|
||||
<wa-option value="dotted">Dotted</wa-option>
|
||||
<wa-option value="double">Double</wa-option>
|
||||
</wa-select>
|
||||
<wa-range name="border-width" label="Border Width" min="1" max="5" value="1" step="1" tooltip="none"></wa-range>
|
||||
<wa-range name="spacing" label="Spacing" min=".5" max="1.5" value="1" step="0.125" tooltip="none"></wa-range>
|
||||
<wa-range name="corners" label="Corners" min="0" max="1.5" value=".25" step=".125" tooltip="none"></wa-range>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
const container = document.getElementById('knobs');
|
||||
const iframeDocument = () => document.querySelector("light-pen").iframeElem.contentWindow.document
|
||||
const themeStylesheet = () => iframeDocument().getElementById('theme-stylesheet');
|
||||
|
||||
// Theme
|
||||
container.querySelector('[name="theme"]').addEventListener('wa-change', event => {
|
||||
themeStylesheet().href = `/dist/themes/${event.target.value}.css`;
|
||||
});
|
||||
|
||||
// Heading text
|
||||
container.querySelector('[name="heading-text"]').addEventListener('wa-input', event => {
|
||||
iframeDocument().documentElement.style.setProperty('--wa-font-family-heading', event.target.value);
|
||||
});
|
||||
|
||||
// Body text
|
||||
container.querySelector('[name="body-text"]').addEventListener('wa-input', event => {
|
||||
iframeDocument().documentElement.style.setProperty('--wa-font-family-body', event.target.value);
|
||||
});
|
||||
|
||||
// Corners
|
||||
container.querySelector('[name="corners"]').addEventListener('wa-input', event => {
|
||||
iframeDocument().documentElement.style.setProperty('--wa-corners-base', `${event.target.value}rem`);
|
||||
});
|
||||
|
||||
// Border width
|
||||
container.querySelector('[name="border-width"]').addEventListener('wa-input', event => {
|
||||
iframeDocument().documentElement.style.setProperty('--wa-border-width-base', `${event.target.value / 16}rem`);
|
||||
});
|
||||
|
||||
// Border style
|
||||
container.querySelector('[name="border-style"]').addEventListener('wa-input', event => {
|
||||
iframeDocument().documentElement.style.setProperty('--wa-border-style', event.target.value);
|
||||
});
|
||||
|
||||
// Spacing style
|
||||
container.querySelector('[name="spacing"]').addEventListener('wa-input', event => {
|
||||
iframeDocument().documentElement.style.setProperty('--wa-space-base', `${event.target.value}rem`);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--knobs-width: 300px;
|
||||
}
|
||||
|
||||
#knobs {
|
||||
background: var(--wa-color-surface-default);
|
||||
border: var(--wa-border-style) var(--wa-border-width-thin) var(--wa-color-surface-outline);
|
||||
border-radius: var(--wa-corners-2x);
|
||||
box-shadow: var(--wa-shadow-level-2);
|
||||
width: var(--knobs-width);
|
||||
padding: 2rem;
|
||||
margin-inline: auto;
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
#knobs p {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<light-pen style="height: 100%;" resize-position="30">
|
||||
<script type="text/plain" slot="html">
|
||||
<link id="theme-stylesheet" href="/dist/themes/default.css" rel="stylesheet">
|
||||
<link id="applied-stylesheet" href="/dist/themes/applied.css"" rel="stylesheet">
|
||||
|
||||
{% include html_file %}
|
||||
</script>
|
||||
|
||||
<script type="text/plain" slot="css">
|
||||
@import "/dist/themes/applied.css";
|
||||
|
||||
{% include css_file %}
|
||||
</script>
|
||||
|
||||
<script type="text/plain" slot="js">
|
||||
import { setBasePath } from "/dist/utilities/base-path.js";
|
||||
setBasePath("/dist");
|
||||
import("/dist/autoloader.js");
|
||||
</script>
|
||||
</light-pen>
|
||||
<div>
|
||||
@@ -2,9 +2,11 @@
|
||||
<li>
|
||||
<h2>Experimental</h2>
|
||||
<ul>
|
||||
<li><a href="/experimental/style-guide">Style Guide</a></li>
|
||||
<li><a href="/experimental/themer">Themer</a></li>
|
||||
<li><a href="/experimental/style-guide">Style Guide</a></li>
|
||||
<li><a href="/experimental/form-validation">Form Validation Styles</a></li>
|
||||
<li style="margin-top: .5rem;"><wa-switch id="theme-toggle">Dark mode</wa-switch></li>
|
||||
<li><a href="/layouts/index.html">Layout Examples</a></li>
|
||||
<script type="module">
|
||||
// Temporary dark toggle
|
||||
const toggle = document.getElementById('theme-toggle');
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 21 KiB |
@@ -1,8 +1,13 @@
|
||||
/**
|
||||
* Turns headings into clickable, deep linkable anchors. The provided doc should be a document object provided by JSDOM.
|
||||
* Turns tables into scrollable tables
|
||||
* The same document will be returned with the appropriate DOM manipulations.
|
||||
*/
|
||||
module.exports = function (doc, options) {
|
||||
// We don't want to run this on layouts.
|
||||
if (doc.querySelector("[data-layout='layout-example.njk']")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tables = [...doc.querySelectorAll('table')];
|
||||
|
||||
options = {
|
||||
|
||||
1
docs/assets/images/favicon.svg
Normal file
1
docs/assets/images/favicon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="186" height="186" viewBox="0 0 186 186"><g fill="none" fill-rule="evenodd"><rect width="186" height="186" fill="#103257" opacity="0"/><path fill="#F6894C" d="M106.95,13.9672306 C106.95,19.1752428 104.10296,23.7175103 99.8823543,26.1190227 L130.2,48.8851296 L159.91784,39.4892184 C158.760743,37.4541707 158.1,35.0993755 158.1,32.5902046 C158.1,24.8763205 164.345703,18.6229741 172.05,18.6229741 C179.754297,18.6229741 186,24.8763205 186,32.5902046 C186,40.3040179 179.754297,46.5574352 172.05,46.5574352 C171.315566,46.5574352 170.594594,46.5006795 169.890983,46.39107 L137.151086,130.163238 C134.361086,137.302399 127.486526,142 119.830057,142 L66.1699429,142 C58.5134743,142 51.6389143,137.302399 48.8489143,130.163238 L16.1089463,46.39107 C15.4052994,46.5006795 14.6842926,46.5574352 13.95,46.5574352 C6.245632,46.5574352 0,40.3040179 0,32.5902046 C0,24.8763205 6.245632,18.6229741 13.95,18.6229741 C21.654368,18.6229741 27.9,24.8763205 27.9,32.5902046 C27.9,35.0993755 27.2391509,37.4541707 26.0822663,39.4892184 L55.8,48.8851296 L86.1176457,26.1190227 C81.89704,23.7175103 79.05,19.1752428 79.05,13.9672306 C79.05,6.25334639 85.2957029,0 93,0 C100.704297,0 106.95,6.25334639 106.95,13.9672306 Z" transform="translate(0 22)"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
BIN
docs/assets/images/layout.png
Normal file
BIN
docs/assets/images/layout.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 8.3 KiB |
@@ -15,7 +15,7 @@
|
||||
}
|
||||
|
||||
function isSidebarVisible() {
|
||||
return getSidebar().getBoundingClientRect().x >= 0;
|
||||
return getSidebar()?.getBoundingClientRect().x >= 0;
|
||||
}
|
||||
|
||||
function toggleSidebar(force) {
|
||||
@@ -24,7 +24,11 @@
|
||||
}
|
||||
|
||||
function updateInert() {
|
||||
getSidebar().inert = !isSidebarVisible();
|
||||
const sidebar = getSidebar();
|
||||
|
||||
if (sidebar) {
|
||||
sidebar.inert = !isSidebarVisible();
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle the menu
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
--docs-content-padding: 2rem;
|
||||
--docs-content-vertical-spacing: 2rem;
|
||||
--docs-search-overlay-background: rgb(0 0 0 / 0.2);
|
||||
--docs-skip-to-main-width: 200px;
|
||||
}
|
||||
|
||||
/* Light theme */
|
||||
@@ -92,7 +91,7 @@ h1:first-of-type {
|
||||
}
|
||||
|
||||
/* Color matching logos */
|
||||
svg.wordmark {
|
||||
svg.logo {
|
||||
color: var(--wa-color-brand-text-on-surface);
|
||||
}
|
||||
|
||||
@@ -166,6 +165,10 @@ pre:not(:last-child) {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
pre {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
pre > code {
|
||||
display: block;
|
||||
background: none !important;
|
||||
@@ -249,22 +252,24 @@ pre .token.italic {
|
||||
top: 0;
|
||||
right: 0;
|
||||
white-space: normal;
|
||||
color: var(--wa-color-neutral-text-on-vivid);
|
||||
transition: 150ms opacity, 150ms scale;
|
||||
color: var(--wa-color-neutral-text-on-muted-alt);
|
||||
transition:
|
||||
150ms opacity,
|
||||
150ms scale;
|
||||
}
|
||||
|
||||
.copy-code-button::part(button) {
|
||||
background-color: var(--wa-color-neutral-fill-vivid);
|
||||
background-color: var(--wa-color-neutral-fill-muted);
|
||||
border-radius: 0 var(--wa-corners-1x) 0 var(--wa-corners-1x);
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.copy-code-button::part(button):hover {
|
||||
background-color: color-mix(in oklab, var(--wa-color-neutral-fill-vivid), var(--wa-color-tint-hover));
|
||||
background-color: color-mix(in oklab, var(--wa-color-neutral-fill-muted), var(--wa-color-tint-hover));
|
||||
}
|
||||
|
||||
.copy-code-button::part(button):active {
|
||||
background-color: color-mix(in oklab, var(--wa-color-neutral-fill-vivid), var(--wa-color-tint-active));
|
||||
background-color: color-mix(in oklab, var(--wa-color-neutral-fill-muted), var(--wa-color-tint-active));
|
||||
}
|
||||
|
||||
pre .copy-code-button {
|
||||
@@ -371,6 +376,10 @@ pre:hover .copy-code-button,
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#sidebar > header svg {
|
||||
margin-bottom: var(--wa-space-s);
|
||||
}
|
||||
|
||||
#sidebar > header a {
|
||||
display: block;
|
||||
}
|
||||
@@ -438,9 +447,8 @@ pre:hover .copy-code-button,
|
||||
font-size: var(--wa-font-size-s);
|
||||
color: var(--wa-color-text-quiet);
|
||||
text-align: right;
|
||||
margin-top: -0.5rem;
|
||||
margin-right: 1rem;
|
||||
margin-bottom: -0.5rem;
|
||||
margin-top: calc(-1 * var(--wa-space-s));
|
||||
margin-bottom: calc(-1 * var(--wa-space-s));
|
||||
}
|
||||
|
||||
.sidebar-buttons {
|
||||
@@ -641,18 +649,17 @@ html.sidebar-open #menu-toggle {
|
||||
}
|
||||
|
||||
/* Skip to main content */
|
||||
#skip-to-main {
|
||||
#skip-to-content {
|
||||
position: fixed;
|
||||
top: 0.25rem;
|
||||
left: calc(50% - var(--docs-skip-to-main-width) / 2);
|
||||
top: var(--wa-space-m);
|
||||
left: var(--wa-space-m);
|
||||
z-index: 100;
|
||||
width: var(--docs-skip-to-main-width);
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
border-radius: 9999px;
|
||||
background: var(--wa-color-surface-default);
|
||||
color: var(--wa-color-text-normal);
|
||||
padding: 0.5rem;
|
||||
padding: var(--wa-space-s);
|
||||
}
|
||||
|
||||
/* Print styles */
|
||||
@@ -733,6 +740,10 @@ html.sidebar-open #menu-toggle {
|
||||
vertical-align: -2px;
|
||||
}
|
||||
|
||||
.splash svg {
|
||||
margin-block-end: var(--wa-space-m);
|
||||
}
|
||||
|
||||
.splash-end {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
@@ -745,10 +756,6 @@ html.sidebar-open #menu-toggle {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.splash .wordmark {
|
||||
max-width: 22rem;
|
||||
}
|
||||
|
||||
.splash-start h1:first-of-type {
|
||||
font-size: var(--wa-font-size-l);
|
||||
font-weight: var(--wa-font-weight-normal);
|
||||
@@ -786,7 +793,6 @@ html.sidebar-open #menu-toggle {
|
||||
}
|
||||
|
||||
.component-header__tag {
|
||||
margin-top: -0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
@@ -819,30 +825,10 @@ html.sidebar-open #menu-toggle {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.repo-button--github wa-icon {
|
||||
color: var(--wa-color-text-normal);
|
||||
}
|
||||
|
||||
.repo-button--github:hover wa-icon {
|
||||
.repo-button wa-icon {
|
||||
color: var(--wa-color-text-inverse);
|
||||
}
|
||||
|
||||
.repo-button--star wa-icon {
|
||||
color: var(--wa-color-yellow-70);
|
||||
}
|
||||
|
||||
.repo-button--star:hover wa-icon {
|
||||
color: var(--wa-color-yellow-80);
|
||||
}
|
||||
|
||||
.repo-button--twitter wa-icon {
|
||||
color: var(--wa-color-blue-60);
|
||||
}
|
||||
|
||||
.repo-button--twitter:hover wa-icon {
|
||||
color: var(--wa-color-blue-70);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
:not(.sidebar-buttons) > .repo-button {
|
||||
width: 100%;
|
||||
|
||||
@@ -28,7 +28,16 @@ module.exports = function (eleventyConfig) {
|
||||
//
|
||||
// Global data
|
||||
//
|
||||
eleventyConfig.addGlobalData('baseUrl', 'https://shoelace.style/'); // the production URL
|
||||
let baseUrl = 'https://shoelace.style/';
|
||||
|
||||
if (process.env.VERCEL_URL) {
|
||||
baseUrl = process.env.VERCEL_URL;
|
||||
if (!process.env.VERCEL_URL.match(/^https?/)) {
|
||||
baseUrl = 'https://' + baseUrl;
|
||||
}
|
||||
}
|
||||
|
||||
eleventyConfig.addGlobalData('baseUrl', baseUrl); // the production URL
|
||||
eleventyConfig.addGlobalData('layout', 'default'); // make 'default' the default layout
|
||||
eleventyConfig.addGlobalData('toc', true); // enable the table of contents
|
||||
eleventyConfig.addGlobalData('meta', {
|
||||
@@ -52,6 +61,11 @@ module.exports = function (eleventyConfig) {
|
||||
eleventyConfig.addPassthroughCopy(assetsDir);
|
||||
eleventyConfig.setServerPassthroughCopyBehavior('passthrough'); // emulates passthrough copy during --serve
|
||||
|
||||
//
|
||||
// Add additional extensions. This allows things like {% include "layout.css" %}
|
||||
//
|
||||
eleventyConfig.setTemplateFormats(['html', 'md', 'njk', 'css']);
|
||||
|
||||
//
|
||||
// Functions
|
||||
//
|
||||
@@ -183,21 +197,23 @@ module.exports = function (eleventyConfig) {
|
||||
}).window.document;
|
||||
const content = doc.querySelector('#content');
|
||||
|
||||
// Get title and headings
|
||||
const title = (doc.querySelector('title')?.textContent || path.basename(result.outputPath)).trim();
|
||||
const headings = [...content.querySelectorAll('h1, h2, h3, h4')]
|
||||
.map(heading => heading.textContent)
|
||||
.join(' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
if (content) {
|
||||
// Get title and headings
|
||||
const title = (doc.querySelector('title')?.textContent || path.basename(result.outputPath)).trim();
|
||||
const headings = [...content.querySelectorAll('h1, h2, h3, h4')]
|
||||
.map(heading => heading.textContent)
|
||||
.join(' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
|
||||
// Remove code blocks and whitespace from content
|
||||
[...content.querySelectorAll('code[class|=language]')].forEach(code => code.remove());
|
||||
const textContent = content.textContent.replace(/\s+/g, ' ').trim();
|
||||
// Remove code blocks and whitespace from content
|
||||
[...content.querySelectorAll('code[class|=language]')].forEach(code => code.remove());
|
||||
const textContent = content.textContent.replace(/\s+/g, ' ').trim();
|
||||
|
||||
// Update the index and map
|
||||
this.add({ id: index, t: title, h: headings, c: textContent });
|
||||
map[index] = { title, url };
|
||||
// Update the index and map
|
||||
this.add({ id: index, t: title, h: headings, c: textContent });
|
||||
map[index] = { title, url };
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ Set the `variant` attribute to change the alert's variant.
|
||||
<wa-alert variant="neutral" open>
|
||||
<wa-icon slot="icon" name="gear"></wa-icon>
|
||||
<strong>Your settings have been updated</strong><br />
|
||||
Settings will take affect on next login.
|
||||
Settings will take effect on next login.
|
||||
</wa-alert>
|
||||
|
||||
<br />
|
||||
@@ -102,7 +102,7 @@ const App = () => (
|
||||
<WaIcon slot="icon" name="gear" />
|
||||
<strong>Your settings have been updated</strong>
|
||||
<br />
|
||||
Settings will take affect on next login.
|
||||
Settings will take effect on next login.
|
||||
</WaAlert>
|
||||
|
||||
<br />
|
||||
@@ -276,7 +276,7 @@ You should always use the `closable` attribute so users can dismiss the notifica
|
||||
<wa-alert variant="neutral" duration="3000" closable>
|
||||
<wa-icon slot="icon" name="gear"></wa-icon>
|
||||
<strong>Your settings have been updated</strong><br />
|
||||
Settings will take affect on next login.
|
||||
Settings will take effect on next login.
|
||||
</wa-alert>
|
||||
|
||||
<wa-alert variant="warning" duration="3000" closable>
|
||||
@@ -361,7 +361,7 @@ const App = () => {
|
||||
<WaIcon slot="icon" name="gear" />
|
||||
<strong>Your settings have been updated</strong>
|
||||
<br />
|
||||
Settings will take affect on next login.
|
||||
Settings will take effect on next login.
|
||||
</WaAlert>
|
||||
|
||||
<WaAlert ref={warning} variant="warning" duration="3000" closable>
|
||||
|
||||
@@ -164,7 +164,7 @@ It's often helpful to have a button that works like a link. This is possible by
|
||||
```html:preview
|
||||
<wa-button href="https://example.com/">Link</wa-button>
|
||||
<wa-button href="https://example.com/" target="_blank">New Window</wa-button>
|
||||
<wa-button href="/assets/images/wordmark.svg" download="shoelace.svg">Download</wa-button>
|
||||
<wa-button href="/assets/images/logo.svg" download="shoelace.svg">Download</wa-button>
|
||||
<wa-button href="https://example.com/" disabled>Disabled</wa-button>
|
||||
```
|
||||
|
||||
@@ -177,7 +177,7 @@ const App = () => (
|
||||
<WaButton href="https://example.com/" target="_blank">
|
||||
New Window
|
||||
</WaButton>
|
||||
<WaButton href="/assets/images/wordmark.svg" download="shoelace.svg">
|
||||
<WaButton href="/assets/images/logo.svg" download="shoelace.svg">
|
||||
Download
|
||||
</WaButton>
|
||||
<WaButton href="https://example.com/" disabled>
|
||||
|
||||
@@ -803,7 +803,7 @@ const App = () => (
|
||||
|
||||
### Aspect Ratio
|
||||
|
||||
Use the `--aspect-ratio` custom property to customize the size of the carousel's viewport.
|
||||
Use the `--aspect-ratio` custom property to customize the size of the carousel's viewport from the default value of 16/9.
|
||||
|
||||
```html:preview
|
||||
<wa-carousel class="aspect-ratio" navigation pagination style="--aspect-ratio: 3/2;">
|
||||
|
||||
@@ -122,8 +122,10 @@ Details are designed to function independently, but you can simulate a group or
|
||||
const container = document.querySelector('.details-group-example');
|
||||
|
||||
// Close all other details when one is shown
|
||||
container.addEventListener('wa-show', event => {
|
||||
[...container.querySelectorAll('wa-details')].map(details => (details.open = event.target === details));
|
||||
container.addEventListener('sl-show', event => {
|
||||
if (event.target.localName === 'sl-details') {
|
||||
[...container.querySelectorAll('sl-details')].map(details => (details.open = event.target === details));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
118
docs/pages/components/layout.md
Normal file
118
docs/pages/components/layout.md
Normal file
@@ -0,0 +1,118 @@
|
||||
---
|
||||
meta:
|
||||
title: Layout
|
||||
description: Layouts offer an easy way to scaffold pages using minimal markup.
|
||||
layout: component
|
||||
---
|
||||
|
||||
The layout component is designed to power full webpages. It is flexible enough to handle most modern designs and includes a simple mechanism for handling desktop and mobile navigation.
|
||||
|
||||
A number of sections are available as part of the layout, most of which are optional. Content is added by [slotting elements](/getting-started/usage/#slots) into various locations.
|
||||
|
||||
This component _does not_ implement any [content sectioning](https://developer.mozilla.org/en-US/docs/Web/HTML/Element#content_sectioning) or "semantic elements" internally (such as `<main>`, `<header>`, `<footer>`, etc.). Instead, it is recommended that you slot in content sectioning elements wherever you feel they're appropriate.
|
||||
|
||||
## Layout Anatomy
|
||||
|
||||
This image depicts the layout's anatomy, including the default positions of each section. The labels represent the [named slots](#slots) you can use to populate them.
|
||||
|
||||
Most slots are optional. Slots that have no content will not be shown, allowing you to opt-in to just the sections of the layout you actually need.
|
||||
|
||||
[](/assets/images/layout.png)
|
||||
|
||||
:::tip
|
||||
If you're not familiar with how slots work in HTML, you might want to [learn more about slots](/getting-started/usage/#slots) before using this component.
|
||||
:::
|
||||
|
||||
## Sticky Sections
|
||||
|
||||
The following sections of the layout are "sticky" by default, meaning they remain in position as the user scrolls.
|
||||
|
||||
- `banner`
|
||||
- `header`
|
||||
- `sub-header`
|
||||
- `aside`
|
||||
- `menu`
|
||||
|
||||
This is often desirable, but you can change this behavior using the `disable-sticky` attribute. Use a space-delimited list of names to tell the layout which sections should not be sticky.
|
||||
|
||||
```html
|
||||
<wa-layout disable-sticky="header aside"> ... </wa-layout>
|
||||
```
|
||||
|
||||
## How to Apply Spacing to Your Layout
|
||||
|
||||
The layout component _does not_ apply spacing for you. You can apply the appropriate paddings or margins directly to the elements you slot in to fine tune your spacing needs.
|
||||
|
||||
TODO - add example here
|
||||
|
||||
When using `<wa-layout>`, make sure to zero out all paddings and margins on `<html>` and `<body>`, otherwise you may see unexpected gaps. The following styles are highly recommended when using `<wa-layout>`.
|
||||
|
||||
```css
|
||||
html,
|
||||
body {
|
||||
min-height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Skip To Content
|
||||
|
||||
The layout provides a "skip to content" link that's visually hidden until the user tabs into it. You don't have to do anything to configure this, unless you want to change the text displayed in the link. In that case, you can slot in your own text using the `skip-to-content` slot.
|
||||
|
||||
This example localizes the "skip to content" link for German users.
|
||||
|
||||
```html
|
||||
<wa-layout>
|
||||
...
|
||||
<span slot="skip-to-content">Zum Inhalt springen</span>
|
||||
...
|
||||
</wa-layout>
|
||||
```
|
||||
|
||||
## Responsiveness
|
||||
|
||||
The layout component tries not to have too many opinions in terms of responsive behaviors — you get to decide with your own CSS and media queries how your content responds! However, the navigation menu _does_ respond by collapsing on smaller screens. The breakpoint at which this occurs is 768px by default, but you can change it using the `mobile-breakpoint` attribute.
|
||||
|
||||
```html
|
||||
<wa-layout mobile-breakpoint="600"> ... </wa-layout>
|
||||
```
|
||||
|
||||
You can provide a button to toggle the navigation menu anywhere inside the layout by adding the `data-toggle-nav` attribute. (This _does not_ have to be a Web Awesome button.)
|
||||
|
||||
```html
|
||||
<wa-layout mobile-breakpoint="600">
|
||||
...
|
||||
<wa-button data-toggle-nav>Menu</wa-button>
|
||||
...
|
||||
</wa-layout>
|
||||
```
|
||||
|
||||
Alternatively, you can apply `nav-state="open"` and `nav-state="closed"` to the layout component to show and hide the navigation, respectively.
|
||||
|
||||
```html
|
||||
<wa-layout nav-state="open"> ... </wa-layout>
|
||||
```
|
||||
|
||||
## Providing Navigation Items
|
||||
|
||||
- TODO - example with navigation items
|
||||
- TODO - example with`<h2>` and `<a>` as navigation items
|
||||
|
||||
## Examples
|
||||
|
||||
### Hero Layout
|
||||
|
||||
- TODO - Sticky header + main + footer
|
||||
|
||||
### Blog Layout
|
||||
|
||||
- TODO - Sticky header + main + aside + footer (blog)
|
||||
|
||||
### App Layout
|
||||
|
||||
- TODO - Menu + main, plus maybe headers and footers in each (app)
|
||||
|
||||
### Docs Layout
|
||||
|
||||
- TODO - Menu + main + aside + footer (docs)
|
||||
89
docs/pages/components/nav-group.md
Normal file
89
docs/pages/components/nav-group.md
Normal file
@@ -0,0 +1,89 @@
|
||||
---
|
||||
meta:
|
||||
title: Nav Group
|
||||
description:
|
||||
layout: component
|
||||
---
|
||||
|
||||
```html:preview
|
||||
<wa-nav-group
|
||||
style="
|
||||
--gap: var(--wa-space-2xl);
|
||||
border: var(--wa-panel-border-width) var(--wa-border-style) var(--wa-color-surface-outline);
|
||||
border-radius: var(--wa-panel-corners);
|
||||
padding: var(--wa-space-square-m);
|
||||
"
|
||||
>
|
||||
<wa-nav-group>
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="search" slot="prefix"></wa-icon>
|
||||
Search
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="bell" slot="prefix"></wa-icon>
|
||||
Notifications
|
||||
</wa-nav-item>
|
||||
</wa-nav-group>
|
||||
|
||||
<wa-nav-group label="Workspace">
|
||||
<wa-nav-item expandable>
|
||||
<div slot="label" style="display: flex; align-items: center; gap: 8px;">
|
||||
<wa-icon name="credit-card"></wa-icon>
|
||||
Payments
|
||||
</div>
|
||||
|
||||
<wa-nav-item href="#" current="page">
|
||||
Transactions
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
Invoices
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
Disputed Charges
|
||||
</wa-nav-item>
|
||||
</wa-nav-item>
|
||||
</wa-nav-group>
|
||||
|
||||
<wa-nav-group label="Reports">
|
||||
<wa-nav-item href="#">
|
||||
Sales
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
Expenses
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
Payroll
|
||||
</wa-nav-item>
|
||||
</wa-nav-group>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="question-circle" slot="prefix"></wa-icon>
|
||||
Help
|
||||
</wa-nav-item>
|
||||
</wa-nav-group>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Nav Group with label
|
||||
|
||||
```html:preview
|
||||
<wa-nav-group label="Workspace">
|
||||
<wa-nav-item href="#" current="page">
|
||||
Transactions
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
Invoices
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
Disputed Charges
|
||||
</wa-nav-item>
|
||||
</wa-nav-group>
|
||||
```
|
||||
232
docs/pages/components/nav-item.md
Normal file
232
docs/pages/components/nav-item.md
Normal file
@@ -0,0 +1,232 @@
|
||||
---
|
||||
meta:
|
||||
title: Nav Item
|
||||
description: |
|
||||
A nav item is intended to be used in a navigation area such as within a nav element in a sidebar or inside of a drawer. A nav item is meant to drive page level navigations.
|
||||
layout: component
|
||||
---
|
||||
|
||||
`wa-nav-item` maps to a `role="listitem"` under the hood and should generally be used as part
|
||||
of a `<wa-nav-group>` for accessibility purposes.
|
||||
|
||||
```html:preview
|
||||
<wa-nav-group>
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="search" slot="prefix"></wa-icon>
|
||||
Search
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="bell" slot="prefix"></wa-icon>
|
||||
Notifications
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<wa-nav-item href="#" current="page">
|
||||
<wa-icon name="house-door" slot="prefix"></wa-icon>
|
||||
Home
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="music-note-list" slot="prefix"></wa-icon>
|
||||
Playlists
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="file-earmark-music" slot="prefix"></wa-icon>
|
||||
Tracks
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="gear" slot="prefix"></wa-icon>
|
||||
Settings
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="question-circle" slot="prefix"></wa-icon>
|
||||
Help
|
||||
</wa-nav-item>
|
||||
</wa-nav-group>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaNavGroup from '@shoelace-style/shoelace/dist/react/nav-group';
|
||||
import WaNavItem from '@shoelace-style/shoelace/dist/react/nav-item';
|
||||
import WaDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||
import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
<WaNavGroup>
|
||||
<WaNavItem href="#">
|
||||
<WaIcon name="search" slot="prefix" />
|
||||
Search
|
||||
</WaNavItem>
|
||||
|
||||
<WaNavItem href="#">
|
||||
<WaIcon name="bell" slot="prefix" />
|
||||
Notifications
|
||||
</WaNavItem>
|
||||
|
||||
<WaDivider></WaDivider>
|
||||
|
||||
<WaNavItem href="#" current="page">
|
||||
<WaIcon name="house-door" slot="prefix" />
|
||||
Home
|
||||
</WaNavItem>
|
||||
|
||||
<WaNavItem href="#">
|
||||
<WaIcon name="music-note-list" slot="prefix" />
|
||||
Playlists
|
||||
</WaNavItem>
|
||||
|
||||
<WaNavItem href="#">
|
||||
<WaIcon name="file-earmark-music" slot="prefix" />
|
||||
Tracks
|
||||
</WaNavItem>
|
||||
|
||||
<WaNavItem href="#">
|
||||
<WaIcon name="gear" slot="prefix" />
|
||||
Settings
|
||||
</WaNavItem>
|
||||
|
||||
<WaNavItem href="#">
|
||||
<WaIcon name="question-circle" slot="prefix" />
|
||||
Help
|
||||
</WaNavItem>
|
||||
</WaNavGroup>
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
## Examples
|
||||
|
||||
### Active nav item
|
||||
|
||||
Set a `<wa-nav-item>` to active using the `current` string attribute. Doing so will map to `aria-current="<string>"` under the hood. If your nav-items are intended for full page navigations, it is recommended to use `current="page"`. If your nav-items are, for example, just the active item within a list, but do not perform full page navigation (such as with table of contents) then use `current="true"`.
|
||||
|
||||
```html:preview
|
||||
<wa-nav-item href="#" current="page">
|
||||
Active Nav Item
|
||||
</wa-nav-item>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaNavItem from '@shoelace-style/shoelace/dist/react/nav-item';
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<WaNavItem href="#" current="page">
|
||||
Active Nav Item
|
||||
</WaNavItem>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Icon only
|
||||
|
||||
```html:preview
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon name="house-door" label="Home"></wa-icon>
|
||||
</wa-nav-item>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaNavItem from '@shoelace-style/shoelace/dist/react/nav-item';
|
||||
import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<WaNavItem href="#">
|
||||
<WaIcon name="house-door" label="Home" />
|
||||
</WaNavItem>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Nav items with prefix + suffix
|
||||
|
||||
```html:preview
|
||||
<wa-nav-item href="#">
|
||||
<wa-icon slot="prefix" name="link-45deg"></wa-icon>
|
||||
Nav Item
|
||||
<wa-icon slot="suffix" name="box-arrow-up-right"></wa-icon>
|
||||
</wa-nav-item>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaNavItem from '@shoelace-style/shoelace/dist/react/nav-item';
|
||||
import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<WaNavItem href="#">
|
||||
<WaIcon slot="prefix" name="link-45deg" />
|
||||
Nav Item
|
||||
<WaIcon slot="suffix" name="box-arrow-up-right" />
|
||||
</WaNavItem>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Nav group with nested nav items
|
||||
|
||||
Use the `expandable` attribute to mark a nav item as expandable. In addition,
|
||||
you can add a `label` slot or attribute to display in the `<wa-details>` element prior
|
||||
to expanding the nav group.
|
||||
|
||||
```html:preview
|
||||
<wa-nav-item expandable>
|
||||
<div slot="label" style="display: flex; align-items: center; gap: 8px;">
|
||||
<wa-icon name="credit-card"></wa-icon>
|
||||
Payments
|
||||
</div>
|
||||
|
||||
<wa-nav-item href="#" current="page">
|
||||
Transactions
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
Invoices
|
||||
</wa-nav-item>
|
||||
|
||||
<wa-nav-item href="#">
|
||||
Disputed Charges
|
||||
</wa-nav-item>
|
||||
</wa-nav-group>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaNavItem from '@shoelace-style/shoelace/dist/react/nav-item';
|
||||
|
||||
<WaNavItem expandable label="Payments">
|
||||
<WaNavItem href="#" current="page">
|
||||
Transactions
|
||||
</WaNavItem>
|
||||
|
||||
<WaNavItem href="#">
|
||||
Invoices
|
||||
</WaNavItem>
|
||||
|
||||
<WaNavItem href="#">
|
||||
Disputed Charges
|
||||
</WaNavItem>
|
||||
</WaNavGroup>
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
@@ -87,26 +87,26 @@ const App = () => (
|
||||
Use the `size` attribute to change a radio button's size.
|
||||
|
||||
```html:preview
|
||||
<wa-radio-group label="Select an option" name="a" value="1">
|
||||
<wa-radio-button size="small" value="1">Option 1</wa-radio-button>
|
||||
<wa-radio-button size="small" value="2">Option 2</wa-radio-button>
|
||||
<wa-radio-button size="small" value="3">Option 3</wa-radio-button>
|
||||
<wa-radio-group size="small" label="Select an option" name="a" value="1">
|
||||
<wa-radio-button value="1">Option 1</wa-radio-button>
|
||||
<wa-radio-button value="2">Option 2</wa-radio-button>
|
||||
<wa-radio-button value="3">Option 3</wa-radio-button>
|
||||
</wa-radio-group>
|
||||
|
||||
<br />
|
||||
|
||||
<wa-radio-group label="Select an option" name="a" value="1">
|
||||
<wa-radio-button size="medium" value="1">Option 1</wa-radio-button>
|
||||
<wa-radio-button size="medium" value="2">Option 2</wa-radio-button>
|
||||
<wa-radio-button size="medium" value="3">Option 3</wa-radio-button>
|
||||
<wa-radio-group size="medium" label="Select an option" name="a" value="1">
|
||||
<wa-radio-button value="1">Option 1</wa-radio-button>
|
||||
<wa-radio-button value="2">Option 2</wa-radio-button>
|
||||
<wa-radio-button value="3">Option 3</wa-radio-button>
|
||||
</wa-radio-group>
|
||||
|
||||
<br />
|
||||
|
||||
<wa-radio-group label="Select an option" name="a" value="1">
|
||||
<wa-radio-button size="large" value="1">Option 1</wa-radio-button>
|
||||
<wa-radio-button size="large" value="2">Option 2</wa-radio-button>
|
||||
<wa-radio-button size="large" value="3">Option 3</wa-radio-button>
|
||||
<wa-radio-group size="large" label="Select an option" name="a" value="1">
|
||||
<wa-radio-button value="1">Option 1</wa-radio-button>
|
||||
<wa-radio-button value="2">Option 2</wa-radio-button>
|
||||
<wa-radio-button value="3">Option 3</wa-radio-button>
|
||||
</wa-radio-group>
|
||||
```
|
||||
|
||||
@@ -115,26 +115,26 @@ import WaRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';
|
||||
import WaRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
|
||||
|
||||
const App = () => (
|
||||
<WaRadioGroup label="Select an option" name="a" value="1">
|
||||
<WaRadioButton size="small" value="1">Option 1</WaRadioButton>
|
||||
<WaRadioButton size="small" value="2">Option 2</WaRadioButton>
|
||||
<WaRadioButton size="small" value="3">Option 3</WaRadioButton>
|
||||
<WaRadioGroup size="small" label="Select an option" name="a" value="1">
|
||||
<WaRadioButton value="1">Option 1</WaRadioButton>
|
||||
<WaRadioButton value="2">Option 2</WaRadioButton>
|
||||
<WaRadioButton value="3">Option 3</WaRadioButton>
|
||||
</WaRadioGroup>
|
||||
|
||||
<br />
|
||||
|
||||
<WaRadioGroup label="Select an option" name="a" value="1">
|
||||
<WaRadioButton size="medium" value="1">Option 1</WaRadioButton>
|
||||
<WaRadioButton size="medium" value="2">Option 2</WaRadioButton>
|
||||
<WaRadioButton size="medium" value="3">Option 3</WaRadioButton>
|
||||
<WaRadioGroup size="medium" label="Select an option" name="a" value="1">
|
||||
<WaRadioButton value="1">Option 1</WaRadioButton>
|
||||
<WaRadioButton value="2">Option 2</WaRadioButton>
|
||||
<WaRadioButton value="3">Option 3</WaRadioButton>
|
||||
</WaRadioGroup>
|
||||
|
||||
<br />
|
||||
|
||||
<WaRadioGroup label="Select an option" name="a" value="1">
|
||||
<WaRadioButton size="large" value="1">Option 1</WaRadioButton>
|
||||
<WaRadioButton size="large" value="2">Option 2</WaRadioButton>
|
||||
<WaRadioButton size="large" value="3">Option 3</WaRadioButton>
|
||||
<WaRadioGroup size="large" label="Select an option" name="a" value="1">
|
||||
<WaRadioButton value="1">Option 1</WaRadioButton>
|
||||
<WaRadioButton value="2">Option 2</WaRadioButton>
|
||||
<WaRadioButton value="3">Option 3</WaRadioButton>
|
||||
</WaRadioGroup>
|
||||
);
|
||||
```
|
||||
@@ -144,26 +144,26 @@ const App = () => (
|
||||
Use the `pill` attribute to give radio buttons rounded edges.
|
||||
|
||||
```html:preview
|
||||
<wa-radio-group label="Select an option" name="a" value="1">
|
||||
<wa-radio-button pill size="small" value="1">Option 1</wa-radio-button>
|
||||
<wa-radio-button pill size="small" value="2">Option 2</wa-radio-button>
|
||||
<wa-radio-button pill size="small" value="3">Option 3</wa-radio-button>
|
||||
<wa-radio-group size="small" label="Select an option" name="a" value="1">
|
||||
<wa-radio-button pill value="1">Option 1</wa-radio-button>
|
||||
<wa-radio-button pill value="2">Option 2</wa-radio-button>
|
||||
<wa-radio-button pill value="3">Option 3</wa-radio-button>
|
||||
</wa-radio-group>
|
||||
|
||||
<br />
|
||||
|
||||
<wa-radio-group label="Select an option" name="a" value="1">
|
||||
<wa-radio-button pill size="medium" value="1">Option 1</wa-radio-button>
|
||||
<wa-radio-button pill size="medium" value="2">Option 2</wa-radio-button>
|
||||
<wa-radio-button pill size="medium" value="3">Option 3</wa-radio-button>
|
||||
<wa-radio-group size="medium" label="Select an option" name="a" value="1">
|
||||
<wa-radio-button pill value="1">Option 1</wa-radio-button>
|
||||
<wa-radio-button pill value="2">Option 2</wa-radio-button>
|
||||
<wa-radio-button pill value="3">Option 3</wa-radio-button>
|
||||
</wa-radio-group>
|
||||
|
||||
<br />
|
||||
|
||||
<wa-radio-group label="Select an option" name="a" value="1">
|
||||
<wa-radio-button pill size="large" value="1">Option 1</wa-radio-button>
|
||||
<wa-radio-button pill size="large" value="2">Option 2</wa-radio-button>
|
||||
<wa-radio-button pill size="large" value="3">Option 3</wa-radio-button>
|
||||
<wa-radio-group size="large" label="Select an option" name="a" value="1">
|
||||
<wa-radio-button pill value="1">Option 1</wa-radio-button>
|
||||
<wa-radio-button pill value="2">Option 2</wa-radio-button>
|
||||
<wa-radio-button pill value="3">Option 3</wa-radio-button>
|
||||
</wa-radio-group>
|
||||
```
|
||||
|
||||
@@ -172,26 +172,26 @@ import WaRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';
|
||||
import WaRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
|
||||
|
||||
const App = () => (
|
||||
<WaRadioGroup label="Select an option" name="a" value="1">
|
||||
<WaRadioButton pill size="small" value="1">Option 1</WaRadioButton>
|
||||
<WaRadioButton pill size="small" value="2">Option 2</WaRadioButton>
|
||||
<WaRadioButton pill size="small" value="3">Option 3</WaRadioButton>
|
||||
<WaRadioGroup size="small" label="Select an option" name="a" value="1">
|
||||
<WaRadioButton pill value="1">Option 1</WaRadioButton>
|
||||
<WaRadioButton pill value="2">Option 2</WaRadioButton>
|
||||
<WaRadioButton pill value="3">Option 3</WaRadioButton>
|
||||
</WaRadioGroup>
|
||||
|
||||
<br />
|
||||
|
||||
<WaRadioGroup label="Select an option" name="a" value="1">
|
||||
<WaRadioButton pill size="medium" value="1">Option 1</WaRadioButton>
|
||||
<WaRadioButton pill size="medium" value="2">Option 2</WaRadioButton>
|
||||
<WaRadioButton pill size="medium" value="3">Option 3</WaRadioButton>
|
||||
<WaRadioGroup size="medium" label="Select an option" name="a" value="1">
|
||||
<WaRadioButton pill value="1">Option 1</WaRadioButton>
|
||||
<WaRadioButton pill value="2">Option 2</WaRadioButton>
|
||||
<WaRadioButton pill value="3">Option 3</WaRadioButton>
|
||||
</WaRadioGroup>
|
||||
|
||||
<br />
|
||||
|
||||
<WaRadioGroup label="Select an option" name="a" value="1">
|
||||
<WaRadioButton pill size="large" value="1">Option 1</WaRadioButton>
|
||||
<WaRadioButton pill size="large" value="2">Option 2</WaRadioButton>
|
||||
<WaRadioButton pill size="large" value="3">Option 3</WaRadioButton>
|
||||
<WaRadioGroup size="large" label="Select an option" name="a" value="1">
|
||||
<WaRadioButton pill value="1">Option 1</WaRadioButton>
|
||||
<WaRadioButton pill value="2">Option 2</WaRadioButton>
|
||||
<WaRadioButton pill value="3">Option 3</WaRadioButton>
|
||||
</WaRadioGroup>
|
||||
);
|
||||
```
|
||||
|
||||
52
docs/pages/experimental/form-validation.md
Normal file
52
docs/pages/experimental/form-validation.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
meta:
|
||||
title: Form Control Validation
|
||||
description: TODO
|
||||
---
|
||||
|
||||
# Form Control Validation
|
||||
|
||||
Adding the `wa-valid` or `wa-invalid` class to a form control will change its appearance. This is useful for applying validation styles to server-rendered form controls.
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
|
||||
<div>
|
||||
<h3>Valid</h3>
|
||||
<wa-input class="wa-valid" label="Name" help-text="Just a first name is fine" placeholder="Enter your name"></wa-input><br>
|
||||
<wa-select class="wa-valid" label="Choose one" help-text="Make a choice already">
|
||||
<wa-option>There can be only one!</wa-option>
|
||||
<wa-option>Well, maybe two is OK</wa-option>
|
||||
</wa-select>
|
||||
<wa-textarea class="wa-valid" label="Bio" help-text="Tell us about yourself" placeholder="Enter a bio"></wa-textarea><br>
|
||||
<wa-range class="wa-valid" value="50" label="Volume" help-text="Crank it up"></wa-range><br>
|
||||
<wa-checkbox class="wa-valid" checked>I am awesome</wa-checkbox><br>
|
||||
<wa-checkbox class="wa-valid">So am I</wa-checkbox><br><br>
|
||||
<wa-switch class="wa-valid" checked>Still awesome</wa-switch><br>
|
||||
<wa-switch class="wa-valid">More awesome</wa-switch><br><br>
|
||||
<wa-radio-group class="wa-valid" label="Select an option" name="a" value="1">
|
||||
<wa-radio value="1">Option 1</wa-radio>
|
||||
<wa-radio value="2">Option 2</wa-radio>
|
||||
<wa-radio value="3">Option 3</wa-radio>
|
||||
</wa-radio-group><br>
|
||||
<wa-button variant="brand">Submit Form</wa-button>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Invalid</h3>
|
||||
<wa-input class="wa-invalid" label="Name" help-text="Just a first name is fine" placeholder="Enter your name"></wa-input><br>
|
||||
<wa-select class="wa-invalid" label="Choose one" help-text="Make a choice already">
|
||||
<wa-option>There can be only one!</wa-option>
|
||||
<wa-option>Well, maybe two is OK</wa-option>
|
||||
</wa-select>
|
||||
<wa-textarea class="wa-invalid" label="Bio" help-text="Tell us about yourself" placeholder="Enter a bio"></wa-textarea><br>
|
||||
<wa-range class="wa-invalid" value="50" label="Volume" help-text="Crank it up"></wa-range><br>
|
||||
<wa-checkbox class="wa-invalid" checked>I am awesome</wa-checkbox><br>
|
||||
<wa-checkbox class="wa-invalid">So am I</wa-checkbox><br><br>
|
||||
<wa-switch class="wa-invalid" checked>Still awesome</wa-switch><br>
|
||||
<wa-switch class="wa-invalid">More awesome</wa-switch><br><br>
|
||||
<wa-radio-group class="wa-invalid" label="Select an option" name="a" value="1">
|
||||
<wa-radio value="1">Option 1</wa-radio>
|
||||
<wa-radio value="2">Option 2</wa-radio>
|
||||
<wa-radio value="3">Option 3</wa-radio>
|
||||
</wa-radio-group><br>
|
||||
<wa-button variant="brand">Submit Form</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -160,12 +160,3 @@ export function thing() {
|
||||
<dt>Definition 3</dt>
|
||||
<dd>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.</dd>
|
||||
</dl>
|
||||
|
||||
## Forms
|
||||
|
||||
<fieldset>
|
||||
<legend>Fieldset</legend>
|
||||
<label><input type="radio" name="gender" value="Male"> Option 1</label><br>
|
||||
<label><input type="radio" name="gender" value="Female"> Option 2</label><br>
|
||||
<label><input type="radio" name="gender" value="other-none-na"> Option 3</label>
|
||||
</fieldset>
|
||||
|
||||
@@ -2,6 +2,549 @@
|
||||
meta:
|
||||
title: Themer
|
||||
description: TODO
|
||||
toc: false
|
||||
---
|
||||
|
||||
# Themer Goes Here
|
||||
<!-- Knobs -->
|
||||
<div id="knobs">
|
||||
<div class="space-vertically">
|
||||
<a href="/">{% include 'logo.njk' %}</a>
|
||||
<wa-select name="theme" label="Theme" value="default">
|
||||
<wa-option value="default">Default</wa-option>
|
||||
<wa-option value="glassy">Glassy</wa-option>
|
||||
<wa-option value="mellow">Mellow</wa-option>
|
||||
<wa-option value="playful">Playful</wa-option>
|
||||
</wa-select>
|
||||
<wa-select name="heading-text" label="Heading" value="">
|
||||
<wa-option value="">Theme default</wa-option>
|
||||
<wa-option value="serif">Serif</wa-option>
|
||||
<wa-option value="sans-serif">Sans-serif</wa-option>
|
||||
<wa-option value="monospace">Monospace</wa-option>
|
||||
<wa-option value="cursive">Cursive</wa-option>
|
||||
</wa-select>
|
||||
<wa-select name="body-text" label="Body" value="">
|
||||
<wa-option value="">Theme default</wa-option>
|
||||
<wa-option value="serif">Serif</wa-option>
|
||||
<wa-option value="sans-serif">Sans-serif</wa-option>
|
||||
<wa-option value="monospace">Monospace</wa-option>
|
||||
<wa-option value="cursive">Cursive</wa-option>
|
||||
</wa-select>
|
||||
<wa-select name="border-style" label="Border Style" value="solid">
|
||||
<wa-option value="solid">Solid</wa-option>
|
||||
<wa-option value="dashed">Dashed</wa-option>
|
||||
<wa-option value="dotted">Dotted</wa-option>
|
||||
<wa-option value="double">Double</wa-option>
|
||||
</wa-select>
|
||||
<wa-range name="border-width" label="Border Width" min="1" max="5" value="1" step="1" tooltip="none"></wa-range>
|
||||
<wa-range name="spacing" label="Spacing" min=".5" max="1.5" value="1" step="0.125" tooltip="none"></wa-range>
|
||||
<wa-range name="corners" label="Corners" min="0" max="1.5" value=".25" step=".125" tooltip="none"></wa-range>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const container = document.getElementById('knobs');
|
||||
const themeStylesheet = document.getElementById('theme-stylesheet');
|
||||
|
||||
// Theme
|
||||
container.querySelector('[name="theme"]').addEventListener('wa-change', event => {
|
||||
themeStylesheet.href = `/dist/themes/${event.target.value}.css`;
|
||||
});
|
||||
|
||||
// Heading text
|
||||
container.querySelector('[name="heading-text"]').addEventListener('wa-input', event => {
|
||||
document.documentElement.style.setProperty('--wa-font-family-heading', event.target.value);
|
||||
});
|
||||
|
||||
// Body text
|
||||
container.querySelector('[name="body-text"]').addEventListener('wa-input', event => {
|
||||
document.documentElement.style.setProperty('--wa-font-family-body', event.target.value);
|
||||
});
|
||||
|
||||
// Corners
|
||||
container.querySelector('[name="corners"]').addEventListener('wa-input', event => {
|
||||
document.documentElement.style.setProperty('--wa-corners-base', `${event.target.value}rem`);
|
||||
});
|
||||
|
||||
// Border width
|
||||
container.querySelector('[name="border-width"]').addEventListener('wa-input', event => {
|
||||
document.documentElement.style.setProperty('--wa-border-width-base', `${event.target.value / 16}rem`);
|
||||
});
|
||||
|
||||
// Border style
|
||||
container.querySelector('[name="border-style"]').addEventListener('wa-input', event => {
|
||||
document.documentElement.style.setProperty('--wa-border-style', event.target.value);
|
||||
});
|
||||
|
||||
// Spacing style
|
||||
container.querySelector('[name="spacing"]').addEventListener('wa-input', event => {
|
||||
document.documentElement.style.setProperty('--wa-space-base', `${event.target.value}rem`);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--knobs-width: 300px;
|
||||
}
|
||||
|
||||
#knobs {
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
top: 2rem;
|
||||
left: 2rem;
|
||||
background: var(--wa-color-surface-default);
|
||||
border: var(--wa-border-style) var(--wa-border-width-thin) var(--wa-color-surface-outline);
|
||||
border-radius: var(--wa-corners-2x);
|
||||
box-shadow: var(--wa-shadow-level-2);
|
||||
width: var(--knobs-width);
|
||||
padding: 2rem;
|
||||
margin-inline: auto;
|
||||
margin-block: 0 4rem;
|
||||
}
|
||||
|
||||
#knobs p {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Preview -->
|
||||
<div class="preview-container">
|
||||
<section class="overlap">
|
||||
<h1>Make it Awesome</h1>
|
||||
<wa-card>
|
||||
<div class="grid">
|
||||
<svg class="image" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<defs>
|
||||
<linearGradient id="linear-gradient" x1="100" y1="166.04" x2="100" y2="0" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" class="gradient-start" stop-color="#f78ca8"/>
|
||||
<stop offset="1" class="gradient-stop" stop-color="#9556d9"/>
|
||||
</linearGradient>
|
||||
<style>
|
||||
.cls-4{stroke-width:0;fill:#3c2358}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="bespin">
|
||||
<path id="sky" d="M171.75 15.52c1.99-.3 3.87 0 5.6 1.07.31.2.45.15.65-.16 2.11-3.3 5.04-5.48 8.91-6.23 4.78-.93 8.95.38 12.48 3.73.2.19.41.37.61.56 0-4.67 0-9.34.01-14.01-.01-.39-.1-.48-.49-.48C133.02.01 66.51 0 0 0v78.43c.31.03.46.29.66.46 6.02 5.38 9.65 12.05 10.81 20.05.12.82.19 1.65.24 2.48.7-.3 1.27-.82 1.93-1.2 2.12-1.21 4.37-1.99 6.82-2.23 4.52-.44 8.61.69 12.21 3.44 3.69 2.82 5.9 6.56 6.6 11.17.15.99.21 2 .2 3 0 .26.02.46.32.57 2.14.78 4 1.98 5.6 3.61.04.04.08.06.13.09.11.11.21.22.32.32.66.84 1.33 1.68 1.87 2.62 1.63 2.87 2.34 5.93 2.03 9.23-.09.92-.33 1.8-.46 2.71v.22c1.01-.42 2.01-.84 3.08-1.11 2.26-.59 4.54-.71 6.85-.37 2.95.43 5.62 1.53 7.96 3.35 3.66 2.85 5.9 6.57 6.59 11.18.15.99.21 2 .2 3 0 .26.02.46.32.57 2.14.78 4 1.98 5.59 3.61.04.04.08.06.13.09l.32.32c.66.84 1.33 1.68 1.87 2.62 1.3 2.27 2.03 4.7 2.09 7.32.01.56.01.57.48.29 3.5-2.11 7.27-3.5 11.3-4.15 1.49-.24 2.99-.38 4.5-.34.5.01.55-.14.39-.55-.73-1.9-1.02-3.88-.94-5.91.14-3.46 1.33-6.53 3.52-9.2 1.73-2.12 3.9-3.62 6.44-4.61.22-.09.36-.17.35-.43-.04-1.69.17-3.35.54-5-3.53-.63-7.06-1.27-10.59-1.89-4.64-.82-9.29-1.63-13.93-2.45-4.27-.75-8.53-1.51-12.8-2.26-.72-.13-.72-.12-.72-.85v-2.5c0-.26.07-.45.24-.65a52.26 52.26 0 0 1 15.31-11.92c.83-.43 1.69-.82 2.52-1.25.21-.11.42-.12.65-.12 1.35 0 2.71-.03 4.06.01.62.02 1.14-.16 1.65-.45 1.73-.99 3.47-1.96 5.26-2.83 3.11-1.52 6.4-2.45 9.83-2.87.32-.04.41-.17.4-.46v-2.71c0-.29.08-.4.38-.39 1.18.01 2.36.01 3.54 0 .29 0 .35.11.34.36-.01.75 0 1.49-.01 2.24 0 .32.05.45.42.44 1.06-.04.95.1.95-.96 0-1.42.01-2.85 0-4.27 0-.36.09-.48.46-.47.87.03 1.74.02 2.6 0 .36 0 .58.06.51.48-.06.36.09.5.46.47.33-.03.66.01.99 0 .32-.02.42.09.42.41-.02 1.21.01 2.43-.02 3.64 0 .4.12.54.5.48.1-.02.21-.01.31 0 .43.05.58-.12.57-.56-.02-1.96 0-3.92-.02-5.88 0-.43.11-.56.55-.55 1.08.03 2.15.03 3.23 0 .38 0 .5.09.49.48-.01 2.1 0 4.2 0 6.3.02.03.05.05.07.08q1.26.08 1.26-1.13c0-.69.02-1.39 0-2.08-.01-.35.11-.44.44-.43 1.68.02 3.37.02 5.05 0 .36 0 .48.09.47.46-.03.97 0 1.94-.01 2.91 0 .62.48.44.8.5.37.07.57-.06.56-.5-.02-1.65 0-3.3-.02-4.94 0-.37.07-.53.48-.5.55.04 1.11.03 1.66 0 .35-.02.44.1.44.44-.01 1.68 0 3.37-.02 5.05 0 .45.1.59.56.59.8 0 .8.03.8-.79v-1.56c0-.25.09-.34.33-.33.73 0 1.46.01 2.19 0 .32 0 .37.15.37.42v2.24c0 .29.06.43.4.44.41 0 .82.11 1.22.17.13-.28.41-.36.65-.49 2.41-1.34 4.98-2.17 7.73-2.42.61-.05 1.21-.08 1.82-.08.63 0 .7-.04.72-.72.04-1.01.18-2 .4-2.98.73-3.42 2.19-6.49 4.48-9.15 2.94-3.42 6.6-5.63 11.05-6.43 4.65-.83 9 .04 13.01 2.53 2.81 1.74 5.01 4.09 6.64 6.96.19.33.33.46.71.2 1.89-1.34 4.03-1.88 6.32-1.86V48.36H167.6a11.05 11.05 0 0 1-10.92-9.3c-.87-5.46 2.69-10.87 8.09-12.24.49-.12.63-.24.45-.78-1.54-4.7 1.62-9.76 6.52-10.5ZM61.4 62c-2.25 2.53-5.1 3.74-8.48 3.74-10.4 0-20.8-.01-31.19 0-3.73 0-6.89-1.3-9.42-4.03-1.78-1.92-2.83-4.2-3.15-6.83-.39-3.28.46-6.21 2.37-8.88.27-.38.67-.66.88-1.09l.53-.53c.97-.79 1.93-1.62 3.11-2.06.89-.33 1.35-.75 1.32-1.79-.03-1.58.32-3.14.95-4.6 2.02-4.69 5.5-7.65 10.57-8.42 5.92-.9 10.54 1.37 13.85 6.32.18.27.3.34.61.16 4.59-2.69 10.17-.74 12.06 4.23.66 1.75.66 3.53.06 5.29-.13.39-.06.52.34.63 4.51 1.28 7.33 4.21 8.18 8.82.62 3.38-.3 6.44-2.6 9.02Z" stroke-width="0" fill="url(#linear-gradient)"/>
|
||||
<path id="city" class="cls-4" d="M179.31 119.35a51.77 51.77 0 0 0-11.3-7.13c-.48-.22-.93-.48-1.52-.47-1.44.04-2.88.01-4.32.02-.25 0-.47-.04-.7-.16-1.47-.81-2.95-1.61-4.42-2.41-3.32-1.8-6.85-2.99-10.59-3.5-.41-.06-.81-.16-1.22-.17-.34 0-.41-.14-.4-.44.01-.75 0-1.49 0-2.24 0-.27-.05-.42-.37-.42-.73.02-1.46.01-2.19 0-.25 0-.34.09-.33.33.01.52 0 1.04 0 1.56 0 .82 0 .79-.8.79-.47 0-.57-.15-.56-.59.03-1.68 0-3.37.02-5.05 0-.34-.09-.46-.44-.44-.55.03-1.11.03-1.66 0-.41-.03-.49.13-.48.5.02 1.65 0 3.3.02 4.94 0 .44-.19.57-.56.5-.32-.06-.81.12-.8-.5.01-.97-.01-1.94.01-2.91 0-.37-.11-.46-.47-.46-1.68.02-3.37.02-5.05 0-.33 0-.46.09-.44.43.03.69 0 1.39 0 2.08q0 1.22-1.26 1.13s-.05-.05-.07-.08c0-2.1-.02-4.2 0-6.3 0-.4-.11-.49-.49-.48-1.08.03-2.15.03-3.23 0-.43-.01-.55.11-.55.55.02 1.96 0 3.92.02 5.88 0 .44-.15.61-.57.56-.1-.01-.21-.02-.31 0-.38.06-.51-.08-.5-.48.03-1.21 0-2.43.02-3.64 0-.32-.1-.43-.42-.41-.33.02-.66-.02-.99 0-.37.03-.52-.11-.46-.47.07-.42-.15-.48-.51-.48-.87.02-1.74.02-2.6 0-.37-.01-.47.11-.46.47.02 1.42 0 2.85 0 4.27 0 1.06.11.92-.95.96-.37.01-.43-.12-.42-.44.02-.75 0-1.49.01-2.24 0-.26-.06-.37-.34-.36-1.18.01-2.36.01-3.54 0-.3 0-.38.1-.38.39.01.9 0 1.8 0 2.71 0 .29-.08.42-.4.46-3.43.42-6.72 1.35-9.83 2.87-1.79.87-3.53 1.84-5.26 2.83-.51.29-1.03.48-1.65.45-1.35-.05-2.71-.01-4.06-.01-.22 0-.44.01-.65.12-.84.43-1.69.82-2.52 1.25a52.39 52.39 0 0 0-15.31 11.92c-.18.2-.25.39-.24.65.01.83 0 1.67 0 2.5 0 .73 0 .72.72.85 4.27.75 8.53 1.51 12.8 2.26 4.64.82 9.29 1.63 13.93 2.45l10.59 1.89c.89.15 1.77.31 2.66.46.53.09 1.02.19 1.49.52 3.96 2.84 7.93 5.66 11.9 8.48.33.24.46.48.46.89-.01 6.16 0 12.32 0 18.49 1.19-.05 2.38.05 3.56.07 0-.66.02-1.32.02-1.98v-16.66c0-.36.09-.59.4-.81 3.96-2.8 7.92-5.61 11.86-8.45.68-.49 1.45-.52 2.21-.66 4.02-.72 8.05-1.42 12.08-2.13.12-.19.24-.38.35-.57 2.78-4.5 6.79-7.02 12.07-7.48 2.11-.18 4.13.21 6.1.91.39.14.5.09.58-.33.28-1.36.75-2.66 1.31-3.93.46-1.03 1.07-1.97 1.61-2.96-.07-.05-.15-.09-.22-.14Z"/>
|
||||
<path id="forefront_clouds" data-name="forefront clouds" d="M0 78.44c.31.03.46.29.66.46 6.02 5.38 9.65 12.05 10.81 20.05.12.82.19 1.65.24 2.48.08 1.35.06 2.7-.02 4.05-.2 3.44-.96 6.75-2.23 9.96-.65 1.65-1.45 3.23-2.36 4.75-.28.47-.26.49.27.54.47.04.93.08 1.39.17 4.47.86 7.92 3.23 10.33 7.08 1.51 2.41 2.21 5.08 2.23 7.91 0 1.73-.35 3.41-.96 5.03-.13.36-.1.49.31.58 4.47 1.04 8.2 3.32 11.16 6.83 2.86 3.39 4.42 7.32 4.69 11.72.21 3.46-.46 6.77-1.99 9.89-.25.51-.18.77.24 1.12 2.2 1.82 4.02 3.97 5.46 6.43.23.39.38.36.7.14 3.29-2.16 6.92-2.9 10.74-2.16 6.1 1.18 10.12 4.82 12.04 10.74.18.55.17.55.65.19 1.39-1.07 2.85-2.04 4.4-2.87 5.52-2.95 11.39-4.27 17.65-3.87 2.72.17 5.37.67 7.95 1.5a31.63 31.63 0 0 1 8.88 4.5c.46.33.51.31.6-.16 1.16-6.42 4.3-11.66 9.42-15.69 3.29-2.59 7.03-4.22 11.16-4.95.97-.17 1.96-.22 2.93-.38 1.19-.05 2.38.05 3.56.07 5.54.48 10.39 2.61 14.66 6.27.13-1.32.2-2.53.43-3.73 1.46-7.48 5.45-13.2 11.93-17.2.39-.24.46-.36.19-.76-2.18-3.3-2.78-6.93-2.15-10.8.25-1.57.82-3.04 1.5-4.47.12-.19.24-.38.35-.57 2.78-4.5 6.79-7.02 12.07-7.48 2.11-.18 4.13.21 6.1.91.39.14.5.09.58-.33.28-1.36.75-2.66 1.31-3.93.46-1.03 1.07-1.97 1.61-2.96.08-.09.17-.17.24-.27 1.04-1.51 2.28-2.84 3.68-4.01 3.67-3.05 7.89-4.61 12.66-4.71 1.31-.03 2.59.17 3.88.32v88.68c0 .37-.05.49-.47.49-66.36-.01-132.71-.01-199.07 0-.42 0-.46-.13-.46-.5V78.43Z" fill="#f9b4d9" stroke-width="0"/>
|
||||
<path id="upper_clouds" data-name="upper clouds" d="M199.99 48.34H167.6a11.05 11.05 0 0 1-10.92-9.3c-.87-5.46 2.69-10.87 8.09-12.24.49-.12.63-.24.45-.78-1.54-4.7 1.62-9.76 6.52-10.5 1.99-.3 3.87 0 5.6 1.07.31.2.45.15.65-.16 2.11-3.3 5.04-5.48 8.91-6.23 4.78-.93 8.95.38 12.48 3.73.2.19.41.37.61.56v33.85ZM12.41 44.92c-.21.43-.61.71-.88 1.09-1.91 2.66-2.76 5.59-2.37 8.88.31 2.63 1.37 4.91 3.15 6.83 2.53 2.73 5.68 4.04 9.42 4.03 10.4-.02 20.8 0 31.19 0 3.38 0 6.23-1.2 8.48-3.74 2.3-2.59 3.22-5.64 2.6-9.02-.85-4.61-3.68-7.53-8.18-8.82-.4-.11-.48-.24-.34-.63.6-1.77.6-3.55-.06-5.29-1.89-4.96-7.46-6.91-12.06-4.23-.31.18-.43.11-.61-.16-3.32-4.95-7.93-7.22-13.85-6.32-5.07.77-8.55 3.72-10.57 8.42-.63 1.47-.98 3.02-.95 4.6.02 1.05-.43 1.46-1.32 1.79-1.18.44-2.14 1.27-3.11 2.06l-.53.53Z" fill="#eeb6e5" stroke-width="0"/>
|
||||
<path id="background_clouds" data-name="background clouds" d="M199.99 110.83c-1.29-.15-2.57-.34-3.88-.32-4.77.09-8.99 1.66-12.66 4.71-1.41 1.17-2.64 2.5-3.68 4.01-.07.1-.16.18-.24.27-.07-.05-.15-.09-.22-.14a51.77 51.77 0 0 0-11.3-7.13c-.48-.22-.93-.48-1.52-.47-1.44.04-2.88.01-4.32.02-.25 0-.47-.04-.7-.16-1.47-.81-2.95-1.61-4.42-2.41-3.32-1.8-6.85-2.99-10.59-3.5.13-.28.41-.36.65-.49 2.41-1.34 4.98-2.17 7.73-2.42.61-.05 1.21-.08 1.82-.08.63 0 .7-.04.72-.72.04-1.01.18-2 .4-2.98.73-3.42 2.19-6.49 4.48-9.15 2.94-3.42 6.6-5.63 11.05-6.43 4.65-.83 9 .04 13.01 2.53 2.81 1.74 5.01 4.09 6.64 6.96.19.33.33.46.71.2 1.89-1.34 4.03-1.88 6.32-1.86v19.58Zm-72.62 35.16c0-.41-.12-.65-.46-.89-3.98-2.81-7.95-5.63-11.9-8.48-.47-.34-.96-.44-1.49-.52-.89-.15-1.77-.3-2.66-.46-.37 1.65-.58 3.31-.54 5 0 .26-.13.35-.35.43-2.54.98-4.71 2.49-6.44 4.61-2.19 2.68-3.39 5.74-3.52 9.2-.08 2.03.2 4.01.94 5.91.16.42.11.56-.39.55-1.51-.04-3.01.1-4.5.34-4.03.65-7.8 2.04-11.3 4.15-.47.28-.46.28-.48-.29-.07-2.62-.79-5.05-2.09-7.32-.54-.94-1.21-1.78-1.87-2.62-.11-.11-.21-.22-.32-.32-.04-.03-.09-.05-.13-.09a14.968 14.968 0 0 0-5.59-3.61c-.3-.11-.32-.31-.32-.57.02-1.01-.05-2.01-.2-3-.7-4.61-2.93-8.33-6.59-11.18-2.35-1.83-5.02-2.92-7.96-3.35-2.31-.34-4.59-.21-6.85.37-1.06.28-2.07.69-3.08 1.11-.1-.07-.07-.15 0-.22.13-.91.37-1.79.46-2.71.31-3.31-.39-6.36-2.03-9.23-.53-.94-1.2-1.77-1.87-2.62-.11-.11-.21-.22-.32-.32-.04-.03-.09-.05-.13-.09a14.926 14.926 0 0 0-5.6-3.61c-.3-.11-.32-.31-.32-.57.02-1.01-.05-2.01-.2-3-.7-4.61-2.91-8.35-6.6-11.17-3.6-2.75-7.69-3.88-12.21-3.44-2.44.24-4.69 1.02-6.82 2.23-.66.38-1.23.89-1.93 1.2.08 1.35.06 2.7-.02 4.05-.2 3.44-.96 6.75-2.23 9.96-.65 1.65-1.45 3.23-2.36 4.75-.28.47-.26.49.27.54.47.04.93.08 1.39.17 4.47.86 7.92 3.23 10.33 7.08 1.51 2.41 2.21 5.08 2.23 7.91 0 1.73-.35 3.41-.96 5.03-.13.36-.1.49.31.58 4.47 1.04 8.2 3.32 11.16 6.83 2.86 3.39 4.42 7.32 4.69 11.72.21 3.46-.46 6.77-1.99 9.89-.25.51-.18.77.24 1.12 2.2 1.82 4.02 3.97 5.46 6.43.23.39.38.36.7.14 3.29-2.16 6.92-2.9 10.74-2.16 6.1 1.18 10.12 4.82 12.04 10.74.18.55.17.55.65.19 1.39-1.07 2.85-2.04 4.4-2.87 5.52-2.95 11.39-4.27 17.65-3.87 2.72.17 5.37.67 7.95 1.5a31.63 31.63 0 0 1 8.88 4.5c.46.33.51.31.6-.16 1.16-6.42 4.3-11.66 9.42-15.69 3.29-2.59 7.03-4.22 11.16-4.95.97-.17 1.96-.22 2.93-.38v-18.49Zm18.22 24.82c.13-1.32.2-2.53.43-3.73 1.46-7.48 5.45-13.2 11.93-17.2.39-.24.46-.36.19-.76-2.18-3.3-2.78-6.93-2.15-10.8.25-1.57.82-3.04 1.51-4.47-4.03.71-8.06 1.41-12.08 2.13-.75.14-1.52.17-2.21.66-3.94 2.84-7.9 5.65-11.86 8.45-.31.22-.4.45-.4.81.01 5.55 0 11.11 0 16.66 0 .66-.01 1.32-.02 1.98 5.54.48 10.39 2.61 14.66 6.27Z" fill="#fddce4" stroke-width="0"/>
|
||||
<path id="fighters" d="m105.65 67.02-.66-2.46c-.59-2.23-1.19-4.46-1.78-6.68-.09-.35-.23-.66-.62-.77-.71-.19-1.18.32-1.01 1.11.16.73.38 1.44.57 2.16q.39 1.49-1.18 1.51c-.29 0-.53-.02-.74-.27-2.08-2.35-6.01-1.53-6.96 1.49-.21.67-.52 1.09-1.16 1.31-.15.05-.29.13-.42.21-.34.22-.47.08-.56-.27-.25-1.01-.54-2.01-.8-3.02-.17-.64-.59-.94-1.12-.79-.45.13-.65.6-.49 1.2.38 1.43.76 2.85 1.14 4.27.44 1.64.87 3.28 1.31 4.93.09.35.24.67.6.8.29.1.59.07.83-.15.25-.23.33-.51.24-.85-.29-1.05-.57-2.11-.84-3.17-.04-.15-.22-.39.08-.48.52-.17 1.07-.1 1.6-.11.31 0 .42.29.6.46 2.12 2.06 5.82 1.26 6.75-1.52.27-.8.68-1.27 1.42-1.53.08-.03.16-.07.23-.11.34-.23.49-.14.59.27.25 1.04.54 2.08.83 3.12.15.53.49.74.96.68.41-.06.67-.4.67-.94-.01-.07-.04-.22-.08-.37Zm-6.83-4.35c.3 0 .48.3.5.56.03.33-.34.19-.44.31-.28 0-.45-.09-.43-.32.02-.24.12-.54.37-.55Zm-1.4-.71c.2.07.55-.06.51.27-.03.23-.14.57-.5.56-.36 0-.35-.34-.39-.58-.06-.34.23-.19.37-.26Zm-2.73 2.19c.06-.17-.01-.49.31-.44.23.04.51.16.51.49 0 .37-.32.36-.56.38-.28.03-.26-.21-.26-.42Zm1.23 1.52c-.02.24-.13.46-.37.58-.29-.13-.51-.34-.51-.62 0-.3.32-.26.52-.29.23-.03.38.11.36.34Zm.07-2.47c-.25-.01-.51-.14-.62-.39-.05-.12.49-.58.64-.54.28.07.2.36.31.53.01.25-.13.41-.32.4Zm.98 3.77c-.13-.17-.6.09-.54-.33.03-.22.15-.58.51-.56.42.02.4.38.43.66.03.25-.19.24-.4.23Zm.2-1.74a.8.8 0 0 1-.81-.78c0-.43.4-.85.82-.85.43 0 .84.42.83.83-.02.47-.37.8-.85.79Zm1.22 1.38c-.32-.01-.3-.38-.32-.61-.02-.21.2-.34.39-.3.22.05.43.19.58.38-.18.27-.38.54-.65.53Zm1-1.43c-.25 0-.51-.16-.53-.48-.02-.37.31-.33.52-.38.29-.07.3.14.3.38-.05.17.02.49-.29.48Zm41.06 1.2c-.52-.13-.91.16-1.07.78-.28 1.04-.56 2.08-.83 3.12-.08.3-.2.37-.47.23-.35-.19-.71-.37-1.07-.54-.28-.13-.4-.32-.47-.63-.22-.96-.68-1.79-1.49-2.37-1.84-1.33-4.08-1.13-5.66.5-.14.14-.26.27-.48.26-.4-.02-.8-.01-1.2-.02-.4-.01-.54-.18-.41-.6.29-1 .55-2.01.82-3.01.15-.59-.04-1.03-.48-1.15-.55-.15-.95.09-1.12.71-.84 3.13-1.68 6.27-2.51 9.4-.15.56.09 1.01.55 1.12.5.11.91-.16 1.07-.74.21-.79.41-1.58.64-2.36.33-1.16.33-1.15 1.46-.63.36.17.62.36.73.79.47 1.94 2.06 3.16 4.08 3.17 1.22.07 2.21-.47 3.07-1.32.14-.14.26-.27.48-.26.43.02.86 0 1.3.07.4.05.42.21.33.55-.29 1.02-.56 2.04-.81 3.07-.14.58.08.98.51 1.1.52.13.94-.11 1.09-.69.84-3.11 1.67-6.23 2.5-9.35.16-.61-.07-1.06-.54-1.18Zm-8.08 1.37c.16-.04.32 0 .29.25-.11.23-.01.64-.45.63-.34-.01-.48-.37-.47-.59.01-.33.4-.22.63-.29Zm-2.02 1.31c.04-.27.22-.58.52-.57.25 0 .34.33.36.56.01.27-.19.34-.45.31-.15-.08-.47-.02-.43-.31Zm-.35 1.49c.01-.21-.02-.46.27-.4.22.04.56 0 .55.38 0 .32-.26.46-.51.48-.32.03-.26-.29-.31-.46Zm1.33 1.9c-.29 0-.59-.24-.63-.52-.03-.24.3-.36.53-.4.21-.04.39.1.42.37-.13.16.01.55-.32.55Zm1.34.36c-.22 0-.41-.03-.35-.28.07-.25.04-.61.42-.61.37 0 .51.37.51.61 0 .32-.4.19-.58.28Zm-.22-1.75c-.44-.02-.79-.37-.78-.8 0-.43.43-.84.85-.82.43.01.83.44.8.86-.03.47-.39.78-.87.77Zm.92-2.38c.03-.21 0-.58.31-.56.26.01.49.24.64.5-.1.25-.33.36-.56.41-.24.05-.43-.11-.39-.35Zm1.26 2.83c-.05.27-.25.59-.51.58-.24 0-.37-.36-.4-.6-.03-.24.21-.32.47-.32.15.1.51 0 .45.34Zm.06-1.09c-.22-.04-.52-.03-.52-.37 0-.34.27-.5.54-.5.29 0 .24.31.28.47 0 .25-.03.45-.3.4Z" fill="#78206a" stroke-width="0"/>
|
||||
<path id="falcon" class="cls-4" d="M55.44 150.43c-2.18.04-3.97 1-5.51 2.46-.38.36-.74.65-1.3.54-.15-.03-.27.04-.39.11-.75.43-1.51.84-2.24 1.31-.58.37-1.07 1.2-.55 2.06.15.26.63.36.48.73-.12.29-.39.52-.6.78-.13.16-.26.33-.39.49-1.22 1.46-1.16 1.37-.19 3.01.29.5.51.56 1.02.27.92-.52 1.83-1.06 2.75-1.58q1.13-.65 1.76.47c.32.57.28.76-.29 1.09-1.06.62-2.13 1.24-3.2 1.84-.42.23-.52.52-.26.93.16.25.3.51.44.76.45.77.68.87 1.57.73l5.24-.81c1.51-.23 3.02-.41 4.43-1.05 2.93-1.33 4.49-3.58 4.63-6.25.01-4.64-3.31-7.94-7.39-7.87Zm1.74 2.26c.32 0 .45.22.48.51-.03.29-.17.51-.48.5-.31 0-.54-.17-.53-.51 0-.33.2-.51.53-.5Zm-1.59 7.62c-1.44 0-2.67-1.18-2.67-2.56 0-1.19 1.08-2.74 2.63-2.55 1.34-.25 2.62 1.11 2.62 2.47 0 1.46-1.14 2.64-2.58 2.64Zm3.51-4.96c.02-.28.19-.49.51-.47.33.02.51.19.5.53-.01.34-.27.44-.5.54-.34-.1-.53-.3-.51-.6Zm1.16 3.76c-.28-.02-.49-.18-.49-.5 0-.33.2-.49.51-.52.33.02.5.2.5.5 0 .32-.18.53-.51.51Zm-3.14-1.42c0 .88-.67 1.57-1.52 1.58-.88 0-1.64-.71-1.64-1.53 0-.84.75-1.58 1.6-1.59.86 0 1.56.68 1.56 1.54Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="content">
|
||||
<h2>Here's a heading</h2>
|
||||
<p>
|
||||
This paragraph is a preview of your body text. We've put enough text
|
||||
here to give you a sense of your theme's line length and line height.
|
||||
Adjust your theme's spacing and size to get the fabulous reading
|
||||
experience that you deserve. It's on us - cheers!
|
||||
</p>
|
||||
<p>
|
||||
<wa-button variant="brand">Action</wa-button>
|
||||
<wa-button variant="text">Low emphasis action →</wa-button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</wa-card>
|
||||
<div class="cards">
|
||||
<wa-card>
|
||||
<div class="space-vertically">
|
||||
<wa-alert variant="success" open>
|
||||
<wa-icon slot="icon" name="check-circle-fill"></wa-icon>
|
||||
This is the way.
|
||||
</wa-alert>
|
||||
<wa-select label="Signet" help-text="This identifies your clan. You can change this later.">
|
||||
<wa-option>Mudhorn</wa-option>
|
||||
</wa-select>
|
||||
<wa-checkbox checked>I swear on my name and the names of the ancestors</wa-checkbox>
|
||||
<wa-button variant="success">Forge</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card>
|
||||
<div class="space-vertically">
|
||||
<wa-alert variant="warning" open>
|
||||
<wa-icon slot="icon" name="check-circle-fill"></wa-icon>
|
||||
It's a trap!
|
||||
</wa-alert>
|
||||
<wa-radio-group label="Faction" value="2">
|
||||
<wa-radio value="1">Galactic Empire</wa-radio>
|
||||
<wa-radio value="2">Rebel Alliance</wa-radio>
|
||||
</wa-radio-group>
|
||||
<wa-input label="Mission" value="Destroy the Death Star"></wa-input>
|
||||
<wa-button variant="warning">Proceed</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
<wa-card>
|
||||
<div class="space-vertically">
|
||||
<wa-alert variant="danger" open>
|
||||
<wa-icon slot="icon" name="check-circle-fill"></wa-icon>
|
||||
That's no moon.
|
||||
</wa-alert>
|
||||
<wa-input label="Destination" value="Alderaan"></wa-input>
|
||||
<wa-switch checked>Jam fighter transmission</wa-switch>
|
||||
<wa-switch disabled>Lock in artillery power</wa-switch>
|
||||
<wa-button variant="danger">Turn around</wa-button>
|
||||
</div>
|
||||
</wa-card>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
html {
|
||||
background: white;
|
||||
background-image: radial-gradient(rgb(0 0 0 / .1) 1.5px, transparent 0);
|
||||
background-size: 28px 28px;
|
||||
background-position: -19px -19px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
main {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
#menu-toggle,
|
||||
#sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 1024px;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
background: var(--wa-color-surface-lowered);
|
||||
padding-inline: var(--wa-space-xl);
|
||||
padding-block-end: var(--wa-space-2xl);
|
||||
translate: calc((var(--knobs-width) + 2rem) / 2);
|
||||
}
|
||||
|
||||
.overlap {
|
||||
position: relative;
|
||||
color: var(--wa-color-text-normal);
|
||||
padding: 0 var(--wa-space-m);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.overlap::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: calc(-1 * var(--wa-space-xl));
|
||||
left: calc(-1 * var(--wa-space-xl));
|
||||
width: calc(100% + var(--wa-space-xl) * 2);
|
||||
height: 300px;
|
||||
background: var(--wa-color-brand-fill-vivid);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.overlap h1 {
|
||||
color: var(--wa-color-brand-text-on-vivid);
|
||||
}
|
||||
|
||||
.overlap .grid {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 3fr;
|
||||
align-items: center;
|
||||
gap: var(--wa-space-xl);
|
||||
padding: var(--wa-space-m);
|
||||
}
|
||||
|
||||
.overlap .image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
border-radius: var(--wa-corners-2x);
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
.overlap .image #city,
|
||||
.overlap .image #falcon {
|
||||
fill: color-mix(in oklab, var(--wa-color-brand-fill-vivid), black 50%);
|
||||
}
|
||||
|
||||
.overlap .image #fighters {
|
||||
fill: color-mix(in oklab, var(--wa-color-brand-fill-vivid), black 30%);
|
||||
}
|
||||
|
||||
.overlap .image #upper_clouds {
|
||||
fill: color-mix(in oklab, var(--wa-color-brand-fill-vivid), white 80%);
|
||||
}
|
||||
|
||||
.overlap .image #background_clouds {
|
||||
fill: color-mix(in oklab, var(--wa-color-brand-fill-vivid), white 90%);
|
||||
}
|
||||
|
||||
.overlap .image #forefront_clouds {
|
||||
fill: color-mix(in oklab, var(--wa-color-brand-fill-vivid), white 70%);
|
||||
}
|
||||
|
||||
.overlap .image .gradient-start {
|
||||
stop-color: color-mix(in oklab, var(--wa-color-brand-fill-vivid), white 80%);
|
||||
}
|
||||
|
||||
.overlap .image .gradient-stop {
|
||||
stop-color: color-mix(in oklab, var(--wa-color-brand-fill-vivid), white 0%);
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-auto-rows: 1fr;
|
||||
gap: 1rem;
|
||||
margin-block-start: var(--wa-space-m);
|
||||
}
|
||||
|
||||
.cards wa-card::part(body),
|
||||
.cards wa-card::part(base) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.space-vertically {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.space-vertically > *:last-child {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
wa-select[label="Signet"]::part(form-control-help-text) {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 670px) {
|
||||
.overlap .grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1040px) {
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1450px) {
|
||||
.cards {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Style Guide -->
|
||||
<wa-card id="style-guide">
|
||||
|
||||
## Typography
|
||||
|
||||
Vel risus commodo viverra maecenas accumsan lacus vel facilisis volutpat. Amet mauris commodo quis imperdiet. Bibendum ut tristique et egestas quis ipsum suspendisse. Sit amet nulla facilisi morbi tempus iaculis urna id volutpat.
|
||||
|
||||
Cras pulvinar mattis nunc sed blandit libero. Facilisis magna etiam tempor orci. Scelerisque eleifend donec pretium vulputate sapien nec. Donec et odio pellentesque diam volutpat commodo sed egestas egestas. Mauris rhoncus aenean vel elit scelerisque mauris pellentesque.
|
||||
|
||||
> What is a Web year now, about three months? And when people can browse around, discover new things, and download them fast, when we all have agents - then Web years could slip by before human beings can notice.
|
||||
>
|
||||
> — Tim Berners-Lee
|
||||
|
||||
## Inline Text
|
||||
|
||||
<div class="docs-grid">
|
||||
<p><strong>Bold</strong></p>
|
||||
<p><em>Italics</em></p>
|
||||
<p><u>Underline</u></p>
|
||||
<p><del>Deleted</del></p>
|
||||
<p><ins>Inserted</ins></p>
|
||||
<p><s>Strike-through</s></p>
|
||||
<p><small>Small</small></p>
|
||||
<p><span>Text <sub>Sub</sub></span></p>
|
||||
<p><span>Text <sup>Sup</sup></span></p>
|
||||
<p><abbr title="Abbreviation">Abbr.</abbr></p>
|
||||
<p><kbd>Keyboard</kbd></p>
|
||||
<p><mark>Highlighted</mark></p>
|
||||
<p><a href="#">Link text</a></p>
|
||||
<p><code>Inline code</code></p>
|
||||
</div>
|
||||
|
||||
## Lists
|
||||
|
||||
- List item 1
|
||||
- List item 2
|
||||
- List item 3
|
||||
- Subitem a
|
||||
- Subitem b
|
||||
|
||||
1. List item 1
|
||||
2. List item 2
|
||||
3. List item 3
|
||||
- Subitem a
|
||||
- Subitem b
|
||||
|
||||
## Headings
|
||||
|
||||
### Heading 3
|
||||
|
||||
Feugiat nisl pretium fusce id. Ipsum dolor sit amet consectetur adipiscing elit. Eget nunc lobortis mattis aliquam faucibus purus. Metus dictum at tempor commodo ullamcorper a lacus vestibulum. Urna condimentum mattis pellentesque id nibh tortor id.
|
||||
|
||||
#### Heading 4
|
||||
|
||||
Gravida arcu ac tortor dignissim convallis aenean. Pellentesque pulvinar pellentesque habitant morbi tristique senectus et. Ipsum nunc aliquet bibendum enim facilisis gravida neque. Donec adipiscing tristique risus nec feugiat in.
|
||||
|
||||
##### Heading 5
|
||||
|
||||
Enim diam vulputate ut pharetra sit. Enim facilisis gravida neque convallis a cras. Enim neque volutpat ac tincidunt vitae semper. Sed egestas egestas fringilla phasellus faucibus scelerisque eleifend donec pretium.
|
||||
|
||||
###### Heading 6
|
||||
|
||||
Tincidunt ornare massa eget egestas purus viverra accumsan in nisl. Facilisis mauris sit amet massa vitae. Nunc faucibus a pellentesque sit amet porttitor. Adipiscing tristique risus nec feugiat in fermentum.
|
||||
|
||||
## Details
|
||||
|
||||
Individual details look like this.
|
||||
|
||||
<details>
|
||||
<summary>Tincidunt nunc pulvinar</summary>
|
||||
<p>Ut lectus arcu bibendum at varius. Convallis a cras semper auctor neque vitae. Odio pellentesque diam volutpat commodo sed egestas. Amet dictum sit amet justo donec enim diam vulputate ut.</p>
|
||||
</details>
|
||||
|
||||
Grouping them provides accordion-style functionality.
|
||||
|
||||
<details>
|
||||
<summary>Enim diam</summary>
|
||||
<p>Nunc faucibus a pellentesque sit amet porttitor. Adipiscing tristique risus nec feugiat in fermentum. Leo duis ut diam quam nulla porttitor massa id. Mauris nunc congue nisi vitae.</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Arcu non odio</summary>
|
||||
<p>Sed libero enim sed faucibus turpis in eu mi bibendum. Nunc mi ipsum faucibus vitae aliquet nec. Ultricies tristique nulla aliquet enim tortor. Tellus at urna condimentum mattis pellentesque.</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Ut porttitor</summary>
|
||||
<p>Eu facilisis sed odio morbi quis commodo odio aenean sed. Sit amet purus gravida quis blandit turpis cursus. Eu consequat ac felis donec et odio pellentesque diam volutpat.</p>
|
||||
</details>
|
||||
|
||||
## Code Blocks
|
||||
|
||||
```
|
||||
// do a thing
|
||||
export function thing() {
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
## Images
|
||||
|
||||

|
||||
|
||||
## Tables
|
||||
|
||||
<table>
|
||||
<caption>I'm just a table</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Column 1</th>
|
||||
<th>Column 2</th>
|
||||
<th>Column 3</th>
|
||||
<th>Column 4</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Cell</td>
|
||||
<td>Cell</td>
|
||||
<td>Cell</td>
|
||||
<td>Cell</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cell</td>
|
||||
<td>Cell</td>
|
||||
<td>Cell</td>
|
||||
<td>Cell</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cell</td>
|
||||
<td>Cell</td>
|
||||
<td>Cell</td>
|
||||
<td>Cell</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cell</td>
|
||||
<td>Cell</td>
|
||||
<td>Cell</td>
|
||||
<td>Cell</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Definition Lists
|
||||
|
||||
<dl>
|
||||
<dt>Definition 1</dt>
|
||||
<dd>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</dd>
|
||||
<dt>Definition 2</dt>
|
||||
<dd>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</dd>
|
||||
<dt>Definition 3</dt>
|
||||
<dd>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.</dd>
|
||||
</dl>
|
||||
|
||||
<h2>Form Control Validation</h2>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
|
||||
<div>
|
||||
<h3>Valid</h3>
|
||||
<wa-input class="wa-valid" label="Name" help-text="Just a first name is fine" placeholder="Enter your name"></wa-input><br>
|
||||
<wa-select class="wa-valid" label="Choose one" help-text="Make a choice already">
|
||||
<wa-option>There can be only one!</wa-option>
|
||||
<wa-option>Well, maybe two is OK</wa-option>
|
||||
</wa-select>
|
||||
<wa-textarea class="wa-valid" label="Bio" help-text="Tell us about yourself" placeholder="Enter a bio"></wa-textarea><br>
|
||||
<wa-range class="wa-valid" value="50" label="Volume" help-text="Crank it up" tooltip="none"></wa-range><br>
|
||||
<wa-checkbox class="wa-valid" checked>I am awesome</wa-checkbox><br>
|
||||
<wa-checkbox class="wa-valid">So am I</wa-checkbox><br><br>
|
||||
<wa-switch class="wa-valid" checked>Still awesome</wa-switch><br>
|
||||
<wa-switch class="wa-valid">More awesome</wa-switch><br><br>
|
||||
<wa-radio-group class="wa-valid" label="Select an option" name="a" value="1">
|
||||
<wa-radio value="1">Option 1</wa-radio>
|
||||
<wa-radio value="2">Option 2</wa-radio>
|
||||
<wa-radio value="3">Option 3</wa-radio>
|
||||
</wa-radio-group><br>
|
||||
<wa-button variant="brand">Submit Form</wa-button>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Invalid</h3>
|
||||
<wa-input class="wa-invalid" label="Name" help-text="Just a first name is fine" placeholder="Enter your name"></wa-input><br>
|
||||
<wa-select class="wa-invalid" label="Choose one" help-text="Make a choice already">
|
||||
<wa-option>There can be only one!</wa-option>
|
||||
<wa-option>Well, maybe two is OK</wa-option>
|
||||
</wa-select>
|
||||
<wa-textarea class="wa-invalid" label="Bio" help-text="Tell us about yourself" placeholder="Enter a bio"></wa-textarea><br>
|
||||
<wa-range class="wa-invalid" value="50" label="Volume" help-text="Crank it up" tooltip="none"></wa-range><br>
|
||||
<wa-checkbox class="wa-invalid" checked>I am awesome</wa-checkbox><br>
|
||||
<wa-checkbox class="wa-invalid">So am I</wa-checkbox><br><br>
|
||||
<wa-switch class="wa-invalid" checked>Still awesome</wa-switch><br>
|
||||
<wa-switch class="wa-invalid">More awesome</wa-switch><br><br>
|
||||
<wa-radio-group class="wa-invalid" label="Select an option" name="a" value="1">
|
||||
<wa-radio value="1">Option 1</wa-radio>
|
||||
<wa-radio value="2">Option 2</wa-radio>
|
||||
<wa-radio value="3">Option 3</wa-radio>
|
||||
</wa-radio-group><br>
|
||||
<wa-button variant="brand">Submit Form</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#style-guide {
|
||||
margin-block-start: var(--wa-flow-spacing);
|
||||
}
|
||||
|
||||
#style-guide wa-card {
|
||||
padding-block: var(--wa-space-xl);
|
||||
padding-inline: var(--wa-space-2xl);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,16 +10,40 @@ Angular [plays nice](https://custom-elements-everywhere.com/#angular) with custo
|
||||
|
||||
## Installation
|
||||
|
||||
### Download the npm package
|
||||
|
||||
To add Web Awesome to your Angular app, install the package from npm.
|
||||
|
||||
```bash
|
||||
npm install @shoelace-style/shoelace
|
||||
```
|
||||
|
||||
Next, [include a theme](/getting-started/themes) and set the [base path](/getting-started/installation#setting-the-base-path) for icons and other assets. In this example, we'll import the light theme and use the CDN as a base path.
|
||||
### Update the Angular Configuration
|
||||
|
||||
Next, [include a theme](/getting-started/themes). In this example, we'll import the light theme.
|
||||
Its also important to load the components by using a `<script>` tag into the index.html file. However, the Angular way to do it is by adding a script configurations into your angular.json file as follows:
|
||||
|
||||
```json
|
||||
"architect": {
|
||||
"build": {
|
||||
...
|
||||
"options": {
|
||||
...
|
||||
"styles": [
|
||||
"src/styles.scss",
|
||||
"@shoelace-style/shoelace/dist/themes/light.css"
|
||||
],
|
||||
"scripts": [
|
||||
"@shoelace-style/shoelace/dist/shoelace.js"
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
### Setting up the base path
|
||||
|
||||
Next, set the [base path](/getting-started/installation#setting-the-base-path) for icons and other assets in the `main.ts`. In this example, we'll use the CDN as a base path.
|
||||
|
||||
```jsx
|
||||
import '@shoelace-style/shoelace/%NPMDIR%/themes/default.css';
|
||||
import { setBasePath } from '@shoelace-style/shoelace/%NPMDIR%/utilities/base-path';
|
||||
|
||||
setBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/');
|
||||
|
||||
@@ -208,13 +208,34 @@ Web Awesome ships with a file called `vscode.html-custom-data.json` that can be
|
||||
}
|
||||
```
|
||||
|
||||
If `settings.json` already exists, simply add the above line to the root of the object. Note that you may need to restart VS Code for the changes to take affect.
|
||||
If `settings.json` already exists, simply add the above line to the root of the object. Note that you may need to restart VS Code for the changes to take effect.
|
||||
|
||||
### JetBrains IDEs
|
||||
|
||||
If you are using a [JetBrains IDE](https://www.jetbrains.com/) and you are installing Web Awesome from NPM, the editor will automatically detect the `web-types.json` file from the package and you should immediately see component information in your editor.
|
||||
If you are using a [JetBrains IDE](https://www.jetbrains.com/) and you are installing Shoelace from NPM, the editor will automatically detect the `web-types.json` file from the package and you should immediately see component information in your editor.
|
||||
|
||||
If you are installing from the CDN, you can [download a local copy](https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace/dist/web-types.json) and add it to the root of your project.
|
||||
If you are installing from the CDN, you can [download a local copy](https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace/dist/web-types.json) and add it to the root of your project. Be sure to add a reference to the `web-types.json` file in your `package.json` in order for your editor to properly detect it.
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"web-types": "./web-types.json"
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
If you are using types from multiple projects, you can add an array of references.
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"web-types": [
|
||||
...,
|
||||
"./web-types.json"
|
||||
]
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Other Editors
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ toc: false
|
||||
|
||||
<div class="splash">
|
||||
<div class="splash-start">
|
||||
{% include 'workmark.njk' %}
|
||||
{% include 'logo.njk' %}
|
||||
|
||||
# <wa-visually-hidden>Web Awesome:</wa-visually-hidden> A forward-thinking library of web components.
|
||||
# <wa-visually-hidden>Web Awesome:</wa-visually-hidden>
|
||||
|
||||
- Works with all frameworks 🧩
|
||||
- Works with CDNs 🚛
|
||||
@@ -85,7 +85,7 @@ With Web Awesome, you can:
|
||||
- Incrementally adopt components as needed (no need to ditch your framework)
|
||||
- Upgrade or switch frameworks without rebuilding foundational components
|
||||
|
||||
If your organization is looking to build a design system, [Web Awesome will save you thousands of dollars](https://medium.com/eightshapes-llc/and-you-thought-buttons-were-easy-26eb5b5c1871).\* All the foundational components you need are right here, ready to be customized for your brand. And since it's built on web standards, browsers will continue to support it for many years to come.
|
||||
If your organization is looking to build a design system, [Web Awesome will save you thousands of dollars](https://medium.com/eightshapes-llc/and-you-thought-buttons-were-easy-26eb5b5c1871). All the foundational components you need are right here, ready to be customized for your brand. And since it's built on web standards, browsers will continue to support it for many years to come.
|
||||
|
||||
Whether you use Web Awesome as a starting point for your organization's design system or for a fun personal project, there's no limit to what you can do with it.
|
||||
|
||||
|
||||
9
docs/pages/layouts/advanced-example-playground.njk
Normal file
9
docs/pages/layouts/advanced-example-playground.njk
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
layout: layout-example.njk
|
||||
---
|
||||
|
||||
{% set in_playground = true %}
|
||||
{% set html_file = "layout-templates/advanced-example.html" %}
|
||||
{% set css_file = "layout-templates/advanced-example.css" %}
|
||||
|
||||
{% include "playground.njk" %}
|
||||
10
docs/pages/layouts/advanced-example.njk
Normal file
10
docs/pages/layouts/advanced-example.njk
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
layout: layout-example.njk
|
||||
---
|
||||
|
||||
<style>
|
||||
{% include "layout-templates/advanced-example.css" %}
|
||||
</style>
|
||||
|
||||
{% include "layout-templates/advanced-example.html" %}
|
||||
|
||||
8
docs/pages/layouts/app-playground.njk
Normal file
8
docs/pages/layouts/app-playground.njk
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
layout: layout-example.njk
|
||||
---
|
||||
|
||||
{% set html_file = "layout-templates/app.html" %}
|
||||
{% set css_file = "layout-templates/app.css" %}
|
||||
|
||||
{% include "playground.njk" %}
|
||||
9
docs/pages/layouts/app.njk
Normal file
9
docs/pages/layouts/app.njk
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
layout: layout-example.njk
|
||||
---
|
||||
|
||||
<style>
|
||||
{% include "layout-templates/app.css" %}
|
||||
</style>
|
||||
|
||||
{% include "layout-templates/app.html" %}
|
||||
10
docs/pages/layouts/example-playground.njk
Normal file
10
docs/pages/layouts/example-playground.njk
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
layout: layout-example.njk
|
||||
---
|
||||
|
||||
<!-- Required for load layout widget -->
|
||||
{% set in_playground = true %}
|
||||
{% set html_file = "layout-templates/example.html" %}
|
||||
{% set css_file = "layout-templates/example.css" %}
|
||||
|
||||
{% include "playground.njk" %}
|
||||
10
docs/pages/layouts/example.njk
Normal file
10
docs/pages/layouts/example.njk
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
layout: layout-example.njk
|
||||
---
|
||||
|
||||
<style>
|
||||
{% include "layout-templates/example.css" %}
|
||||
</style>
|
||||
|
||||
{% include "layout-templates/example.html" %}
|
||||
|
||||
9
docs/pages/layouts/hero-playground.njk
Normal file
9
docs/pages/layouts/hero-playground.njk
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
layout: layout-example.njk
|
||||
---
|
||||
|
||||
{% set in_playground = true %}
|
||||
{% set html_file = "layout-templates/hero.html" %}
|
||||
{% set css_file = "layout-templates/hero.css" %}
|
||||
|
||||
{% include "playground.njk" %}
|
||||
9
docs/pages/layouts/hero.njk
Normal file
9
docs/pages/layouts/hero.njk
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
layout: layout-example.njk
|
||||
---
|
||||
|
||||
<style>
|
||||
{% include "layout-templates/example.css" %}
|
||||
</style>
|
||||
|
||||
{% include "layout-templates/example.html" %}
|
||||
18
docs/pages/layouts/index.md
Normal file
18
docs/pages/layouts/index.md
Normal file
@@ -0,0 +1,18 @@
|
||||
<script type="module">
|
||||
window.Turbo.session.drive = false
|
||||
</script>
|
||||
|
||||
- [Example Layout](/layouts/example/index.html)
|
||||
- [Example Playground](/layouts/example-playground/index.html)
|
||||
|
||||
- [Advanced Example Layout](/layouts/advanced-example/index.html)
|
||||
- [Advanced Example Playground](/layouts/advanced-example-playground/index.html)
|
||||
|
||||
- [App Layout](/layouts/app/index.html)
|
||||
- [App Playground](/layouts/app-playground/index.html)
|
||||
|
||||
- [Hero Layout](/layouts/hero/index.html)
|
||||
- [Hero Playground](/layouts/hero-playground/index.html)
|
||||
|
||||
- [Sport Awesome Layout](/layouts/sport-awesome/index.html)
|
||||
- [Sport Awesome Playground](/layouts/sport-awesome-playground/index.html)
|
||||
10
docs/pages/layouts/sport-awesome-playground.njk
Normal file
10
docs/pages/layouts/sport-awesome-playground.njk
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
layout: layout-example.njk
|
||||
---
|
||||
|
||||
{% set in_playground = true %}
|
||||
{% set html_file = "layout-templates/sport-awesome.html" %}
|
||||
{% set css_file = "layout-templates/sport-awesome.css" %}
|
||||
|
||||
{% include "playground.njk" %}
|
||||
|
||||
10
docs/pages/layouts/sport-awesome.njk
Normal file
10
docs/pages/layouts/sport-awesome.njk
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
layout: layout-example.njk
|
||||
---
|
||||
|
||||
<style>
|
||||
{% include "layout-templates/sport-awesome.css" %}
|
||||
</style>
|
||||
|
||||
{% include "layout-templates/sport-awesome.html" %}
|
||||
|
||||
@@ -16,13 +16,52 @@ New versions of Web Awesome are released as-needed and generally occur when a cr
|
||||
|
||||
- Changed the `sl` prefix to `wa` for Web Awesome, including tags, events, etc.
|
||||
- Changed `primary` variants to `brand` in all components
|
||||
- Improved submenu selection by implementing the [safe triangle](https://www.smashingmagazine.com/2023/08/better-context-menus-safe-triangles/) method [#1550]
|
||||
- Removed `default` from `<wa-button>` and made `neutral` the new default
|
||||
- Removed the `circle` modifier from `<wa-button>` because button's no longer have a set height
|
||||
|
||||
## Next
|
||||
|
||||
- Fixed a bug with bundled components using CDN builds not having translations on initial connect [#1696]
|
||||
- Fixed a bug where the `"sl-change"` event would always fire simultaneously with `"sl-input"` event in `<sl-color-picker>`. The `<sl-change>` event now only fires when a user stops dragging a slider or stops dragging on the color canvas. [#1689]
|
||||
|
||||
## 2.11.2
|
||||
|
||||
- Fixed a bug in `<sl-carousel>` component that caused an error to be thrown when rendered with Lit [#1684]
|
||||
|
||||
## 2.11.1
|
||||
|
||||
- Improved the experimental `<sl-carousel>` component [#1605]
|
||||
|
||||
## 2.11.0
|
||||
|
||||
- Added the Croatian translation [#1656]
|
||||
- Fixed a bug that caused the [[Escape]] key to stop propagating when tooltips are disabled [#1607]
|
||||
- Fixed a bug that made it impossible to style placeholders in `<sl-select>` [#1667]
|
||||
- Fixed a bug that caused `dist/react/index.js` to be blank [#1659]
|
||||
|
||||
## 2.10.0
|
||||
|
||||
- Added the Simplified Chinese translation [#1604]
|
||||
- Fixed a bug [in the localize dependency](https://github.com/shoelace-style/localize/issues/20) that caused underscores in language codes to throw a `RangeError`
|
||||
- Fixed a bug in the focus trapping utility used by modals that caused unexpected focus behavior. [#1583]
|
||||
- Fixed a bug in `<sl-copy-button>` that prevented exported tooltip parts from being styled [#1586]
|
||||
- Fixed a bug in `<sl-menu>` that caused it not to fire the `sl-select` event if you clicked an element inside of a `<sl-menu-item>` [#1599]
|
||||
- Fixed a bug that caused focus trap logic to hang the browser in certain circumstances [#1612]
|
||||
- Improved submenu selection by implementing the [safe triangle](https://www.smashingmagazine.com/2023/08/better-context-menus-safe-triangles/) method [#1550]
|
||||
- Updated `@shoelace-style/localize` to 3.1.0
|
||||
- Updated `@lib-labs/react` to stable `@lit/react`
|
||||
- Updated Bootstrap Icons to 1.11.1
|
||||
- Updated Lit to 3.0.0
|
||||
- Updated TypeScript to 5.2.2
|
||||
- Updated all other dependencies to latest versions
|
||||
|
||||
## 2.9.0
|
||||
|
||||
- Added the `modal` property to `<sl-dialog>` and `<sl-drawer>` to support third-party modals [#1571]
|
||||
- Fixed a bug in the autoloader causing it to register non-Shoelace elements [#1563]
|
||||
- Fixed a bug in `<wa-switch>` that resulted in improper spacing between the label and the required asterisk [#1540]
|
||||
- Fixed a bug in `<sl-switch>` that resulted in improper spacing between the label and the required asterisk [#1540]
|
||||
- Fixed a bug in `<sl-icon>` that caused icons to not load when the default library used a sprite sheet [#1572]
|
||||
- Removed error when a missing popup anchor is provided [#1548]
|
||||
- Updated `@ctrl/tinycolor` to 4.0.1 [#1542]
|
||||
- Updated Bootstrap Icons to 1.11.0
|
||||
|
||||
@@ -36,6 +36,7 @@ I realize that one cannot reasonably enforce this any more than one can enforce
|
||||
The [issue tracker](https://github.com/shoelace-style/shoelace/issues) is for bug reports, feature requests, and pull requests.
|
||||
|
||||
- Please **do not** use the issue tracker for personal support requests. Use [the discussion forum](https://github.com/shoelace-style/shoelace/discussions/categories/help) instead.
|
||||
- Please **do not** use the issue tracker for feature requests. Use [the discussion forum](https://github.com/shoelace-style/shoelace/discussions/categories/ideas) instead.
|
||||
- Please **do not** derail, hijack, or troll issues. Keep the discussion on topic and be respectful of others.
|
||||
- Please **do not** post comments with "+1" or "👍". Use [reactions](https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) instead.
|
||||
- Please **do** use the issue tracker for feature requests, bug reports, and pull requests.
|
||||
@@ -44,10 +45,10 @@ Issues that do not follow these guidelines are subject to closure. There simply
|
||||
|
||||
### Feature Requests
|
||||
|
||||
Feature requests can be added using the issue tracker.
|
||||
Feature requests can be added using [the discussion forum](https://github.com/shoelace-style/shoelace/discussions/categories/ideas).
|
||||
|
||||
- Please **do** search for an existing request before suggesting a new feature.
|
||||
- Please **do** use the "👍" reaction to vote for a feature.
|
||||
- Please **do** use the voting buttons to vote for a feature.
|
||||
- Please **do** share substantial use cases and perspective that support new features if they haven't already been mentioned.
|
||||
- Please **do not** bump, spam, or ping contributors to prioritize your own feature.
|
||||
|
||||
|
||||
@@ -28,5 +28,4 @@ Form control tokens control the appearance of form controls such as [input](/com
|
||||
| `--wa-form-controls-border-color-resting` | `var(--wa-color-neutral-outline-muted-alt)` |
|
||||
| `--wa-form-controls-border-color-activated` | `var(--wa-color-brand-action-vivid)` |
|
||||
| `--wa-form-controls-value-line-height` | `var(--wa-font-height-compact)` |
|
||||
| `--wa-form-controls-padding` | `var(--wa-space-square-s)` |
|
||||
| `--wa-form-controls-placeholder-color` | `var(--wa-color-neutral-60)` |
|
||||
|
||||
@@ -88,7 +88,7 @@ module.exports = environment;
|
||||
The final step is to add the corresponding `pack_tags` to the page. You should have the following `tags` in the `<head>` section of `app/views/layouts/application.html.erb`.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- ... -->
|
||||
|
||||
9019
package-lock.json
generated
9019
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
95
package.json
95
package.json
@@ -25,8 +25,15 @@
|
||||
"./dist/react/*": "./dist/react/*",
|
||||
"./dist/translations/*": "./dist/translations/*"
|
||||
},
|
||||
"files": ["dist", "cdn"],
|
||||
"keywords": ["web components", "custom elements", "components"],
|
||||
"files": [
|
||||
"dist",
|
||||
"cdn"
|
||||
],
|
||||
"keywords": [
|
||||
"web components",
|
||||
"custom elements",
|
||||
"components"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/shoelace-style/shoelace.git"
|
||||
@@ -39,11 +46,10 @@
|
||||
"build": "node scripts/build.js",
|
||||
"verify": "npm run prettier:check && npm run lint && npm run build && npm run test",
|
||||
"prepublishOnly": "npm run verify",
|
||||
"prettier": "prettier --write --loglevel warn .",
|
||||
"prettier:check": "prettier --check --loglevel warn .",
|
||||
"prettier": "prettier --write --log-level warn .",
|
||||
"prettier:check": "prettier --check --log-level warn .",
|
||||
"lint": "eslint src --max-warnings 0",
|
||||
"lint:fix": "eslint src --max-warnings 0 --fix",
|
||||
"ts-check": "tsc --noEmit --project ./tsconfig.json",
|
||||
"create": "plop --plopfile scripts/plop/plopfile.js",
|
||||
"test": "web-test-runner --group default",
|
||||
"test:component": "web-test-runner -- --watch --group",
|
||||
@@ -56,79 +62,82 @@
|
||||
"node": ">=14.17.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^4.0.1",
|
||||
"@floating-ui/dom": "^1.2.1",
|
||||
"@lit-labs/react": "^2.0.3",
|
||||
"@ctrl/tinycolor": "^4.0.2",
|
||||
"@floating-ui/dom": "^1.5.3",
|
||||
"@shoelace-style/animations": "^1.1.0",
|
||||
"@shoelace-style/localize": "^3.1.1",
|
||||
"@shoelace-style/localize": "^3.1.2",
|
||||
"composed-offset-position": "^0.0.4",
|
||||
"lit": "^2.7.5",
|
||||
"lit": "^3.0.0",
|
||||
"qr-creator": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "^2.0.1",
|
||||
"@custom-elements-manifest/analyzer": "^0.8.3",
|
||||
"@open-wc/testing": "^3.1.7",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/react": "^18.0.26",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"@web/dev-server-esbuild": "^0.3.3",
|
||||
"@web/test-runner": "^0.15.0",
|
||||
"@web/test-runner-commands": "^0.6.5",
|
||||
"@custom-elements-manifest/analyzer": "^0.8.4",
|
||||
"@lit/react": "^1.0.0",
|
||||
"@open-wc/testing": "^3.2.0",
|
||||
"@types/mocha": "^10.0.2",
|
||||
"@types/react": "^18.2.28",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.5",
|
||||
"@typescript-eslint/parser": "^6.7.5",
|
||||
"@web/dev-server-esbuild": "^0.3.6",
|
||||
"@web/test-runner": "^0.15.3",
|
||||
"@web/test-runner-commands": "^0.6.6",
|
||||
"@web/test-runner-playwright": "^0.9.0",
|
||||
"bootstrap-icons": "^1.11.0",
|
||||
"bootstrap-icons": "^1.11.1",
|
||||
"browser-sync": "^2.29.3",
|
||||
"chalk": "^5.2.0",
|
||||
"chalk": "^5.3.0",
|
||||
"change-case": "^4.1.2",
|
||||
"command-line-args": "^5.2.1",
|
||||
"comment-parser": "^1.3.1",
|
||||
"comment-parser": "^1.4.0",
|
||||
"cspell": "^6.18.1",
|
||||
"custom-element-jet-brains-integration": "^1.1.0",
|
||||
"custom-element-vs-code-integration": "^1.1.0",
|
||||
"del": "^7.0.0",
|
||||
"custom-element-jet-brains-integration": "^1.4.0",
|
||||
"custom-element-vs-code-integration": "^1.2.1",
|
||||
"del": "^7.1.0",
|
||||
"download": "^8.0.0",
|
||||
"esbuild": "^0.18.2",
|
||||
"esbuild": "^0.19.4",
|
||||
"esbuild-plugin-replace": "^1.4.0",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint": "^8.51.0",
|
||||
"eslint-plugin-chai-expect": "^3.0.0",
|
||||
"eslint-plugin-chai-friendly": "^0.7.2",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-lit": "^1.8.3",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-lit": "^1.9.1",
|
||||
"eslint-plugin-lit-a11y": "^4.1.0",
|
||||
"eslint-plugin-markdown": "^3.0.0",
|
||||
"eslint-plugin-markdown": "^3.0.1",
|
||||
"eslint-plugin-sort-imports-es6-autofix": "^0.6.0",
|
||||
"eslint-plugin-wc": "^1.5.0",
|
||||
"eslint-plugin-wc": "^2.0.4",
|
||||
"front-matter": "^4.0.2",
|
||||
"get-port": "^7.0.0",
|
||||
"globby": "^13.1.3",
|
||||
"globby": "^13.2.2",
|
||||
"husky": "^8.0.3",
|
||||
"jsdom": "^22.1.0",
|
||||
"jsonata": "^2.0.1",
|
||||
"lint-staged": "^13.1.0",
|
||||
"jsonata": "^2.0.3",
|
||||
"lint-staged": "^14.0.1",
|
||||
"lunr": "^2.3.9",
|
||||
"markdown-it-container": "^3.0.0",
|
||||
"markdown-it-ins": "^3.0.1",
|
||||
"markdown-it-kbd": "^2.2.2",
|
||||
"markdown-it-mark": "^3.0.1",
|
||||
"markdown-it-replace-it": "^1.0.0",
|
||||
"npm-check-updates": "^16.6.2",
|
||||
"ora": "^6.3.1",
|
||||
"npm-check-updates": "^16.14.6",
|
||||
"ora": "^7.0.1",
|
||||
"pascal-case": "^3.1.2",
|
||||
"plop": "^3.1.1",
|
||||
"prettier": "^2.8.8",
|
||||
"plop": "^4.0.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prismjs": "^1.29.0",
|
||||
"react": "^18.2.0",
|
||||
"recursive-copy": "^2.0.14",
|
||||
"sinon": "^15.0.1",
|
||||
"sinon": "^16.1.0",
|
||||
"smartquotes": "^2.3.2",
|
||||
"source-map": "^0.7.4",
|
||||
"strip-css-comments": "^5.0.0",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^5.1.3",
|
||||
"user-agent-data-types": "^0.3.0"
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.2.2",
|
||||
"user-agent-data-types": "^0.3.1"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,js}": ["eslint --max-warnings 0 --cache --fix", "prettier --write"]
|
||||
"*.{ts,js}": [
|
||||
"eslint --max-warnings 0 --cache --fix",
|
||||
"prettier --write"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-env node */
|
||||
module.exports = {
|
||||
/** @type {import("prettier").Config} */
|
||||
const config = {
|
||||
arrowParens: 'avoid',
|
||||
bracketSpacing: true,
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
@@ -16,3 +16,5 @@ module.exports = {
|
||||
trailingComma: 'none',
|
||||
useTabs: false
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -87,7 +87,7 @@ async function buildTheDocs(watch = false) {
|
||||
// Builds the source with esbuild.
|
||||
//
|
||||
async function buildTheSource() {
|
||||
const alwaysExternal = ['@lit-labs/react', 'react'];
|
||||
const alwaysExternal = ['@lit/react', 'react'];
|
||||
|
||||
const cdnConfig = {
|
||||
format: 'esm',
|
||||
@@ -122,7 +122,7 @@ async function buildTheSource() {
|
||||
// We don't bundle certain dependencies in the unbundled build. This ensures we ship bare module specifiers,
|
||||
// allowing end users to better optimize when using a bundler. (Only packages that ship ESM can be external.)
|
||||
//
|
||||
// We never bundle React or @lit-labs/react though!
|
||||
// We never bundle React or @lit/react though!
|
||||
//
|
||||
external: alwaysExternal,
|
||||
splitting: true,
|
||||
@@ -181,7 +181,6 @@ async function nextTask(label, action) {
|
||||
console.error(`${chalk.red('✘')} ${err}`);
|
||||
if (err.stdout) console.error(chalk.red(err.stdout));
|
||||
if (err.stderr) console.error(chalk.red(err.stderr));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +247,14 @@ if (serve) {
|
||||
|
||||
const bs = browserSync.create();
|
||||
const port = await getPort({ port: portNumbers(4000, 4999) });
|
||||
/**
|
||||
* @type {import("browser-sync").Options}
|
||||
*/
|
||||
const browserSyncConfig = {
|
||||
snippetOptions: {
|
||||
// Ignore all HTML files within the templates folder
|
||||
ignorePaths: ['/assets/playground-service-worker-proxy.html', '/assets/playground-service-worker.js']
|
||||
},
|
||||
startPath: '/',
|
||||
port,
|
||||
logLevel: 'silent',
|
||||
@@ -329,12 +335,12 @@ if (!serve) {
|
||||
|
||||
await nextTask('Building the docs', async () => {
|
||||
result = await buildTheDocs();
|
||||
});
|
||||
|
||||
// Log deferred output
|
||||
if (result.output.length > 0) {
|
||||
console.log('\n' + result.output.join('\n'));
|
||||
}
|
||||
// Log deferred output
|
||||
if (result.output.length > 0) {
|
||||
console.log('\n' + result.output.join('\n'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Cleanup on exit
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import commandLineArgs from 'command-line-args';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import { deleteSync } from 'del';
|
||||
import prettier from 'prettier';
|
||||
import prettierConfig from '../prettier.config.cjs';
|
||||
import { default as prettierConfig } from '../prettier.config.js';
|
||||
import { getAllComponents } from './shared.js';
|
||||
|
||||
const { outdir } = commandLineArgs({ name: 'outdir', type: String });
|
||||
@@ -20,7 +19,7 @@ const metadata = JSON.parse(fs.readFileSync(path.join(outdir, 'custom-elements.j
|
||||
const components = getAllComponents(metadata);
|
||||
const index = [];
|
||||
|
||||
components.map(component => {
|
||||
for await (const component of components) {
|
||||
const tagWithoutPrefix = component.tagName.replace(/^wa-/, '');
|
||||
const componentDir = path.join(reactDir, tagWithoutPrefix);
|
||||
const componentFile = path.join(componentDir, 'index.ts');
|
||||
@@ -31,8 +30,7 @@ components.map(component => {
|
||||
const eventExports = (component.events || [])
|
||||
.map(event => `export type { ${event.eventName} } from '../../../src/events/events';`)
|
||||
.join('\n');
|
||||
const eventNameImport =
|
||||
(component.events || []).length > 0 ? `import { type EventName } from '@lit-labs/react';` : ``;
|
||||
const eventNameImport = (component.events || []).length > 0 ? `import { type EventName } from '@lit/react';` : ``;
|
||||
const events = (component.events || [])
|
||||
.map(event => `${event.reactName}: '${event.name}' as EventName<${event.eventName}>`)
|
||||
.join(',\n');
|
||||
@@ -41,10 +39,10 @@ components.map(component => {
|
||||
|
||||
const jsDoc = component.jsDoc || '';
|
||||
|
||||
const source = prettier.format(
|
||||
const source = await prettier.format(
|
||||
`
|
||||
import * as React from 'react';
|
||||
import { createComponent } from '@lit-labs/react';
|
||||
import { createComponent } from '@lit/react';
|
||||
import Component from '../../${importPath}';
|
||||
|
||||
${eventNameImport}
|
||||
@@ -75,7 +73,7 @@ components.map(component => {
|
||||
index.push(`export { default as ${component.name} } from './${tagWithoutPrefix}/index.js';`);
|
||||
|
||||
fs.writeFileSync(componentFile, source, 'utf8');
|
||||
});
|
||||
}
|
||||
|
||||
// Generate the index file
|
||||
fs.writeFileSync(path.join(reactDir, 'index.ts'), index.join('\n'), 'utf8');
|
||||
|
||||
@@ -24,7 +24,7 @@ filesToEmbed.forEach(file => {
|
||||
});
|
||||
|
||||
// Loop through each theme file, copying the .css and generating a .js version for Lit users
|
||||
files.forEach(file => {
|
||||
files.forEach(async file => {
|
||||
let source = fs.readFileSync(file, 'utf8');
|
||||
|
||||
// If the source has "/* _filename.css */" in it, replace it with the embedded styles
|
||||
@@ -32,11 +32,11 @@ files.forEach(file => {
|
||||
source = source.replace(`/* ${key} */`, embeds[key]);
|
||||
});
|
||||
|
||||
const css = prettier.format(stripComments(source), {
|
||||
const css = await prettier.format(stripComments(source), {
|
||||
parser: 'css'
|
||||
});
|
||||
|
||||
let js = prettier.format(
|
||||
let js = await prettier.format(
|
||||
`
|
||||
import { css } from 'lit';
|
||||
|
||||
|
||||
@@ -210,10 +210,12 @@ describe('<wa-alert>', () => {
|
||||
};
|
||||
|
||||
it('deletes the toast stack after the last alert is done', async () => {
|
||||
const container = await fixture<HTMLElement>(html`<div>
|
||||
<wa-alert data-testid="alert1" closable>alert 1</wa-alert>
|
||||
<wa-alert data-testid="alert2" closable>alert 2</wa-alert>
|
||||
</div>`);
|
||||
const container = await fixture<HTMLElement>(
|
||||
html`<div>
|
||||
<wa-alert data-testid="alert1" closable>alert 1</wa-alert>
|
||||
<wa-alert data-testid="alert2" closable>alert 2</wa-alert>
|
||||
</div>`
|
||||
);
|
||||
|
||||
const alert1 = queryByTestId<WaAlert>(container, 'alert1');
|
||||
const alert2 = queryByTestId<WaAlert>(container, 'alert2');
|
||||
|
||||
@@ -22,6 +22,7 @@ export default css`
|
||||
font-size: calc(var(--size) * 0.5);
|
||||
color: var(--wa-color-neutral-text-on-vivid);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ export default css`
|
||||
white-space: nowrap;
|
||||
padding: 0.35em 0.6em;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,5 +80,6 @@ export default css`
|
||||
align-items: center;
|
||||
margin: 0 var(--wa-space-xs);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -21,11 +21,15 @@ export default css`
|
||||
font-weight: var(--wa-font-weight-action);
|
||||
text-decoration: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
padding: 0;
|
||||
transition: var(--wa-transition-faster) background-color, var(--wa-transition-faster) color,
|
||||
var(--wa-transition-faster) border, var(--wa-transition-faster) box-shadow;
|
||||
transition:
|
||||
var(--wa-transition-faster) background-color,
|
||||
var(--wa-transition-faster) color,
|
||||
var(--wa-transition-faster) border,
|
||||
var(--wa-transition-faster) box-shadow;
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,25 +98,25 @@ describe('<wa-button>', () => {
|
||||
});
|
||||
|
||||
it('should render a link with rel="noreferrer noopener" when target is set and rel is not', async () => {
|
||||
const el = await fixture<WaButton>(
|
||||
html` <wa-button href="https://example.com/" target="_blank">Link</wa-button> `
|
||||
);
|
||||
const el = await fixture<WaButton>(html`
|
||||
<wa-button href="https://example.com/" target="_blank">Link</wa-button>
|
||||
`);
|
||||
const link = el.shadowRoot!.querySelector('a')!;
|
||||
expect(link?.getAttribute('rel')).to.equal('noreferrer noopener');
|
||||
});
|
||||
|
||||
it('should render a link with rel="" when a target is provided and rel is empty', async () => {
|
||||
const el = await fixture<WaButton>(
|
||||
html` <wa-button href="https://example.com/" target="_blank" rel="">Link</wa-button> `
|
||||
);
|
||||
const el = await fixture<WaButton>(html`
|
||||
<wa-button href="https://example.com/" target="_blank" rel="">Link</wa-button>
|
||||
`);
|
||||
const link = el.shadowRoot!.querySelector('a')!;
|
||||
expect(link?.getAttribute('rel')).to.equal('');
|
||||
});
|
||||
|
||||
it(`should render a link with a custom rel when a custom rel is provided`, async () => {
|
||||
const el = await fixture<WaButton>(
|
||||
html` <wa-button href="https://example.com/" target="_blank" rel="1">Link</wa-button> `
|
||||
);
|
||||
const el = await fixture<WaButton>(html`
|
||||
<wa-button href="https://example.com/" target="_blank" rel="1">Link</wa-button>
|
||||
`);
|
||||
const link = el.shadowRoot!.querySelector('a')!;
|
||||
expect(link?.getAttribute('rel')).to.equal('1');
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ export default css`
|
||||
:host {
|
||||
--border-color: var(--wa-color-surface-outline);
|
||||
--border-radius: var(--wa-panel-corners);
|
||||
--border-style: var(--wa-border-style);
|
||||
--border-width: var(--wa-panel-border-width);
|
||||
--padding: var(--wa-space-l);
|
||||
|
||||
@@ -18,7 +19,7 @@ export default css`
|
||||
flex-direction: column;
|
||||
background-color: var(--wa-color-surface-raised);
|
||||
box-shadow: var(--wa-shadow-level-1);
|
||||
border: solid var(--border-width) var(--border-color);
|
||||
border: var(--border-style) var(--border-width) var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
@@ -41,7 +42,7 @@ export default css`
|
||||
|
||||
.card__header {
|
||||
display: block;
|
||||
border-bottom: solid var(--border-width) var(--border-color);
|
||||
border-bottom: var(--border-style) var(--border-width) var(--border-color);
|
||||
padding: calc(var(--padding) / 2) var(--padding);
|
||||
}
|
||||
|
||||
@@ -61,7 +62,7 @@ export default css`
|
||||
|
||||
.card--has-footer .card__footer {
|
||||
display: block;
|
||||
border-top: solid var(--border-width) var(--border-color);
|
||||
border-top: var(--border-style) var(--border-width) var(--border-color);
|
||||
padding: var(--padding);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ describe('<wa-card>', () => {
|
||||
|
||||
describe('when provided no parameters', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaCard>(
|
||||
html` <wa-card>This is just a basic card. No image, no header, and no footer. Just your content.</wa-card> `
|
||||
);
|
||||
el = await fixture<WaCard>(html`
|
||||
<wa-card>This is just a basic card. No image, no header, and no footer. Just your content.</wa-card>
|
||||
`);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
|
||||
@@ -17,10 +17,6 @@ import type { CSSResultGroup } from 'lit';
|
||||
export default class WaCarouselItem extends WebAwesomeElement {
|
||||
static styles: CSSResultGroup = styles;
|
||||
|
||||
static isCarouselItem(node: Node) {
|
||||
return node instanceof Element && node.getAttribute('aria-roledescription') === 'slide';
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.setAttribute('role', 'group');
|
||||
|
||||
@@ -19,8 +19,8 @@ export default css`
|
||||
}
|
||||
|
||||
::slotted(img) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
object-fit: cover;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import '../../internal/scrollend-polyfill.js';
|
||||
|
||||
import { AutoplayController } from './autoplay-controller.js';
|
||||
import { clamp } from '../../internal/math.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
@@ -10,10 +12,10 @@ import { range } from 'lit/directives/range.js';
|
||||
import { ScrollController } from './scroll-controller.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import styles from './carousel.styles.js';
|
||||
import WaCarouselItem from '../carousel-item/carousel-item.component.js';
|
||||
import WaIcon from '../icon/icon.component.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
import type { CSSResultGroup, PropertyValueMap } from 'lit';
|
||||
import type WaCarouselItem from '../carousel-item/carousel-item.component.js';
|
||||
|
||||
/**
|
||||
* @summary Carousels display an arbitrary number of content slides along a horizontal or vertical axis.
|
||||
@@ -40,7 +42,7 @@ import type { CSSResultGroup } from 'lit';
|
||||
* @csspart navigation-button--next - Applied to the next button.
|
||||
*
|
||||
* @cssproperty --slide-gap - The space between each slide.
|
||||
* @cssproperty --aspect-ratio - The aspect ratio of each slide.
|
||||
* @cssproperty [--aspect-ratio=16/9] - The aspect ratio of each slide.
|
||||
* @cssproperty --scroll-hint - The amount of padding to apply to the scroll area, allowing adjacent slides to become
|
||||
* partially visible as a scroll hint.
|
||||
*/
|
||||
@@ -68,7 +70,7 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
|
||||
/**
|
||||
* Specifies the number of slides the carousel will advance when scrolling, useful when specifying a `slides-per-page`
|
||||
* greater than one.
|
||||
* greater than one. It can't be higher than `slides-per-page`.
|
||||
*/
|
||||
@property({ type: Number, attribute: 'slides-per-move' }) slidesPerMove = 1;
|
||||
|
||||
@@ -78,7 +80,6 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
/** When set, it is possible to scroll through the slides by dragging them with the mouse. */
|
||||
@property({ type: Boolean, reflect: true, attribute: 'mouse-dragging' }) mouseDragging = false;
|
||||
|
||||
@query('slot:not([name])') defaultSlot: HTMLSlotElement;
|
||||
@query('.carousel__slides') scrollContainer: HTMLElement;
|
||||
@query('.carousel__pagination') paginationContainer: HTMLElement;
|
||||
|
||||
@@ -87,7 +88,6 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
|
||||
private autoplayController = new AutoplayController(this, () => this.next());
|
||||
private scrollController = new ScrollController(this);
|
||||
private readonly slides = this.getElementsByTagName('wa-carousel-item');
|
||||
private intersectionObserver: IntersectionObserver; // determines which slide is displayed
|
||||
// A map containing the state of all the slides
|
||||
private readonly intersectionObserverEntries = new Map<Element, IntersectionObserverEntry>();
|
||||
@@ -133,19 +133,45 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
protected firstUpdated(): void {
|
||||
this.initializeSlides();
|
||||
this.mutationObserver = new MutationObserver(this.handleSlotChange);
|
||||
this.mutationObserver.observe(this, { childList: true, subtree: false });
|
||||
this.mutationObserver.observe(this, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValueMap<WaCarousel> | Map<PropertyKey, unknown>): void {
|
||||
// Ensure the slidesPerMove is never higher than the slidesPerPage
|
||||
if (changedProperties.has('slidesPerMove') || changedProperties.has('slidesPerPage')) {
|
||||
this.slidesPerMove = Math.min(this.slidesPerMove, this.slidesPerPage);
|
||||
}
|
||||
}
|
||||
|
||||
private getPageCount() {
|
||||
return Math.ceil(this.getSlides().length / this.slidesPerPage);
|
||||
const slidesCount = this.getSlides().length;
|
||||
const { slidesPerPage, slidesPerMove, loop } = this;
|
||||
|
||||
const pages = loop ? slidesCount / slidesPerMove : (slidesCount - slidesPerPage) / slidesPerMove + 1;
|
||||
|
||||
return Math.ceil(pages);
|
||||
}
|
||||
|
||||
private getCurrentPage() {
|
||||
return Math.ceil(this.activeSlide / this.slidesPerPage);
|
||||
return Math.ceil(this.activeSlide / this.slidesPerMove);
|
||||
}
|
||||
|
||||
private canScrollNext(): boolean {
|
||||
return this.loop || this.getCurrentPage() < this.getPageCount() - 1;
|
||||
}
|
||||
|
||||
private canScrollPrev(): boolean {
|
||||
return this.loop || this.getCurrentPage() > 0;
|
||||
}
|
||||
|
||||
/** @internal Gets all carousel items. */
|
||||
private getSlides({ excludeClones = true }: { excludeClones?: boolean } = {}) {
|
||||
return [...this.slides].filter(slide => !excludeClones || !slide.hasAttribute('data-clone'));
|
||||
return [...this.children].filter(
|
||||
(el: HTMLElement) => this.isCarouselItem(el) && (!excludeClones || !el.hasAttribute('data-clone'))
|
||||
) as WaCarouselItem[];
|
||||
}
|
||||
|
||||
private handleKeyDown(event: KeyboardEvent) {
|
||||
@@ -201,20 +227,22 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
|
||||
// Scrolls to the original slide without animating, so the user won't notice that the position has changed
|
||||
this.goToSlide(clonePosition, 'auto');
|
||||
|
||||
return;
|
||||
} else if (firstIntersecting) {
|
||||
// Update the current index based on the first visible slide
|
||||
const slideIndex = slides.indexOf(firstIntersecting.target as WaCarouselItem);
|
||||
// Set the index to the first "snappable" slide
|
||||
this.activeSlide = Math.ceil(slideIndex / this.slidesPerMove) * this.slidesPerMove;
|
||||
}
|
||||
}
|
||||
|
||||
// Activate the first intersecting slide
|
||||
if (firstIntersecting) {
|
||||
this.activeSlide = slides.indexOf(firstIntersecting.target as WaCarouselItem);
|
||||
}
|
||||
private isCarouselItem(node: Node): node is WaCarouselItem {
|
||||
return node instanceof Element && node.tagName.toLowerCase() === 'wa-carousel-item';
|
||||
}
|
||||
|
||||
private handleSlotChange = (mutations: MutationRecord[]) => {
|
||||
const needsInitialization = mutations.some(mutation =>
|
||||
[...mutation.addedNodes, ...mutation.removedNodes].some(
|
||||
node => WaCarouselItem.isCarouselItem(node) && !(node as HTMLElement).hasAttribute('data-clone')
|
||||
(el: HTMLElement) => this.isCarouselItem(el) && !el.hasAttribute('data-clone')
|
||||
)
|
||||
);
|
||||
|
||||
@@ -222,13 +250,13 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
if (needsInitialization) {
|
||||
this.initializeSlides();
|
||||
}
|
||||
|
||||
this.requestUpdate();
|
||||
};
|
||||
|
||||
@watch('loop', { waitUntilFirstUpdate: true })
|
||||
@watch('slidesPerPage', { waitUntilFirstUpdate: true })
|
||||
initializeSlides() {
|
||||
const slides = this.getSlides();
|
||||
const intersectionObserver = this.intersectionObserver;
|
||||
|
||||
this.intersectionObserverEntries.clear();
|
||||
@@ -246,23 +274,11 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
}
|
||||
});
|
||||
|
||||
this.updateSlidesSnap();
|
||||
|
||||
if (this.loop) {
|
||||
// Creates clones to be placed before and after the original elements to simulate infinite scrolling
|
||||
const slidesPerPage = this.slidesPerPage;
|
||||
const lastSlides = slides.slice(-slidesPerPage);
|
||||
const firstSlides = slides.slice(0, slidesPerPage);
|
||||
|
||||
lastSlides.reverse().forEach((slide, i) => {
|
||||
const clone = slide.cloneNode(true) as HTMLElement;
|
||||
clone.setAttribute('data-clone', String(slides.length - i - 1));
|
||||
this.prepend(clone);
|
||||
});
|
||||
|
||||
firstSlides.forEach((slide, i) => {
|
||||
const clone = slide.cloneNode(true) as HTMLElement;
|
||||
clone.setAttribute('data-clone', String(i));
|
||||
this.append(clone);
|
||||
});
|
||||
this.createClones();
|
||||
}
|
||||
|
||||
this.getSlides({ excludeClones: false }).forEach(slide => {
|
||||
@@ -273,6 +289,26 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
this.goToSlide(this.activeSlide, 'auto');
|
||||
}
|
||||
|
||||
private createClones() {
|
||||
const slides = this.getSlides();
|
||||
|
||||
const slidesPerPage = this.slidesPerPage;
|
||||
const lastSlides = slides.slice(-slidesPerPage);
|
||||
const firstSlides = slides.slice(0, slidesPerPage);
|
||||
|
||||
lastSlides.reverse().forEach((slide, i) => {
|
||||
const clone = slide.cloneNode(true) as HTMLElement;
|
||||
clone.setAttribute('data-clone', String(slides.length - i - 1));
|
||||
this.prepend(clone);
|
||||
});
|
||||
|
||||
firstSlides.forEach((slide, i) => {
|
||||
const clone = slide.cloneNode(true) as HTMLElement;
|
||||
clone.setAttribute('data-clone', String(i));
|
||||
this.append(clone);
|
||||
});
|
||||
}
|
||||
|
||||
@watch('activeSlide')
|
||||
handelSlideChange() {
|
||||
const slides = this.getSlides();
|
||||
@@ -292,12 +328,12 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
}
|
||||
|
||||
@watch('slidesPerMove')
|
||||
handleSlidesPerMoveChange() {
|
||||
const slides = this.getSlides({ excludeClones: false });
|
||||
updateSlidesSnap() {
|
||||
const slides = this.getSlides();
|
||||
|
||||
const slidesPerMove = this.slidesPerMove;
|
||||
slides.forEach((slide, i) => {
|
||||
const shouldSnap = Math.abs(i - slidesPerMove) % slidesPerMove === 0;
|
||||
const shouldSnap = (i + slidesPerMove) % slidesPerMove === 0;
|
||||
if (shouldSnap) {
|
||||
slide.style.removeProperty('scroll-snap-align');
|
||||
} else {
|
||||
@@ -325,15 +361,7 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
* @param behavior - The behavior used for scrolling.
|
||||
*/
|
||||
previous(behavior: ScrollBehavior = 'smooth') {
|
||||
let previousIndex = this.activeSlide || this.activeSlide - this.slidesPerMove;
|
||||
let canSnap = false;
|
||||
|
||||
while (!canSnap && previousIndex > 0) {
|
||||
previousIndex -= 1;
|
||||
canSnap = Math.abs(previousIndex - this.slidesPerMove) % this.slidesPerMove === 0;
|
||||
}
|
||||
|
||||
this.goToSlide(previousIndex, behavior);
|
||||
this.goToSlide(this.activeSlide - this.slidesPerMove, behavior);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -357,8 +385,13 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
const slides = this.getSlides();
|
||||
const slidesWithClones = this.getSlides({ excludeClones: false });
|
||||
|
||||
// No need to do anything in case there are no items in the carousel
|
||||
if (!slides.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sets the next index without taking into account clones, if any.
|
||||
const newActiveSlide = (index + slides.length) % slides.length;
|
||||
const newActiveSlide = loop ? (index + slides.length) % slides.length : clamp(index, 0, slides.length - 1);
|
||||
this.activeSlide = newActiveSlide;
|
||||
|
||||
// Get the index of the next slide. For looping carousel it adds `slidesPerPage`
|
||||
@@ -377,11 +410,11 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { scrollController, slidesPerPage } = this;
|
||||
const { scrollController, slidesPerMove } = this;
|
||||
const pagesCount = this.getPageCount();
|
||||
const currentPage = this.getCurrentPage();
|
||||
const prevEnabled = this.loop || currentPage > 0;
|
||||
const nextEnabled = this.loop || currentPage < pagesCount - 1;
|
||||
const prevEnabled = this.canScrollPrev();
|
||||
const nextEnabled = this.canScrollNext();
|
||||
const isLtr = this.localize.dir() === 'ltr';
|
||||
|
||||
return html`
|
||||
@@ -459,7 +492,7 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
aria-selected="${isActive ? 'true' : 'false'}"
|
||||
aria-label="${this.localize.term('goToSlide', index + 1, pagesCount)}"
|
||||
tabindex=${isActive ? '0' : '-1'}
|
||||
@click=${() => this.goToSlide(index * slidesPerPage)}
|
||||
@click=${() => this.goToSlide(index * slidesPerMove)}
|
||||
@keydown=${this.handleKeyDown}
|
||||
></button>
|
||||
`;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import '../../../dist/webawesome.js';
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { expect, fixture, html, oneEvent } from '@open-wc/testing';
|
||||
import { map } from 'lit/directives/map.js';
|
||||
import { range } from 'lit/directives/range.js';
|
||||
import sinon from 'sinon';
|
||||
import type WaCarousel from './carousel.js';
|
||||
|
||||
@@ -223,6 +225,36 @@ describe('<wa-carousel>', () => {
|
||||
// Assert
|
||||
expect(el.scrollContainer.style.getPropertyValue('--slides-per-page').trim()).to.be.equal('2');
|
||||
});
|
||||
|
||||
[
|
||||
[7, 2, 1, false, 6],
|
||||
[5, 3, 3, false, 2],
|
||||
[10, 2, 2, false, 5],
|
||||
[7, 2, 1, true, 7],
|
||||
[5, 3, 3, true, 2],
|
||||
[10, 2, 2, true, 5]
|
||||
].forEach(([slides, slidesPerPage, slidesPerMove, loop, expected]: [number, number, number, boolean, number]) => {
|
||||
it(`should display ${expected} pages for ${slides} slides grouped by ${slidesPerPage} and scrolled by ${slidesPerMove}${
|
||||
loop ? ' (loop)' : ''
|
||||
}`, async () => {
|
||||
// Arrange
|
||||
const el = await fixture<WaCarousel>(html`
|
||||
<wa-carousel
|
||||
pagination
|
||||
navigation
|
||||
slides-per-page="${slidesPerPage}"
|
||||
slides-per-move="${slidesPerMove}"
|
||||
?loop=${loop}
|
||||
>
|
||||
${map(range(slides), i => html`<wa-carousel-item>${i}</wa-carousel-item>`)}
|
||||
</wa-carousel>
|
||||
`);
|
||||
|
||||
// Assert
|
||||
const paginationItems = el.shadowRoot!.querySelectorAll('.carousel__pagination-item');
|
||||
expect(paginationItems.length).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when `slides-per-move` attribute is provided', () => {
|
||||
@@ -230,7 +262,7 @@ describe('<wa-carousel>', () => {
|
||||
// Arrange
|
||||
const expectedSnapGranularity = 2;
|
||||
const el = await fixture<WaCarousel>(html`
|
||||
<wa-carousel slides-per-move="${expectedSnapGranularity}">
|
||||
<wa-carousel slides-per-page="${expectedSnapGranularity}" slides-per-move="${expectedSnapGranularity}">
|
||||
<wa-carousel-item>Node 1</wa-carousel-item>
|
||||
<wa-carousel-item>Node 2</wa-carousel-item>
|
||||
<wa-carousel-item>Node 3</wa-carousel-item>
|
||||
@@ -252,6 +284,89 @@ describe('<wa-carousel>', () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should be possible to move by the given number of slides at a time', async () => {
|
||||
// Arrange
|
||||
const el = await fixture<WaCarousel>(html`
|
||||
<wa-carousel navigation slides-per-move="2" slides-per-page="2">
|
||||
<wa-carousel-item>Node 1</wa-carousel-item>
|
||||
<wa-carousel-item>Node 2</wa-carousel-item>
|
||||
<wa-carousel-item class="expected">Node 3</wa-carousel-item>
|
||||
<wa-carousel-item class="expected">Node 4</wa-carousel-item>
|
||||
<wa-carousel-item>Node 5</wa-carousel-item>
|
||||
<wa-carousel-item>Node 6</wa-carousel-item>
|
||||
</wa-carousel>
|
||||
`);
|
||||
const expectedSlides = el.querySelectorAll('.expected')!;
|
||||
const nextButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--next')!;
|
||||
|
||||
// Act
|
||||
await clickOnElement(nextButton);
|
||||
|
||||
await oneEvent(el.scrollContainer, 'scrollend');
|
||||
await el.updateComplete;
|
||||
|
||||
// Assert
|
||||
for (const expectedSlide of expectedSlides) {
|
||||
expect(expectedSlide).to.have.class('--in-view');
|
||||
expect(expectedSlide).to.be.visible;
|
||||
}
|
||||
});
|
||||
|
||||
it('should be possible to move by a number that is less than the displayed number', async () => {
|
||||
// Arrange
|
||||
const el = await fixture<WaCarousel>(html`
|
||||
<wa-carousel navigation slides-per-move="1" slides-per-page="2">
|
||||
<wa-carousel-item>Node 1</wa-carousel-item>
|
||||
<wa-carousel-item>Node 2</wa-carousel-item>
|
||||
<wa-carousel-item>Node 3</wa-carousel-item>
|
||||
<wa-carousel-item>Node 4</wa-carousel-item>
|
||||
<wa-carousel-item class="expected">Node 5</wa-carousel-item>
|
||||
<wa-carousel-item class="expected">Node 6</wa-carousel-item>
|
||||
</wa-carousel>
|
||||
`);
|
||||
const expectedSlides = el.querySelectorAll('.expected')!;
|
||||
const nextButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--next')!;
|
||||
|
||||
// Act
|
||||
await clickOnElement(nextButton);
|
||||
await clickOnElement(nextButton);
|
||||
await clickOnElement(nextButton);
|
||||
await clickOnElement(nextButton);
|
||||
await clickOnElement(nextButton);
|
||||
await clickOnElement(nextButton);
|
||||
|
||||
await oneEvent(el.scrollContainer, 'scrollend');
|
||||
await el.updateComplete;
|
||||
|
||||
// Assert
|
||||
for (const expectedSlide of expectedSlides) {
|
||||
expect(expectedSlide).to.have.class('--in-view');
|
||||
expect(expectedSlide).to.be.visible;
|
||||
}
|
||||
});
|
||||
|
||||
it('should not be possible to move by a number that is greater than the displayed number', async () => {
|
||||
// Arrange
|
||||
const expectedSlidesPerMove = 2;
|
||||
const el = await fixture<WaCarousel>(html`
|
||||
<wa-carousel slides-per-page="${expectedSlidesPerMove}">
|
||||
<wa-carousel-item>Node 1</wa-carousel-item>
|
||||
<wa-carousel-item>Node 2</wa-carousel-item>
|
||||
<wa-carousel-item>Node 3</wa-carousel-item>
|
||||
<wa-carousel-item>Node 4</wa-carousel-item>
|
||||
<wa-carousel-item>Node 5</wa-carousel-item>
|
||||
<wa-carousel-item>Node 6</wa-carousel-item>
|
||||
</wa-carousel>
|
||||
`);
|
||||
|
||||
// Act
|
||||
el.slidesPerMove = 3;
|
||||
await el.updateComplete;
|
||||
|
||||
// Assert
|
||||
expect(el.slidesPerMove).to.be.equal(expectedSlidesPerMove);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when `orientation` attribute is provided', () => {
|
||||
@@ -465,7 +580,7 @@ describe('<wa-carousel>', () => {
|
||||
it('should scroll the carousel to the next slide', async () => {
|
||||
// Arrange
|
||||
const el = await fixture<WaCarousel>(html`
|
||||
<wa-carousel slides-per-move="2">
|
||||
<wa-carousel slides-per-page="2" slides-per-move="2">
|
||||
<wa-carousel-item>Node 1</wa-carousel-item>
|
||||
<wa-carousel-item>Node 2</wa-carousel-item>
|
||||
<wa-carousel-item>Node 3</wa-carousel-item>
|
||||
@@ -485,7 +600,7 @@ describe('<wa-carousel>', () => {
|
||||
it('should scroll the carousel to the previous slide', async () => {
|
||||
// Arrange
|
||||
const el = await fixture<WaCarousel>(html`
|
||||
<wa-carousel slides-per-move="2">
|
||||
<wa-carousel slides-per-page="2" slides-per-move="2">
|
||||
<wa-carousel-item>Node 1</wa-carousel-item>
|
||||
<wa-carousel-item>Node 2</wa-carousel-item>
|
||||
<wa-carousel-item>Node 3</wa-carousel-item>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { debounce } from '../../internal/debounce.js';
|
||||
import { prefersReducedMotion } from '../../internal/animate.js';
|
||||
import { waitForEvent } from '../../internal/event.js';
|
||||
import type { ReactiveController, ReactiveElement } from 'lit';
|
||||
@@ -12,7 +11,6 @@ interface ScrollHost extends ReactiveElement {
|
||||
*/
|
||||
export class ScrollController<T extends ScrollHost> implements ReactiveController {
|
||||
private host: T;
|
||||
private pointers = new Set();
|
||||
|
||||
dragging = false;
|
||||
scrolling = false;
|
||||
@@ -30,11 +28,10 @@ export class ScrollController<T extends ScrollHost> implements ReactiveControlle
|
||||
const scrollContainer = host.scrollContainer;
|
||||
|
||||
scrollContainer.addEventListener('scroll', this.handleScroll, { passive: true });
|
||||
scrollContainer.addEventListener('scrollend', this.handleScrollEnd, true);
|
||||
scrollContainer.addEventListener('pointerdown', this.handlePointerDown);
|
||||
scrollContainer.addEventListener('pointerup', this.handlePointerUp);
|
||||
scrollContainer.addEventListener('pointercancel', this.handlePointerUp);
|
||||
scrollContainer.addEventListener('touchstart', this.handleTouchStart, { passive: true });
|
||||
scrollContainer.addEventListener('touchend', this.handleTouchEnd);
|
||||
}
|
||||
|
||||
hostDisconnected(): void {
|
||||
@@ -42,11 +39,10 @@ export class ScrollController<T extends ScrollHost> implements ReactiveControlle
|
||||
const scrollContainer = host.scrollContainer;
|
||||
|
||||
scrollContainer.removeEventListener('scroll', this.handleScroll);
|
||||
scrollContainer.removeEventListener('scrollend', this.handleScrollEnd, true);
|
||||
scrollContainer.removeEventListener('pointerdown', this.handlePointerDown);
|
||||
scrollContainer.removeEventListener('pointerup', this.handlePointerUp);
|
||||
scrollContainer.removeEventListener('pointercancel', this.handlePointerUp);
|
||||
scrollContainer.removeEventListener('touchstart', this.handleTouchStart);
|
||||
scrollContainer.removeEventListener('touchend', this.handleTouchEnd);
|
||||
}
|
||||
|
||||
handleScroll = () => {
|
||||
@@ -54,35 +50,22 @@ export class ScrollController<T extends ScrollHost> implements ReactiveControlle
|
||||
this.scrolling = true;
|
||||
this.host.requestUpdate();
|
||||
}
|
||||
this.handleScrollEnd();
|
||||
};
|
||||
|
||||
@debounce(100)
|
||||
handleScrollEnd() {
|
||||
if (!this.pointers.size) {
|
||||
// If no pointer is active in the scroll area then the scroll has ended
|
||||
handleScrollEnd = () => {
|
||||
if (this.scrolling && !this.dragging) {
|
||||
this.scrolling = false;
|
||||
this.host.scrollContainer.dispatchEvent(
|
||||
new CustomEvent('scrollend', {
|
||||
bubbles: false,
|
||||
cancelable: false
|
||||
})
|
||||
);
|
||||
this.host.requestUpdate();
|
||||
} else {
|
||||
// otherwise let's wait a bit more
|
||||
this.handleScrollEnd();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handlePointerDown = (event: PointerEvent) => {
|
||||
// Do not handle drag for touch interactions as scroll is natively supported
|
||||
if (event.pointerType === 'touch') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pointers.add(event.pointerId);
|
||||
|
||||
const canDrag = this.mouseDragging && !this.dragging && event.button === 0;
|
||||
const canDrag = this.mouseDragging && event.button === 0;
|
||||
if (canDrag) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -105,24 +88,9 @@ export class ScrollController<T extends ScrollHost> implements ReactiveControlle
|
||||
};
|
||||
|
||||
handlePointerUp = (event: PointerEvent) => {
|
||||
this.pointers.delete(event.pointerId);
|
||||
this.host.scrollContainer.releasePointerCapture(event.pointerId);
|
||||
|
||||
if (this.pointers.size === 0) {
|
||||
this.handleDragEnd();
|
||||
}
|
||||
};
|
||||
|
||||
handleTouchEnd = (event: TouchEvent) => {
|
||||
for (const touch of event.changedTouches) {
|
||||
this.pointers.delete(touch.identifier);
|
||||
}
|
||||
};
|
||||
|
||||
handleTouchStart = (event: TouchEvent) => {
|
||||
for (const touch of event.touches) {
|
||||
this.pointers.add(touch.identifier);
|
||||
}
|
||||
this.handleDragEnd();
|
||||
};
|
||||
|
||||
handleDragStart() {
|
||||
@@ -140,12 +108,11 @@ export class ScrollController<T extends ScrollHost> implements ReactiveControlle
|
||||
});
|
||||
}
|
||||
|
||||
async handleDragEnd() {
|
||||
handleDragEnd() {
|
||||
const host = this.host;
|
||||
const scrollContainer = host.scrollContainer;
|
||||
|
||||
scrollContainer.removeEventListener('pointermove', this.handlePointerMove);
|
||||
this.dragging = false;
|
||||
|
||||
const startLeft = scrollContainer.scrollLeft;
|
||||
const startTop = scrollContainer.scrollTop;
|
||||
@@ -158,12 +125,16 @@ export class ScrollController<T extends ScrollHost> implements ReactiveControlle
|
||||
scrollContainer.scrollTo({ left: startLeft, top: startTop, behavior: 'auto' });
|
||||
scrollContainer.scrollTo({ left: finalLeft, top: finalTop, behavior: prefersReducedMotion() ? 'auto' : 'smooth' });
|
||||
|
||||
if (this.scrolling) {
|
||||
await waitForEvent(scrollContainer, 'scrollend');
|
||||
}
|
||||
// Wait for scroll to be applied
|
||||
requestAnimationFrame(async () => {
|
||||
if (startLeft !== finalLeft || startTop !== finalTop) {
|
||||
await waitForEvent(scrollContainer, 'scrollend');
|
||||
}
|
||||
|
||||
scrollContainer.style.removeProperty('scroll-snap-type');
|
||||
scrollContainer.style.removeProperty('scroll-snap-type');
|
||||
|
||||
host.requestUpdate();
|
||||
this.dragging = false;
|
||||
host.requestUpdate();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,14 @@ export default css`
|
||||
width: var(--toggle-size);
|
||||
height: var(--toggle-size);
|
||||
border: var(--wa-border-style) var(--wa-form-controls-border-width) var(--wa-form-controls-border-color-resting);
|
||||
border-radius: var(--wa-corners-half);
|
||||
border-radius: min(0.375rem, var(--wa-corners-half)); /* min so it doesn't look like a circle/checkbox */
|
||||
background-color: var(--wa-form-controls-background);
|
||||
color: var(--wa-form-controls-text-color);
|
||||
transition: var(--wa-transition-fast) border-color, var(--wa-transition-fast) background-color,
|
||||
var(--wa-transition-fast) color, var(--wa-transition-fast) box-shadow;
|
||||
transition:
|
||||
var(--wa-transition-fast) border-color,
|
||||
var(--wa-transition-fast) background-color,
|
||||
var(--wa-transition-fast) color,
|
||||
var(--wa-transition-fast) box-shadow;
|
||||
}
|
||||
|
||||
.checkbox__input {
|
||||
@@ -97,6 +100,7 @@ export default css`
|
||||
line-height: var(--toggle-size);
|
||||
margin-inline-start: var(--wa-space-xs);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
:host([required]) .checkbox__label::after {
|
||||
|
||||
@@ -243,7 +243,8 @@ export default class WaColorPicker extends WebAwesomeElement implements WebAweso
|
||||
const container = this.shadowRoot!.querySelector<HTMLElement>('.color-picker__slider.color-picker__alpha')!;
|
||||
const handle = container.querySelector<HTMLElement>('.color-picker__slider-handle')!;
|
||||
const { width } = container.getBoundingClientRect();
|
||||
let oldValue = this.value;
|
||||
let initialValue = this.value;
|
||||
let currentValue = this.value;
|
||||
|
||||
handle.focus();
|
||||
event.preventDefault();
|
||||
@@ -253,12 +254,17 @@ export default class WaColorPicker extends WebAwesomeElement implements WebAweso
|
||||
this.alpha = clamp((x / width) * 100, 0, 100);
|
||||
this.syncValues();
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
oldValue = this.value;
|
||||
this.emit('wa-change');
|
||||
if (this.value !== currentValue) {
|
||||
currentValue = this.value;
|
||||
this.emit('wa-input');
|
||||
}
|
||||
},
|
||||
onStop: () => {
|
||||
if (this.value !== initialValue) {
|
||||
initialValue = this.value;
|
||||
this.emit('wa-change');
|
||||
}
|
||||
},
|
||||
initialEvent: event
|
||||
});
|
||||
}
|
||||
@@ -267,7 +273,8 @@ export default class WaColorPicker extends WebAwesomeElement implements WebAweso
|
||||
const container = this.shadowRoot!.querySelector<HTMLElement>('.color-picker__slider.color-picker__hue')!;
|
||||
const handle = container.querySelector<HTMLElement>('.color-picker__slider-handle')!;
|
||||
const { width } = container.getBoundingClientRect();
|
||||
let oldValue = this.value;
|
||||
let initialValue = this.value;
|
||||
let currentValue = this.value;
|
||||
|
||||
handle.focus();
|
||||
event.preventDefault();
|
||||
@@ -277,12 +284,17 @@ export default class WaColorPicker extends WebAwesomeElement implements WebAweso
|
||||
this.hue = clamp((x / width) * 360, 0, 360);
|
||||
this.syncValues();
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
oldValue = this.value;
|
||||
this.emit('wa-change');
|
||||
if (this.value !== currentValue) {
|
||||
currentValue = this.value;
|
||||
this.emit('wa-input');
|
||||
}
|
||||
},
|
||||
onStop: () => {
|
||||
if (this.value !== initialValue) {
|
||||
initialValue = this.value;
|
||||
this.emit('wa-change');
|
||||
}
|
||||
},
|
||||
initialEvent: event
|
||||
});
|
||||
}
|
||||
@@ -291,7 +303,8 @@ export default class WaColorPicker extends WebAwesomeElement implements WebAweso
|
||||
const grid = this.shadowRoot!.querySelector<HTMLElement>('.color-picker__grid')!;
|
||||
const handle = grid.querySelector<HTMLElement>('.color-picker__grid-handle')!;
|
||||
const { width, height } = grid.getBoundingClientRect();
|
||||
let oldValue = this.value;
|
||||
let initialValue = this.value;
|
||||
let currentValue = this.value;
|
||||
|
||||
handle.focus();
|
||||
event.preventDefault();
|
||||
@@ -304,13 +317,18 @@ export default class WaColorPicker extends WebAwesomeElement implements WebAweso
|
||||
this.brightness = clamp(100 - (y / height) * 100, 0, 100);
|
||||
this.syncValues();
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
oldValue = this.value;
|
||||
this.emit('wa-change');
|
||||
if (this.value !== currentValue) {
|
||||
currentValue = this.value;
|
||||
this.emit('wa-input');
|
||||
}
|
||||
},
|
||||
onStop: () => (this.isDraggingGridHandle = false),
|
||||
onStop: () => {
|
||||
this.isDraggingGridHandle = false;
|
||||
if (this.value !== initialValue) {
|
||||
initialValue = this.value;
|
||||
this.emit('wa-change');
|
||||
}
|
||||
},
|
||||
initialEvent: event
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ export default css`
|
||||
background-color: var(--wa-color-surface-raised);
|
||||
border-radius: var(--wa-corners-1x);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.color-picker--inline {
|
||||
@@ -243,7 +244,11 @@ export default css`
|
||||
linear-gradient(45deg, transparent 75%, var(--wa-color-neutral-fill-muted-alt) 75%),
|
||||
linear-gradient(45deg, var(--wa-color-neutral-fill-muted-alt) 25%, transparent 25%);
|
||||
background-size: 10px 10px;
|
||||
background-position: 0 0, 0 0, -5px -5px, 5px 5px;
|
||||
background-position:
|
||||
0 0,
|
||||
0 0,
|
||||
-5px -5px,
|
||||
5px 5px;
|
||||
}
|
||||
|
||||
.color-picker--disabled {
|
||||
@@ -309,7 +314,9 @@ export default css`
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background-color: currentColor;
|
||||
box-shadow: inset 0 0 0 2px var(--wa-form-controls-border-color-resting), inset 0 0 0 4px var(--wa-color-white);
|
||||
box-shadow:
|
||||
inset 0 0 0 2px var(--wa-form-controls-border-color-resting),
|
||||
inset 0 0 0 4px var(--wa-color-white);
|
||||
}
|
||||
|
||||
.color-dropdown__trigger--empty:before {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import '../../../dist/webawesome.js';
|
||||
import { aTimeout, expect, fixture, html, oneEvent } from '@open-wc/testing';
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { clickOnElement, dragElement } from '../../internal/test.js';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import { serialize } from '../../utilities/form.js';
|
||||
@@ -31,11 +31,21 @@ describe('<wa-color-picker>', () => {
|
||||
|
||||
await clickOnElement(trigger); // open the dropdown
|
||||
await aTimeout(200); // wait for the dropdown to open
|
||||
await clickOnElement(grid); // click on the grid
|
||||
await el.updateComplete;
|
||||
|
||||
// Simulate a drag event. "sl-change" should not fire until we stop dragging.
|
||||
await dragElement(grid, 2, 0, {
|
||||
afterMouseDown: () => {
|
||||
expect(changeHandler).to.have.not.been.called;
|
||||
expect(inputHandler).to.have.been.calledOnce;
|
||||
},
|
||||
afterMouseMove: () => {
|
||||
expect(inputHandler).to.have.been.calledTwice;
|
||||
}
|
||||
});
|
||||
|
||||
expect(changeHandler).to.have.been.calledOnce;
|
||||
expect(inputHandler).to.have.been.calledOnce;
|
||||
expect(inputHandler).to.have.been.calledTwice;
|
||||
});
|
||||
|
||||
it('should emit wa-change and wa-input when the hue slider is moved', async () => {
|
||||
@@ -50,10 +60,23 @@ describe('<wa-color-picker>', () => {
|
||||
|
||||
await clickOnElement(trigger); // open the dropdown
|
||||
await aTimeout(200); // wait for the dropdown to open
|
||||
await clickOnElement(slider); // click on the hue slider
|
||||
|
||||
// Simulate a drag event. "wa-change" should not fire until we stop dragging.
|
||||
await dragElement(slider, 20, 0, {
|
||||
afterMouseDown: () => {
|
||||
expect(changeHandler).to.have.not.been.called;
|
||||
expect(inputHandler).to.have.been.calledOnce;
|
||||
},
|
||||
afterMouseMove: () => {
|
||||
// It's not twice because you can't change the hue of white!
|
||||
expect(inputHandler).to.have.been.calledOnce;
|
||||
}
|
||||
});
|
||||
|
||||
await el.updateComplete;
|
||||
|
||||
expect(changeHandler).to.have.been.calledOnce;
|
||||
// It's not twice because you can't change the hue of white!
|
||||
expect(inputHandler).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
@@ -69,11 +92,22 @@ describe('<wa-color-picker>', () => {
|
||||
|
||||
await clickOnElement(trigger); // open the dropdown
|
||||
await aTimeout(200); // wait for the dropdown to open
|
||||
await clickOnElement(slider); // click on the opacity slider
|
||||
|
||||
// Simulate a drag event. "wa-change" should not fire until we stop dragging.
|
||||
await dragElement(slider, 2, 0, {
|
||||
afterMouseDown: () => {
|
||||
expect(changeHandler).to.have.not.been.called;
|
||||
expect(inputHandler).to.have.been.calledOnce;
|
||||
},
|
||||
afterMouseMove: () => {
|
||||
expect(inputHandler).to.have.been.calledTwice;
|
||||
}
|
||||
});
|
||||
|
||||
await el.updateComplete;
|
||||
|
||||
expect(changeHandler).to.have.been.calledOnce;
|
||||
expect(inputHandler).to.have.been.calledOnce;
|
||||
expect(inputHandler).to.have.been.calledTwice;
|
||||
});
|
||||
|
||||
it('should emit wa-change and wa-input when toggling the format', async () => {
|
||||
@@ -97,9 +131,9 @@ describe('<wa-color-picker>', () => {
|
||||
});
|
||||
|
||||
it('should render the correct swatches when passing a string of color values', async () => {
|
||||
const el = await fixture<WaColorPicker>(
|
||||
html` <wa-color-picker swatches="red; #008000; rgb(0,0,255);"></wa-color-picker> `
|
||||
);
|
||||
const el = await fixture<WaColorPicker>(html`
|
||||
<wa-color-picker swatches="red; #008000; rgb(0,0,255);"></wa-color-picker>
|
||||
`);
|
||||
const swatches = [...el.shadowRoot!.querySelectorAll('[part~="swatch"] > div')];
|
||||
|
||||
expect(swatches.length).to.equal(3);
|
||||
|
||||
@@ -206,9 +206,9 @@ export default class WaCopyButton extends WebAwesomeElement {
|
||||
?disabled=${this.disabled}
|
||||
?hoist=${this.hoist}
|
||||
exportparts="
|
||||
base:tooltip__base
|
||||
base__popup:tooltip__base__popup
|
||||
base__arrow:tooltip__base__arrow
|
||||
base:tooltip__base,
|
||||
base__popup:tooltip__base__popup,
|
||||
base__arrow:tooltip__base__arrow,
|
||||
body:tooltip__body
|
||||
"
|
||||
>
|
||||
|
||||
@@ -24,6 +24,7 @@ export default css`
|
||||
align-items: center;
|
||||
padding: var(--wa-space-m);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -75,7 +76,7 @@ export default css`
|
||||
display: none;
|
||||
}
|
||||
|
||||
.details__body {
|
||||
:not(.details--open) .details__body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,9 @@ import type { CSSResultGroup } from 'lit';
|
||||
* @animation dialog.denyClose - The animation to use when a request to close the dialog is denied.
|
||||
* @animation dialog.overlay.show - The animation to use when showing the dialog's overlay.
|
||||
* @animation dialog.overlay.hide - The animation to use when hiding the dialog's overlay.
|
||||
* @property modal - Exposes the internal modal utility that controls focus trapping. To temporarily disable focus
|
||||
* trapping and allow third-party modals spawned from an active Shoelace modal, call `modal.activateExternal()` when
|
||||
* the third-party modal opens. Upon closing, call `modal.deactivateExternal()` to restore Shoelace's focus trapping.
|
||||
*/
|
||||
export default class WaDialog extends WebAwesomeElement {
|
||||
static styles: CSSResultGroup = styles;
|
||||
@@ -69,8 +72,8 @@ export default class WaDialog extends WebAwesomeElement {
|
||||
|
||||
private readonly hasSlotController = new HasSlotController(this, 'footer');
|
||||
private readonly localize = new LocalizeController(this);
|
||||
private modal = new Modal(this);
|
||||
private originalTrigger: HTMLElement | null;
|
||||
public modal = new Modal(this);
|
||||
|
||||
@query('.dialog') dialog: HTMLElement;
|
||||
@query('.dialog__panel') panel: HTMLElement;
|
||||
|
||||
@@ -17,9 +17,9 @@ describe('<wa-dialog>', () => {
|
||||
});
|
||||
|
||||
it('should not be visible without the open attribute', async () => {
|
||||
const el = await fixture<WaDialog>(
|
||||
html` <wa-dialog>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog> `
|
||||
);
|
||||
const el = await fixture<WaDialog>(html`
|
||||
<wa-dialog>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
|
||||
`);
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
|
||||
expect(base.hidden).to.be.true;
|
||||
|
||||
@@ -68,6 +68,10 @@ import type { CSSResultGroup } from 'lit';
|
||||
* @animation drawer.denyClose - The animation to use when a request to close the drawer is denied.
|
||||
* @animation drawer.overlay.show - The animation to use when showing the drawer's overlay.
|
||||
* @animation drawer.overlay.hide - The animation to use when hiding the drawer's overlay.
|
||||
*
|
||||
* @property modal - Exposes the internal modal utility that controls focus trapping. To temporarily disable focus
|
||||
* trapping and allow third-party modals spawned from an active Shoelace modal, call `modal.activateExternal()` when
|
||||
* the third-party modal opens. Upon closing, call `modal.deactivateExternal()` to restore Shoelace's focus trapping.
|
||||
*/
|
||||
export default class WaDrawer extends WebAwesomeElement {
|
||||
static styles: CSSResultGroup = styles;
|
||||
@@ -75,8 +79,8 @@ export default class WaDrawer extends WebAwesomeElement {
|
||||
|
||||
private readonly hasSlotController = new HasSlotController(this, 'footer');
|
||||
private readonly localize = new LocalizeController(this);
|
||||
private modal = new Modal(this);
|
||||
private originalTrigger: HTMLElement | null;
|
||||
public modal = new Modal(this);
|
||||
|
||||
@query('.drawer') drawer: HTMLElement;
|
||||
@query('.drawer__panel') panel: HTMLElement;
|
||||
|
||||
@@ -16,9 +16,9 @@ describe('<wa-drawer>', () => {
|
||||
});
|
||||
|
||||
it('should not be visible without the open attribute', async () => {
|
||||
const el = await fixture<WaDrawer>(
|
||||
html` <wa-drawer>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-drawer> `
|
||||
);
|
||||
const el = await fixture<WaDrawer>(html`
|
||||
<wa-drawer>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-drawer>
|
||||
`);
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
|
||||
expect(base.hidden).to.be.true;
|
||||
|
||||
@@ -52,11 +52,9 @@ describe('<wa-format-date>', () => {
|
||||
];
|
||||
results.forEach(setup => {
|
||||
it(`date has correct language format: ${setup.lang}`, async () => {
|
||||
const el = await fixture<WaFormatDate>(
|
||||
html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" lang="${setup.lang}"></wa-format-date>
|
||||
`
|
||||
);
|
||||
const el = await fixture<WaFormatDate>(html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" lang="${setup.lang}"></wa-format-date>
|
||||
`);
|
||||
expect(el.shadowRoot?.textContent?.trim()).to.equal(setup.result);
|
||||
});
|
||||
});
|
||||
@@ -66,14 +64,12 @@ describe('<wa-format-date>', () => {
|
||||
const weekdays = ['narrow', 'short', 'long'];
|
||||
weekdays.forEach((weekdayFormat: 'narrow' | 'short' | 'long') => {
|
||||
it(`date has correct weekday format: ${weekdayFormat}`, async () => {
|
||||
const el = await fixture<WaFormatDate>(
|
||||
html`
|
||||
<wa-format-date
|
||||
.date="${new Date(new Date().getFullYear(), 0, 1)}"
|
||||
weekday="${weekdayFormat}"
|
||||
></wa-format-date>
|
||||
`
|
||||
);
|
||||
const el = await fixture<WaFormatDate>(html`
|
||||
<wa-format-date
|
||||
.date="${new Date(new Date().getFullYear(), 0, 1)}"
|
||||
weekday="${weekdayFormat}"
|
||||
></wa-format-date>
|
||||
`);
|
||||
|
||||
const expected = new Intl.DateTimeFormat('en-US', { weekday: weekdayFormat }).format(
|
||||
new Date(new Date().getFullYear(), 0, 1)
|
||||
@@ -87,11 +83,9 @@ describe('<wa-format-date>', () => {
|
||||
const eras = ['narrow', 'short', 'long'];
|
||||
eras.forEach((eraFormat: 'narrow' | 'short' | 'long') => {
|
||||
it(`date has correct era format: ${eraFormat}`, async () => {
|
||||
const el = await fixture<WaFormatDate>(
|
||||
html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" era="${eraFormat}"></wa-format-date>
|
||||
`
|
||||
);
|
||||
const el = await fixture<WaFormatDate>(html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" era="${eraFormat}"></wa-format-date>
|
||||
`);
|
||||
|
||||
const expected = new Intl.DateTimeFormat('en-US', { era: eraFormat }).format(
|
||||
new Date(new Date().getFullYear(), 0, 1)
|
||||
@@ -105,11 +99,9 @@ describe('<wa-format-date>', () => {
|
||||
const yearFormats = ['numeric', '2-digit'];
|
||||
yearFormats.forEach((yearFormat: 'numeric' | '2-digit') => {
|
||||
it(`date has correct year format: ${yearFormat}`, async () => {
|
||||
const el = await fixture<WaFormatDate>(
|
||||
html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" year="${yearFormat}"></wa-format-date>
|
||||
`
|
||||
);
|
||||
const el = await fixture<WaFormatDate>(html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" year="${yearFormat}"></wa-format-date>
|
||||
`);
|
||||
|
||||
const expected = new Intl.DateTimeFormat('en-US', { year: yearFormat }).format(
|
||||
new Date(new Date().getFullYear(), 0, 1)
|
||||
@@ -123,11 +115,9 @@ describe('<wa-format-date>', () => {
|
||||
const monthFormats = ['numeric', '2-digit', 'narrow', 'short', 'long'];
|
||||
monthFormats.forEach((monthFormat: 'numeric' | '2-digit' | 'narrow' | 'short' | 'long') => {
|
||||
it(`date has correct month format: ${monthFormat}`, async () => {
|
||||
const el = await fixture<WaFormatDate>(
|
||||
html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" month="${monthFormat}"></wa-format-date>
|
||||
`
|
||||
);
|
||||
const el = await fixture<WaFormatDate>(html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" month="${monthFormat}"></wa-format-date>
|
||||
`);
|
||||
|
||||
const expected = new Intl.DateTimeFormat('en-US', { month: monthFormat }).format(
|
||||
new Date(new Date().getFullYear(), 0, 1)
|
||||
@@ -141,11 +131,9 @@ describe('<wa-format-date>', () => {
|
||||
const dayFormats = ['numeric', '2-digit'];
|
||||
dayFormats.forEach((dayFormat: 'numeric' | '2-digit') => {
|
||||
it(`date has correct day format: ${dayFormat}`, async () => {
|
||||
const el = await fixture<WaFormatDate>(
|
||||
html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" day="${dayFormat}"></wa-format-date>
|
||||
`
|
||||
);
|
||||
const el = await fixture<WaFormatDate>(html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" day="${dayFormat}"></wa-format-date>
|
||||
`);
|
||||
|
||||
const expected = new Intl.DateTimeFormat('en-US', { day: dayFormat }).format(
|
||||
new Date(new Date().getFullYear(), 0, 1)
|
||||
@@ -159,11 +147,9 @@ describe('<wa-format-date>', () => {
|
||||
const hourFormats = ['numeric', '2-digit'];
|
||||
hourFormats.forEach((hourFormat: 'numeric' | '2-digit') => {
|
||||
it(`date has correct hour format: ${hourFormat}`, async () => {
|
||||
const el = await fixture<WaFormatDate>(
|
||||
html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" hour="${hourFormat}"></wa-format-date>
|
||||
`
|
||||
);
|
||||
const el = await fixture<WaFormatDate>(html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" hour="${hourFormat}"></wa-format-date>
|
||||
`);
|
||||
|
||||
const expected = new Intl.DateTimeFormat('en-US', { hour: hourFormat }).format(
|
||||
new Date(new Date().getFullYear(), 0, 1)
|
||||
@@ -177,14 +163,9 @@ describe('<wa-format-date>', () => {
|
||||
const minuteFormats = ['numeric', '2-digit'];
|
||||
minuteFormats.forEach((minuteFormat: 'numeric' | '2-digit') => {
|
||||
it(`date has correct minute format: ${minuteFormat}`, async () => {
|
||||
const el = await fixture<WaFormatDate>(
|
||||
html`
|
||||
<wa-format-date
|
||||
.date="${new Date(new Date().getFullYear(), 0, 1)}"
|
||||
minute="${minuteFormat}"
|
||||
></wa-format-date>
|
||||
`
|
||||
);
|
||||
const el = await fixture<WaFormatDate>(html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" minute="${minuteFormat}"></wa-format-date>
|
||||
`);
|
||||
|
||||
const expected = new Intl.DateTimeFormat('en-US', { minute: minuteFormat }).format(
|
||||
new Date(new Date().getFullYear(), 0, 1)
|
||||
@@ -198,14 +179,9 @@ describe('<wa-format-date>', () => {
|
||||
const secondFormats = ['numeric', '2-digit'];
|
||||
secondFormats.forEach((secondFormat: 'numeric' | '2-digit') => {
|
||||
it(`date has correct second format: ${secondFormat}`, async () => {
|
||||
const el = await fixture<WaFormatDate>(
|
||||
html`
|
||||
<wa-format-date
|
||||
.date="${new Date(new Date().getFullYear(), 0, 1)}"
|
||||
second="${secondFormat}"
|
||||
></wa-format-date>
|
||||
`
|
||||
);
|
||||
const el = await fixture<WaFormatDate>(html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" second="${secondFormat}"></wa-format-date>
|
||||
`);
|
||||
|
||||
const expected = new Intl.DateTimeFormat('en-US', { second: secondFormat }).format(
|
||||
new Date(new Date().getFullYear(), 0, 1)
|
||||
@@ -219,14 +195,12 @@ describe('<wa-format-date>', () => {
|
||||
const timeZoneNameFormats = ['short', 'long'];
|
||||
timeZoneNameFormats.forEach((timeZoneNameFormat: 'short' | 'long') => {
|
||||
it(`date has correct timeZoneName format: ${timeZoneNameFormat}`, async () => {
|
||||
const el = await fixture<WaFormatDate>(
|
||||
html`
|
||||
<wa-format-date
|
||||
.date="${new Date(new Date().getFullYear(), 0, 1)}"
|
||||
time-zone-name="${timeZoneNameFormat}"
|
||||
></wa-format-date>
|
||||
`
|
||||
);
|
||||
const el = await fixture<WaFormatDate>(html`
|
||||
<wa-format-date
|
||||
.date="${new Date(new Date().getFullYear(), 0, 1)}"
|
||||
time-zone-name="${timeZoneNameFormat}"
|
||||
></wa-format-date>
|
||||
`);
|
||||
|
||||
const expected = new Intl.DateTimeFormat('en-US', { timeZoneName: timeZoneNameFormat }).format(
|
||||
new Date(new Date().getFullYear(), 0, 1)
|
||||
@@ -240,14 +214,9 @@ describe('<wa-format-date>', () => {
|
||||
const timeZones = ['America/New_York', 'America/Los_Angeles', 'Europe/Zurich'];
|
||||
timeZones.forEach(timeZone => {
|
||||
it(`date has correct timeZoneName format: ${timeZone}`, async () => {
|
||||
const el = await fixture<WaFormatDate>(
|
||||
html`
|
||||
<wa-format-date
|
||||
.date="${new Date(new Date().getFullYear(), 0, 1)}"
|
||||
time-zone="${timeZone}"
|
||||
></wa-format-date>
|
||||
`
|
||||
);
|
||||
const el = await fixture<WaFormatDate>(html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" time-zone="${timeZone}"></wa-format-date>
|
||||
`);
|
||||
|
||||
const expected = new Intl.DateTimeFormat('en-US', { timeZone: timeZone }).format(
|
||||
new Date(new Date().getFullYear(), 0, 1)
|
||||
@@ -261,14 +230,12 @@ describe('<wa-format-date>', () => {
|
||||
const hourFormatValues = ['auto', '12', '24'];
|
||||
hourFormatValues.forEach(hourFormatValue => {
|
||||
it(`date has correct hourFormat format: ${hourFormatValue}`, async () => {
|
||||
const el = await fixture<WaFormatDate>(
|
||||
html`
|
||||
<wa-format-date
|
||||
.date="${new Date(new Date().getFullYear(), 0, 1)}"
|
||||
hour-format="${hourFormatValue as 'auto' | '12' | '24'}"
|
||||
></wa-format-date>
|
||||
`
|
||||
);
|
||||
const el = await fixture<WaFormatDate>(html`
|
||||
<wa-format-date
|
||||
.date="${new Date(new Date().getFullYear(), 0, 1)}"
|
||||
hour-format="${hourFormatValue as 'auto' | '12' | '24'}"
|
||||
></wa-format-date>
|
||||
`);
|
||||
|
||||
const expected = new Intl.DateTimeFormat('en-US', {
|
||||
hour12: hourFormatValue === 'auto' ? undefined : hourFormatValue === '12'
|
||||
|
||||
@@ -24,9 +24,9 @@ describe('<wa-format-number>', () => {
|
||||
describe('lang property', () => {
|
||||
['de', 'de-CH', 'fr', 'es', 'he', 'ja', 'nl', 'pl', 'pt', 'ru'].forEach(lang => {
|
||||
it(`number has correct language format: ${lang}`, async () => {
|
||||
const el = await fixture<WaFormatNumber>(
|
||||
html` <wa-format-number value="1000" lang="${lang}"></wa-format-number> `
|
||||
);
|
||||
const el = await fixture<WaFormatNumber>(html`
|
||||
<wa-format-number value="1000" lang="${lang}"></wa-format-number>
|
||||
`);
|
||||
const expected = new Intl.NumberFormat(lang, { style: 'decimal', useGrouping: true }).format(1000);
|
||||
expect(el.shadowRoot?.textContent).to.equal(expected);
|
||||
});
|
||||
@@ -36,9 +36,9 @@ describe('<wa-format-number>', () => {
|
||||
describe('type property', () => {
|
||||
['currency', 'decimal', 'percent'].forEach(type => {
|
||||
it(`number has correct type format: ${type}`, async () => {
|
||||
const el = await fixture<WaFormatNumber>(
|
||||
html` <wa-format-number value="1000" type="${type}"></wa-format-number> `
|
||||
);
|
||||
const el = await fixture<WaFormatNumber>(html`
|
||||
<wa-format-number value="1000" type="${type}"></wa-format-number>
|
||||
`);
|
||||
const expected = new Intl.NumberFormat('en-US', { style: type, currency: 'USD' }).format(1000);
|
||||
expect(el.shadowRoot?.textContent).to.equal(expected);
|
||||
});
|
||||
@@ -62,9 +62,9 @@ describe('<wa-format-number>', () => {
|
||||
describe('currency property', () => {
|
||||
['USD', 'CAD', 'AUD', 'UAH'].forEach(currency => {
|
||||
it(`number has correct type format: ${currency}`, async () => {
|
||||
const el = await fixture<WaFormatNumber>(
|
||||
html` <wa-format-number value="1000" currency="${currency}"></wa-format-number> `
|
||||
);
|
||||
const el = await fixture<WaFormatNumber>(html`
|
||||
<wa-format-number value="1000" currency="${currency}"></wa-format-number>
|
||||
`);
|
||||
const expected = new Intl.NumberFormat('en-US', { style: 'decimal', currency: currency }).format(1000);
|
||||
expect(el.shadowRoot?.textContent).to.equal(expected);
|
||||
});
|
||||
@@ -74,9 +74,9 @@ describe('<wa-format-number>', () => {
|
||||
describe('currencyDisplay property', () => {
|
||||
['symbol', 'narrowSymbol', 'code', 'name'].forEach(currencyDisplay => {
|
||||
it(`number has correct type format: ${currencyDisplay}`, async () => {
|
||||
const el = await fixture<WaFormatNumber>(
|
||||
html` <wa-format-number value="1000" currency-display="${currencyDisplay}"></wa-format-number> `
|
||||
);
|
||||
const el = await fixture<WaFormatNumber>(html`
|
||||
<wa-format-number value="1000" currency-display="${currencyDisplay}"></wa-format-number>
|
||||
`);
|
||||
const expected = new Intl.NumberFormat('en-US', { style: 'decimal', currencyDisplay: currencyDisplay }).format(
|
||||
1000
|
||||
);
|
||||
@@ -88,9 +88,9 @@ describe('<wa-format-number>', () => {
|
||||
describe('minimumIntegerDigits property', () => {
|
||||
[4, 5, 6].forEach(minDigits => {
|
||||
it(`number has correct type format: ${minDigits}`, async () => {
|
||||
const el = await fixture<WaFormatNumber>(
|
||||
html` <wa-format-number value="1000" minimum-integer-digits="${minDigits}"></wa-format-number> `
|
||||
);
|
||||
const el = await fixture<WaFormatNumber>(html`
|
||||
<wa-format-number value="1000" minimum-integer-digits="${minDigits}"></wa-format-number>
|
||||
`);
|
||||
const expected = new Intl.NumberFormat('en-US', {
|
||||
style: 'decimal',
|
||||
currencyDisplay: 'symbol',
|
||||
@@ -104,9 +104,9 @@ describe('<wa-format-number>', () => {
|
||||
describe('minimumFractionDigits property', () => {
|
||||
[4, 5, 6].forEach(minFractionDigits => {
|
||||
it(`number has correct type format: ${minFractionDigits}`, async () => {
|
||||
const el = await fixture<WaFormatNumber>(
|
||||
html` <wa-format-number value="1000" minimum-fraction-digits="${minFractionDigits}"></wa-format-number> `
|
||||
);
|
||||
const el = await fixture<WaFormatNumber>(html`
|
||||
<wa-format-number value="1000" minimum-fraction-digits="${minFractionDigits}"></wa-format-number>
|
||||
`);
|
||||
const expected = new Intl.NumberFormat('en-US', {
|
||||
style: 'decimal',
|
||||
currencyDisplay: 'symbol',
|
||||
@@ -120,9 +120,9 @@ describe('<wa-format-number>', () => {
|
||||
describe('maximumFractionDigits property', () => {
|
||||
[4, 5, 6].forEach(maxFractionDigits => {
|
||||
it(`number has correct type format: ${maxFractionDigits}`, async () => {
|
||||
const el = await fixture<WaFormatNumber>(
|
||||
html` <wa-format-number value="1000" maximum-fraction-digits="${maxFractionDigits}"></wa-format-number> `
|
||||
);
|
||||
const el = await fixture<WaFormatNumber>(html`
|
||||
<wa-format-number value="1000" maximum-fraction-digits="${maxFractionDigits}"></wa-format-number>
|
||||
`);
|
||||
const expected = new Intl.NumberFormat('en-US', {
|
||||
style: 'decimal',
|
||||
currencyDisplay: 'symbol',
|
||||
@@ -136,11 +136,9 @@ describe('<wa-format-number>', () => {
|
||||
describe('minimumSignificantDigits property', () => {
|
||||
[4, 5, 6].forEach(minSignificantDigits => {
|
||||
it(`number has correct type format: ${minSignificantDigits}`, async () => {
|
||||
const el = await fixture<WaFormatNumber>(
|
||||
html`
|
||||
<wa-format-number value="1000" minimum-significant-digits="${minSignificantDigits}"></wa-format-number>
|
||||
`
|
||||
);
|
||||
const el = await fixture<WaFormatNumber>(html`
|
||||
<wa-format-number value="1000" minimum-significant-digits="${minSignificantDigits}"></wa-format-number>
|
||||
`);
|
||||
const expected = new Intl.NumberFormat('en-US', {
|
||||
style: 'decimal',
|
||||
currencyDisplay: 'symbol',
|
||||
@@ -154,11 +152,9 @@ describe('<wa-format-number>', () => {
|
||||
describe('maximumSignificantDigits property', () => {
|
||||
[4, 5, 6].forEach(maxSignificantDigits => {
|
||||
it(`number has correct type format: ${maxSignificantDigits}`, async () => {
|
||||
const el = await fixture<WaFormatNumber>(
|
||||
html`
|
||||
<wa-format-number value="1000" maximum-significant-digits="${maxSignificantDigits}"></wa-format-number>
|
||||
`
|
||||
);
|
||||
const el = await fixture<WaFormatNumber>(html`
|
||||
<wa-format-number value="1000" maximum-significant-digits="${maxSignificantDigits}"></wa-format-number>
|
||||
`);
|
||||
const expected = new Intl.NumberFormat('en-US', {
|
||||
style: 'decimal',
|
||||
currencyDisplay: 'symbol',
|
||||
|
||||
@@ -30,15 +30,13 @@ describe('<wa-icon-button>', () => {
|
||||
|
||||
describe('when styling the host element', () => {
|
||||
it('renders the correct color and font size', async () => {
|
||||
const el = await fixture<WaIconButton>(
|
||||
html`
|
||||
<wa-icon-button
|
||||
library="system"
|
||||
name="check"
|
||||
style="color: rgb(0, 136, 221); font-size: 2rem;"
|
||||
></wa-icon-button>
|
||||
`
|
||||
);
|
||||
const el = await fixture<WaIconButton>(html`
|
||||
<wa-icon-button
|
||||
library="system"
|
||||
name="check"
|
||||
style="color: rgb(0, 136, 221); font-size: 2rem;"
|
||||
></wa-icon-button>
|
||||
`);
|
||||
const icon = el.shadowRoot!.querySelector('wa-icon')!;
|
||||
const styles = getComputedStyle(icon);
|
||||
|
||||
@@ -85,16 +83,16 @@ describe('<wa-icon-button>', () => {
|
||||
describe('and target is present', () => {
|
||||
['_blank', '_parent', '_self', '_top'].forEach((target: LinkTarget) => {
|
||||
it(`the anchor target is the provided target: ${target}`, async () => {
|
||||
const el = await fixture<WaIconButton>(
|
||||
html` <wa-icon-button href="some/path" target="${target}"></wa-icon-button> `
|
||||
);
|
||||
const el = await fixture<WaIconButton>(html`
|
||||
<wa-icon-button href="some/path" target="${target}"></wa-icon-button>
|
||||
`);
|
||||
expect(el.shadowRoot?.querySelector(`a[target="${target}"]`)).to.exist;
|
||||
});
|
||||
|
||||
it(`the anchor rel is set to 'noreferrer noopener'`, async () => {
|
||||
const el = await fixture<WaIconButton>(
|
||||
html` <wa-icon-button href="some/path" target="${target}"></wa-icon-button> `
|
||||
);
|
||||
const el = await fixture<WaIconButton>(html`
|
||||
<wa-icon-button href="some/path" target="${target}"></wa-icon-button>
|
||||
`);
|
||||
expect(el.shadowRoot?.querySelector(`a[rel="noreferrer noopener"]`)).to.exist;
|
||||
});
|
||||
});
|
||||
@@ -103,9 +101,9 @@ describe('<wa-icon-button>', () => {
|
||||
describe('and download is present', () => {
|
||||
it(`the anchor download attribute is the provided download`, async () => {
|
||||
const fakeDownload = 'some/path';
|
||||
const el = await fixture<WaIconButton>(
|
||||
html` <wa-icon-button href="some/path" download="${fakeDownload}"></wa-icon-button> `
|
||||
);
|
||||
const el = await fixture<WaIconButton>(html`
|
||||
<wa-icon-button href="some/path" download="${fakeDownload}"></wa-icon-button>
|
||||
`);
|
||||
|
||||
expect(el.shadowRoot?.querySelector(`a[download="${fakeDownload}"]`)).to.exist;
|
||||
});
|
||||
@@ -121,9 +119,9 @@ describe('<wa-icon-button>', () => {
|
||||
|
||||
it('the internal aria-label attribute is set to the provided label when rendering an anchor', async () => {
|
||||
const fakeLabel = 'some label';
|
||||
const el = await fixture<WaIconButton>(
|
||||
html` <wa-icon-button href="some/path" label="${fakeLabel}"></wa-icon-button> `
|
||||
);
|
||||
const el = await fixture<WaIconButton>(html`
|
||||
<wa-icon-button href="some/path" label="${fakeLabel}"></wa-icon-button>
|
||||
`);
|
||||
expect(el.shadowRoot?.querySelector(`a[aria-label="${fakeLabel}"]`)).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,11 @@ type SVGResult = HTMLTemplateResult | SVGSVGElement | typeof RETRYABLE_ERROR | t
|
||||
let parser: DOMParser;
|
||||
const iconCache = new Map<string, Promise<SVGResult>>();
|
||||
|
||||
interface IconSource {
|
||||
url?: string;
|
||||
fromLibrary: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Icons are symbols that can be used to represent various options within an application.
|
||||
* @documentation https://shoelace.style/components/icon
|
||||
@@ -104,12 +109,19 @@ export default class WaIcon extends WebAwesomeElement {
|
||||
unwatchIcon(this);
|
||||
}
|
||||
|
||||
private getUrl() {
|
||||
private getIconSource(): IconSource {
|
||||
const library = getIconLibrary(this.library);
|
||||
if (this.name && library) {
|
||||
return library.resolver(this.name);
|
||||
return {
|
||||
url: library.resolver(this.name),
|
||||
fromLibrary: true
|
||||
};
|
||||
}
|
||||
return this.src;
|
||||
|
||||
return {
|
||||
url: this.src,
|
||||
fromLibrary: false
|
||||
};
|
||||
}
|
||||
|
||||
@watch('label')
|
||||
@@ -129,8 +141,8 @@ export default class WaIcon extends WebAwesomeElement {
|
||||
|
||||
@watch(['name', 'src', 'library'])
|
||||
async setIcon() {
|
||||
const library = getIconLibrary(this.library);
|
||||
const url = this.getUrl();
|
||||
const { url, fromLibrary } = this.getIconSource();
|
||||
const library = fromLibrary ? getIconLibrary(this.library) : undefined;
|
||||
|
||||
if (!url) {
|
||||
this.svg = null;
|
||||
@@ -154,7 +166,7 @@ export default class WaIcon extends WebAwesomeElement {
|
||||
iconCache.delete(url);
|
||||
}
|
||||
|
||||
if (url !== this.getUrl()) {
|
||||
if (url !== this.getIconSource().url) {
|
||||
// If the url has changed while fetching the icon, ignore this request
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@ export default css`
|
||||
vertical-align: middle;
|
||||
overflow: hidden;
|
||||
cursor: text;
|
||||
transition: var(--wa-transition-fast) border, var(--wa-transition-fast) background-color;
|
||||
transition:
|
||||
var(--wa-transition-fast) border,
|
||||
var(--wa-transition-fast) background-color;
|
||||
}
|
||||
|
||||
/* Standard inputs */
|
||||
@@ -101,6 +103,7 @@ export default css`
|
||||
.input__control::placeholder {
|
||||
color: var(--wa-form-controls-placeholder-color);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.input__control:focus {
|
||||
|
||||
281
src/components/layout/layout.component.ts
Normal file
281
src/components/layout/layout.component.ts
Normal file
@@ -0,0 +1,281 @@
|
||||
import { html } from 'lit';
|
||||
import { live } from 'lit/directives/live.js';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
import styles from './layout.styles.js';
|
||||
import WaDrawer from '../drawer/drawer.component.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import type { CSSResultGroup, PropertyValueMap } from 'lit';
|
||||
|
||||
/**
|
||||
* @summary Layouts offer an easy way to scaffold pages using minimal markup.
|
||||
* @documentation https://shoelace.style/components/layout
|
||||
* @status experimental
|
||||
* @since 3.0
|
||||
*
|
||||
* @slot - The page's main content.
|
||||
* @slot banner - The banner that gets display above the header. The banner will not be shown if no content is provided.
|
||||
* @slot header - The header to display at the top of the page. If a banner is present, the header will appear below the banner. The header will not be shown if there is no content.
|
||||
* @slot subheader - A subheader to display below the `header`. This is a good place to put things like breadcrumbs.
|
||||
* @slot menu - The left side of the page. If you slot an element in here, you will override the default "navigation" slot and will be handling navigation on your own. This also will not disable the fallback behavior of the navigation button. This section "sticks" to the top as the page scrolls.
|
||||
* @slot navigation-header - The header for a navigation area. On mobile this will be the header for `<wa-drawer>`.
|
||||
* @slot navigation - The main content to display in the navigation area.
|
||||
* @slot navigation-footer - The footer for a navigation area. On mobile this will be the footer for `<wa-drawer>`.
|
||||
* @slot main-header - Header to display inline above the main content.
|
||||
* @slot main-footer - Footer to display inline below the main content.
|
||||
* @slot aside - Content to be shown on the right side of the page. Typically contains a table of contents, ads, etc. This section "sticks" to the top as the page scrolls.
|
||||
* @slot skip-to-content - The "skip to content" slot. You can override this If you would like to override the `Skip to content` button and add additional "Skip to X", they can be inserted here.
|
||||
* @slot footer - The content to display in the footer. This is always displayed underneath the viewport so will always make the page "scrollable".
|
||||
*
|
||||
* @csspart base - The component's base wrapper.
|
||||
* @csspart banner - The banner to show above header.
|
||||
* @csspart header - The header, usually for top level navigation / branding.
|
||||
* @csspart subheader - Shown below the header, usually intended for things like breadcrumbs and other page level navigation.
|
||||
* @csspart body - The wrapper around menu, main, and aside.
|
||||
* @csspart menu - The left hand side of the page. Generally intended for navigation.
|
||||
* @csspart main-header - The header above main content.
|
||||
* @csspart main-content - The main content.
|
||||
* @csspart main-footer - The footer below main content.
|
||||
* @csspart aside - The right hand side of the page. Used for things like table of contents, ads, etc.
|
||||
* @csspart skip-to-content - The "skip to content" link that shows when focused.
|
||||
* @csspart nav-button - The default mobile `<sl-icon-button>` displayed on mobile viewports.
|
||||
* @csspart footer - The footer of the page. This is always below the initial viewport.
|
||||
*
|
||||
* @cssproperty [--menu-width=auto] - The width of the layout's "menu" section.
|
||||
* @cssproperty [--main-width=1fr] - The width of the layout's "main" section.
|
||||
* @cssproperty [--aside-width=auto] - The wide of the layout's "aside" section.
|
||||
* @cssproperty [--banner-height=0px] - The height of the banner. This gets calculated when the layout initializes. If the height is known, you can set it here to prevent shifting when the page loads.
|
||||
* @cssproperty [--header-height=0px] - The height of the header. This gets calculated when the layout initializes. If the height is known, you can set it here to prevent shifting when the page loads.
|
||||
* @cssproperty [--subheader-height=0px] - The height of the subheader. This gets calculated when the layout initializes. If the height is known, you can set it here to prevent shifting when the page loads.
|
||||
*/
|
||||
export default class WaLayout extends WebAwesomeElement {
|
||||
static styles: CSSResultGroup = styles;
|
||||
static dependencies = {
|
||||
'wa-drawer': WaDrawer
|
||||
};
|
||||
|
||||
private headerResizeObserver = this.slotResizeObserver('header');
|
||||
private subheaderResizeObserver = this.slotResizeObserver('subheader');
|
||||
private bannerResizeObserver = this.slotResizeObserver('banner');
|
||||
private footerResizeObserver = this.slotResizeObserver('footer');
|
||||
|
||||
private slotResizeObserver(slot: string) {
|
||||
return new ResizeObserver(entries => {
|
||||
for (const entry of entries) {
|
||||
if (entry.contentBoxSize) {
|
||||
const contentBoxSize = entry.borderBoxSize[0];
|
||||
this.style.setProperty(`--${slot}-height`, `${contentBoxSize.blockSize}px`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleNavigationToggle = (e: Event) => {
|
||||
// Don't toggle the nav when we're in desktop mode
|
||||
if (this.view === 'desktop') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.composedPath().find((el: Element) => el?.hasAttribute?.('data-toggle-nav'))) {
|
||||
e.preventDefault();
|
||||
this.toggleNavigation();
|
||||
}
|
||||
};
|
||||
|
||||
@query("[part~='header']") header: HTMLElement;
|
||||
@query("[part~='subheader']") subheader: HTMLElement;
|
||||
@query("[part~='footer']") footer: HTMLElement;
|
||||
@query("[part~='banner']") banner: HTMLElement;
|
||||
@query("[part~='drawer']") navigationDrawer: WaDrawer;
|
||||
|
||||
/**
|
||||
* The view is a reflection of the "mobileBreakpoint", when the layout is larger than the `mobile-breakpoint` (768 by
|
||||
* default), it is considered to be a "desktop" view. The view is merely a way to distinguish when to show/hide the
|
||||
* navigation. You can use additional media queries to make other adjustments to content as necessary.
|
||||
*/
|
||||
@property({ attribute: 'view', reflect: true }) view: 'mobile' | 'desktop' = 'mobile';
|
||||
|
||||
/**
|
||||
* Whether or not the navigation drawer is open. Note, the navigation drawer is only "open" on mobile views.
|
||||
*/
|
||||
@property({ attribute: 'nav-open', reflect: true, type: Boolean }) navOpen = false;
|
||||
|
||||
/**
|
||||
* At what "px" to hide the "menu" slot and collapse into a hamburger button
|
||||
*/
|
||||
@property({ attribute: 'mobile-breakpoint' }) mobileBreakpoint = 768;
|
||||
|
||||
/**
|
||||
* Where to place the navigation when in the mobile viewport.
|
||||
*/
|
||||
@property({ attribute: 'navigation-placement', reflect: true }) navigationPlacement: 'start' | 'end' = 'start';
|
||||
|
||||
layoutResizeObserver = new ResizeObserver(entries => {
|
||||
for (const entry of entries) {
|
||||
if (entry.contentBoxSize) {
|
||||
const contentBoxSize = entry.borderBoxSize[0];
|
||||
const layoutWidth = contentBoxSize.inlineSize;
|
||||
|
||||
const oldView = this.view;
|
||||
|
||||
if (layoutWidth >= this.mobileBreakpoint) {
|
||||
this.view = 'desktop';
|
||||
} else {
|
||||
this.view = 'mobile';
|
||||
}
|
||||
|
||||
this.requestUpdate('view', oldView);
|
||||
|
||||
this.style.setProperty(`--layout-width`, `${layoutWidth}px`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
protected update(changedProperties: PropertyValueMap<this> | Map<PropertyKey, unknown>): void {
|
||||
if (changedProperties.has('view')) {
|
||||
this.hideNavigation();
|
||||
}
|
||||
super.update(changedProperties);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.addEventListener('click', this.handleNavigationToggle);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
this.layoutResizeObserver.observe(this);
|
||||
|
||||
setTimeout(() => {
|
||||
this.headerResizeObserver.observe(this.header);
|
||||
this.subheaderResizeObserver.observe(this.subheader);
|
||||
this.bannerResizeObserver.observe(this.banner);
|
||||
this.footerResizeObserver.observe(this.footer);
|
||||
});
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
// If the user provides a #main-content id, it should be present in the default slot and the "skip to
|
||||
// content" link will point to it. If not, we'll prepend an empty element for them so things just work.
|
||||
if (!document.getElementById('main-content')) {
|
||||
const div = document.createElement('div');
|
||||
div.id = 'main-content';
|
||||
div.slot = 'skip-to-content-target';
|
||||
this.prepend(div);
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.layoutResizeObserver.unobserve(this);
|
||||
this.headerResizeObserver.unobserve(this.header);
|
||||
this.subheaderResizeObserver.unobserve(this.subheader);
|
||||
this.footerResizeObserver.unobserve(this.footer);
|
||||
this.bannerResizeObserver.unobserve(this.banner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the mobile navigation drawer
|
||||
*/
|
||||
showNavigation() {
|
||||
this.navOpen = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the mobile navigation drawer
|
||||
*/
|
||||
hideNavigation() {
|
||||
this.navOpen = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the mobile navigation drawer
|
||||
*/
|
||||
toggleNavigation() {
|
||||
this.navOpen = !this.navOpen;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<a href="#main-content" part="skip-to-content" class="skip-to-content">
|
||||
<slot name="skip-to-content">Skip to content</slot>
|
||||
</a>
|
||||
|
||||
<div class="base" part="base">
|
||||
<div class="banner" part="banner">
|
||||
<slot name="banner"></slot>
|
||||
</div>
|
||||
|
||||
<div class="header" part="header">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
|
||||
<div class="subheader" part="subheader">
|
||||
<slot name="subheader"></slot>
|
||||
</div>
|
||||
|
||||
<div class="body" part="body">
|
||||
<div class="menu" part="menu">
|
||||
<slot name="menu">
|
||||
<nav name="navigation" class="navigation" part="navigation navigation-desktop">
|
||||
<slot name=${this.view === 'desktop' ? 'navigation-header' : '___'}></slot>
|
||||
<slot name=${this.view === 'desktop' ? 'navigation' : '____'}></slot>
|
||||
<slot name=${this.view === 'desktop' ? 'navigation-footer' : '___'}></slot>
|
||||
</nav>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<div class="main" part="main">
|
||||
<div class="main-header" part="main-header">
|
||||
<slot name="main-header"></slot>
|
||||
</div>
|
||||
|
||||
<div class="main-content" part="main-content">
|
||||
<slot name="skip-to-content-target"></slot>
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<div class="main-footer" part="main-footer">
|
||||
<slot name="main-footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="aside" part="aside">
|
||||
<slot name="aside"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer" part="footer">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<wa-drawer
|
||||
placement=${this.navigationPlacement}
|
||||
part="drawer"
|
||||
?open=${live(this.navOpen)}
|
||||
@wa-after-show=${() => (this.navOpen = this.navigationDrawer.open)}
|
||||
@wa-after-hide=${() => (this.navOpen = this.navigationDrawer.open)}
|
||||
exportparts="
|
||||
panel:drawer__panel
|
||||
base:drawer__base
|
||||
overlay:drawer__overlay
|
||||
panel:drawer__panel
|
||||
header:drawer__header
|
||||
header-actions:drawer__header-actions
|
||||
title:drawer__title
|
||||
close-button:drawer__close-button
|
||||
close-button__base:drawer__close-button__base
|
||||
body:drawer__body
|
||||
footer:drawer__footer
|
||||
"
|
||||
class="navigation-drawer"
|
||||
>
|
||||
<slot slot="label" name=${this.view === 'mobile' ? 'navigation-header' : '___'}></slot>
|
||||
<slot name=${this.view === 'mobile' ? 'navigation' : '____'}></slot>
|
||||
<slot slot="footer" name=${this.view === 'mobile' ? 'navigation-footer' : '___'}></slot>
|
||||
</wa-drawer>
|
||||
`;
|
||||
}
|
||||
}
|
||||
202
src/components/layout/layout.styles.ts
Normal file
202
src/components/layout/layout.styles.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
|
||||
--menu-width: auto;
|
||||
--main-width: 1fr;
|
||||
--aside-width: auto;
|
||||
|
||||
--banner-height: 0px;
|
||||
--header-height: 0px;
|
||||
--subheader-height: 0px;
|
||||
}
|
||||
|
||||
:host([disable-sticky~='banner']) :is([part~='header'], [part~='subheader']) {
|
||||
--banner-height: 0px !important;
|
||||
}
|
||||
|
||||
:host([disable-sticky~='header']) [part~='subheader'] {
|
||||
--header-height: 0px !important;
|
||||
}
|
||||
|
||||
/* Nothing else depends on subheader-height. */
|
||||
:host([disable-sticky~='subheader']) {
|
||||
}
|
||||
|
||||
:host([disable-sticky~='aside']) [part~='aside'],
|
||||
:host([disable-sticky~='menu']) [part~='menu'] {
|
||||
height: unset;
|
||||
max-height: unset;
|
||||
}
|
||||
|
||||
:host([disable-sticky~='banner']) [part~='banner'],
|
||||
:host([disable-sticky~='header']) [part~='header'],
|
||||
:host([disable-sticky~='subheader']) [part~='subheader'],
|
||||
:host([disable-sticky~='aside']) [part~='aside'],
|
||||
:host([disable-sticky~='menu']) [part~='menu'] {
|
||||
position: static;
|
||||
overflow: unset;
|
||||
}
|
||||
|
||||
:host([disable-sticky~='aside']) [part~='aside'],
|
||||
:host([disable-sticky~='menu']) [part~='menu'] {
|
||||
height: auto;
|
||||
max-height: auto;
|
||||
}
|
||||
|
||||
/* Hide nav toggles in desktop view */
|
||||
:host([view='desktop']) ::slotted([data-toggle-nav]) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
[part~='base'] {
|
||||
min-height: 100%;
|
||||
display: grid;
|
||||
grid-template-rows: repeat(3, minmax(0, auto)) minmax(0, 1fr) minmax(0, auto);
|
||||
grid-template-columns: 100%;
|
||||
width: 100%;
|
||||
grid-template-areas:
|
||||
'banner'
|
||||
'header'
|
||||
'subheader'
|
||||
'body'
|
||||
'footer';
|
||||
}
|
||||
|
||||
/* Grid areas */
|
||||
[part~='banner'] {
|
||||
grid-area: banner;
|
||||
}
|
||||
|
||||
[part~='header'] {
|
||||
grid-area: header;
|
||||
}
|
||||
|
||||
[part~='subheader'] {
|
||||
grid-area: subheader;
|
||||
}
|
||||
|
||||
[part~='menu'] {
|
||||
grid-area: menu;
|
||||
}
|
||||
|
||||
[part~='body'] {
|
||||
grid-area: body;
|
||||
}
|
||||
|
||||
[part~='main'] {
|
||||
grid-area: main;
|
||||
}
|
||||
|
||||
[part~='aside'] {
|
||||
grid-area: aside;
|
||||
}
|
||||
|
||||
[part~='footer'] {
|
||||
grid-area: footer;
|
||||
}
|
||||
|
||||
/* Z-indexes */
|
||||
[part~='banner'],
|
||||
[part~='header'],
|
||||
[part~='subheader'] {
|
||||
position: sticky;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
[part~='banner'] {
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
[part~='header'] {
|
||||
top: var(--banner-height);
|
||||
}
|
||||
|
||||
[part~='subheader'] {
|
||||
top: calc(var(--header-height) + var(--banner-height));
|
||||
}
|
||||
|
||||
[part~='body'] {
|
||||
display: grid;
|
||||
height: 100%;
|
||||
align-items: flex-start;
|
||||
grid-template-columns: minmax(0, var(--menu-width)) minmax(0, var(--main-width)) minmax(0, var(--aside-width));
|
||||
grid-template-rows: minmax(0, 1fr);
|
||||
grid-template-areas: 'menu main aside';
|
||||
}
|
||||
|
||||
[part~='main'] {
|
||||
display: grid;
|
||||
min-height: 100%;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-rows: minmax(0, auto) minmax(0, 1fr) minmax(0, auto);
|
||||
grid-template-areas:
|
||||
'main-header'
|
||||
'main-content'
|
||||
'main-footer';
|
||||
}
|
||||
|
||||
[part~='main-header'] {
|
||||
grid-area: main-header;
|
||||
}
|
||||
|
||||
[part~='main-content'] {
|
||||
grid-area: main-content;
|
||||
}
|
||||
|
||||
[part~='main-footer'] {
|
||||
grid-area: main-footer;
|
||||
}
|
||||
|
||||
/* Visually hidden */
|
||||
.skip-to-content:not(:focus-within) {
|
||||
position: absolute !important;
|
||||
width: 1px !important;
|
||||
height: 1px !important;
|
||||
clip: rect(0 0 0 0) !important;
|
||||
clip-path: inset(50%) !important;
|
||||
border: none !important;
|
||||
overflow: hidden !important;
|
||||
white-space: nowrap !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.skip-to-content {
|
||||
position: absolute;
|
||||
top: var(--wa-space-m);
|
||||
left: var(--wa-space-m);
|
||||
z-index: 6;
|
||||
border-radius: var(--wa-corners-1x);
|
||||
background-color: var(--wa-color-surface-default);
|
||||
color: var(--wa-color-text-link);
|
||||
text-decoration: none;
|
||||
padding: var(--wa-space-s) var(--wa-space-m);
|
||||
box-shadow: var(--wa-shadow-level-3);
|
||||
outline: var(--wa-focus-ring);
|
||||
outline-offset: var(--wa-focus-ring-offset);
|
||||
}
|
||||
|
||||
[part~='menu'],
|
||||
[part~='aside'] {
|
||||
position: sticky;
|
||||
top: calc(var(--banner-height) + var(--header-height) + var(--subheader-height));
|
||||
z-index: 4;
|
||||
height: calc(100dvh - var(--header-height) - var(--banner-height) - var(--subheader-height));
|
||||
max-height: calc(100dvh - var(--header-height) - var(--banner-height) - var(--subheader-height));
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
[part~='navigation'] {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-rows: minmax(0, auto) minmax(0, 1fr) minmax(0, auto);
|
||||
}
|
||||
`;
|
||||
10
src/components/layout/layout.test.ts
Normal file
10
src/components/layout/layout.test.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import '../../../dist/webawesome.js';
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
|
||||
describe('<wa-layout>', () => {
|
||||
it('should render a component', async () => {
|
||||
const el = await fixture(html` <sl-layout></sl-layout> `);
|
||||
|
||||
expect(el).to.exist;
|
||||
});
|
||||
});
|
||||
10
src/components/layout/layout.ts
Normal file
10
src/components/layout/layout.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import WaLayout from './layout.component.js';
|
||||
export * from './layout.component.js';
|
||||
export default WaLayout;
|
||||
WaLayout.define('wa-layout');
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'wa-layout': WaLayout;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,14 @@ export default css`
|
||||
:host {
|
||||
--submenu-offset: -2px;
|
||||
|
||||
/* Private */
|
||||
--safe-triangle-cursor-x: 0;
|
||||
--safe-triangle-cursor-y: 0;
|
||||
--safe-triangle-submenu-start-x: 0;
|
||||
--safe-triangle-submenu-start-y: 0;
|
||||
--safe-triangle-submenu-end-x: 0;
|
||||
--safe-triangle-submenu-end-y: 0;
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -23,6 +31,7 @@ export default css`
|
||||
padding: var(--wa-space-2xs) var(--wa-space-2xs);
|
||||
transition: var(--wa-transition-fast) fill;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -60,6 +69,22 @@ export default css`
|
||||
margin-inline-start: var(--wa-space-xs);
|
||||
}
|
||||
|
||||
/* Safe triangle */
|
||||
.menu-item--submenu-expanded::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
z-index: calc(var(--wa-z-index-dropdown) - 1);
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
clip-path: polygon(
|
||||
var(--safe-triangle-cursor-x) var(--safe-triangle-cursor-y),
|
||||
var(--safe-triangle-submenu-start-x) var(--safe-triangle-submenu-start-y),
|
||||
var(--safe-triangle-submenu-end-x) var(--safe-triangle-submenu-end-y)
|
||||
);
|
||||
}
|
||||
|
||||
:host(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ describe('<wa-menu-item>', () => {
|
||||
});
|
||||
menu.addEventListener('wa-select', selectHandler);
|
||||
|
||||
const submenu = menu.querySelector('wa-menu-item');
|
||||
const submenu = menu.querySelector<WaMenuItem>('wa-menu-item')!;
|
||||
submenu.focus();
|
||||
await menu.updateComplete;
|
||||
await sendKeys({ press: 'ArrowRight' });
|
||||
@@ -145,7 +145,7 @@ describe('<wa-menu-item>', () => {
|
||||
it('should focus on outer menu if ArrowRight is pressed on nested menuitem', async () => {
|
||||
const menu = await fixture<WaMenuItem>(html`
|
||||
<wa-menu>
|
||||
<wa-menu-item value="outer-item-1">
|
||||
<wa-menu-item id="outer" value="outer-item-1">
|
||||
Submenu
|
||||
<wa-menu slot="submenu">
|
||||
<wa-menu-item value="inner-item-1"> Nested Item 1 </wa-menu-item>
|
||||
@@ -155,11 +155,13 @@ describe('<wa-menu-item>', () => {
|
||||
`);
|
||||
|
||||
const focusHandler = sinon.spy((event: FocusEvent) => {
|
||||
expect(event.target.value).to.equal('outer-item-1');
|
||||
expect(event.relatedTarget.value).to.equal('inner-item-1');
|
||||
const target = event.target as WaMenuItem;
|
||||
const relatedTarget = event.relatedTarget as WaMenuItem;
|
||||
expect(target.value).to.equal('outer-item-1');
|
||||
expect(relatedTarget.value).to.equal('inner-item-1');
|
||||
});
|
||||
|
||||
const outerItem = menu.querySelector('wa-menu-item');
|
||||
const outerItem = menu.querySelector<WaMenuItem>('#outer')!;
|
||||
outerItem.focus();
|
||||
await menu.updateComplete;
|
||||
await sendKeys({ press: 'ArrowRight' });
|
||||
|
||||
@@ -49,6 +49,7 @@ export class SubmenuController implements ReactiveController {
|
||||
|
||||
private addListeners() {
|
||||
if (!this.isConnected) {
|
||||
this.host.addEventListener('mousemove', this.handleMouseMove);
|
||||
this.host.addEventListener('mouseover', this.handleMouseOver);
|
||||
this.host.addEventListener('keydown', this.handleKeyDown);
|
||||
this.host.addEventListener('click', this.handleClick);
|
||||
@@ -61,6 +62,7 @@ export class SubmenuController implements ReactiveController {
|
||||
if (!this.isPopupConnected) {
|
||||
if (this.popupRef.value) {
|
||||
this.popupRef.value.addEventListener('mouseover', this.handlePopupMouseover);
|
||||
this.popupRef.value.addEventListener('wa-reposition', this.handlePopupReposition);
|
||||
this.isPopupConnected = true;
|
||||
}
|
||||
}
|
||||
@@ -68,6 +70,7 @@ export class SubmenuController implements ReactiveController {
|
||||
|
||||
private removeListeners() {
|
||||
if (this.isConnected) {
|
||||
this.host.removeEventListener('mousemove', this.handleMouseMove);
|
||||
this.host.removeEventListener('mouseover', this.handleMouseOver);
|
||||
this.host.removeEventListener('keydown', this.handleKeyDown);
|
||||
this.host.removeEventListener('click', this.handleClick);
|
||||
@@ -77,11 +80,18 @@ export class SubmenuController implements ReactiveController {
|
||||
if (this.isPopupConnected) {
|
||||
if (this.popupRef.value) {
|
||||
this.popupRef.value.removeEventListener('mouseover', this.handlePopupMouseover);
|
||||
this.popupRef.value.removeEventListener('wa-reposition', this.handlePopupReposition);
|
||||
this.isPopupConnected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the safe triangle cursor position
|
||||
private handleMouseMove = (event: MouseEvent) => {
|
||||
this.host.style.setProperty('--safe-triangle-cursor-x', `${event.clientX}px`);
|
||||
this.host.style.setProperty('--safe-triangle-cursor-y', `${event.clientY}px`);
|
||||
};
|
||||
|
||||
private handleMouseOver = () => {
|
||||
if (this.hasSlotController.test('submenu')) {
|
||||
this.enableSubmenu();
|
||||
@@ -188,6 +198,24 @@ export class SubmenuController implements ReactiveController {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
// Set the safe triangle values for the submenu when the position changes
|
||||
private handlePopupReposition = () => {
|
||||
const submenuSlot: HTMLSlotElement | null = this.host.renderRoot.querySelector("slot[name='submenu']");
|
||||
const menu = submenuSlot?.assignedElements({ flatten: true }).filter(el => el.localName === 'wa-menu')[0];
|
||||
const isRtl = this.localize.dir() === 'rtl';
|
||||
|
||||
if (!menu) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { left, top, width, height } = menu.getBoundingClientRect();
|
||||
|
||||
this.host.style.setProperty('--safe-triangle-submenu-start-x', `${isRtl ? left + width : left}px`);
|
||||
this.host.style.setProperty('--safe-triangle-submenu-start-y', `${top}px`);
|
||||
this.host.style.setProperty('--safe-triangle-submenu-end-x', `${isRtl ? left + width : left}px`);
|
||||
this.host.style.setProperty('--safe-triangle-submenu-end-y', `${top + height}px`);
|
||||
};
|
||||
|
||||
private setSubmenuState(state: boolean) {
|
||||
if (this.popupRef.value) {
|
||||
if (this.popupRef.value.active !== state) {
|
||||
|
||||
@@ -14,5 +14,6 @@ export default css`
|
||||
color: var(--wa-color-neutral-text-on-surface);
|
||||
padding: var(--wa-space-2xs) var(--wa-space-xl);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
`;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user