mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-13 04:29:14 +00:00
Compare commits
84 Commits
hints
...
layouts-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1329d4475a | ||
|
|
f5448f3f09 | ||
|
|
b33efe697b | ||
|
|
306911d663 | ||
|
|
c0e49cc4ab | ||
|
|
64ac34447c | ||
|
|
667e78243c | ||
|
|
93883537a0 | ||
|
|
900d4b74e3 | ||
|
|
c371dae683 | ||
|
|
ecf2da5201 | ||
|
|
5383411572 | ||
|
|
af3958cc63 | ||
|
|
30561d565b | ||
|
|
4340f89830 | ||
|
|
9a46c29072 | ||
|
|
55bc5f0432 | ||
|
|
dc7c68d6c5 | ||
|
|
213bde0f7f | ||
|
|
c195b9f444 | ||
|
|
58a9f04623 | ||
|
|
b134911703 | ||
|
|
0fa4810ddb | ||
|
|
1f09a53fab | ||
|
|
2d95eedbac | ||
|
|
26d83f37d6 | ||
|
|
a751053803 | ||
|
|
44a7dd5089 | ||
|
|
902ab85f32 | ||
|
|
21647001a4 | ||
|
|
a0ee2256c4 | ||
|
|
51f5c30526 | ||
|
|
9e0aa9c2ec | ||
|
|
15a9c63040 | ||
|
|
a1a09a2b2b | ||
|
|
1df6afa541 | ||
|
|
d4e2abe218 | ||
|
|
ac94d838f0 | ||
|
|
23a0f4afdc | ||
|
|
00f23c485d | ||
|
|
7a31446162 | ||
|
|
11893fe80c | ||
|
|
e23adf4d11 | ||
|
|
739a6033af | ||
|
|
ed43baa459 | ||
|
|
9cbc27a6ef | ||
|
|
b017c4df1e | ||
|
|
27aaa82a9c | ||
|
|
57194ed12d | ||
|
|
74d5b4c3f4 | ||
|
|
e95bfab77b | ||
|
|
78785b872d | ||
|
|
6f0c41cddf | ||
|
|
52bda73657 | ||
|
|
aaf845c72a | ||
|
|
fc66179dc0 | ||
|
|
67702b8d89 | ||
|
|
81ff1422e8 | ||
|
|
27984299e0 | ||
|
|
0cac319988 | ||
|
|
0509fb041f | ||
|
|
32bc7f2207 | ||
|
|
7c73b6a458 | ||
|
|
6b579a6946 | ||
|
|
896fe76a8d | ||
|
|
ed1621410b | ||
|
|
29e591e69b | ||
|
|
396e632679 | ||
|
|
316f8eb16d | ||
|
|
131b1ee57e | ||
|
|
75004768bb | ||
|
|
c8067674f6 | ||
|
|
caf4dc5526 | ||
|
|
93841348e1 | ||
|
|
8ad392a5ac | ||
|
|
6fc8a5166e | ||
|
|
0e27e1dd3d | ||
|
|
1b33f38280 | ||
|
|
340869fa91 | ||
|
|
06ff11114a | ||
|
|
ebe30a5ce8 | ||
|
|
4c84dec601 | ||
|
|
ece156de0b | ||
|
|
191f7d708c |
@@ -171,6 +171,7 @@
|
||||
"valpha",
|
||||
"valuenow",
|
||||
"valuetext",
|
||||
"viewports",
|
||||
"WCAG",
|
||||
"webawesome",
|
||||
"WEBP",
|
||||
|
||||
@@ -61,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>
|
||||
|
||||
|
||||
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 -->
|
||||
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>
|
||||
@@ -6,6 +6,7 @@
|
||||
<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');
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
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 |
@@ -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 */
|
||||
@@ -650,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 */
|
||||
@@ -795,7 +793,6 @@ html.sidebar-open #menu-toggle {
|
||||
}
|
||||
|
||||
.component-header__tag {
|
||||
margin-top: -0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
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 %}
|
||||
@@ -49,7 +49,7 @@ toc: false
|
||||
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);
|
||||
@@ -167,7 +167,7 @@ toc: false
|
||||
<wa-alert variant="warning" open>
|
||||
<wa-icon slot="icon" name="check-circle-fill"></wa-icon>
|
||||
It's a trap!
|
||||
</wa-alert>
|
||||
</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>
|
||||
@@ -226,7 +226,7 @@ toc: false
|
||||
padding: 0 var(--wa-space-m);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
.overlap::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
@@ -264,7 +264,7 @@ toc: false
|
||||
}
|
||||
|
||||
.overlap .image #fighters {
|
||||
fill: color-mix(in oklab, var(--wa-color-brand-fill-vivid), black 30%);
|
||||
fill: color-mix(in oklab, var(--wa-color-brand-fill-vivid), black 30%);
|
||||
}
|
||||
|
||||
.overlap .image #upper_clouds {
|
||||
@@ -295,6 +295,7 @@ toc: false
|
||||
margin-block-start: var(--wa-space-m);
|
||||
}
|
||||
|
||||
.cards wa-card::part(body),
|
||||
.cards wa-card::part(base) {
|
||||
height: 100%;
|
||||
}
|
||||
@@ -303,6 +304,11 @@ toc: false
|
||||
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) {
|
||||
|
||||
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" %}
|
||||
|
||||
@@ -46,8 +46,8 @@
|
||||
"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",
|
||||
"create": "plop --plopfile scripts/plop/plopfile.js",
|
||||
|
||||
@@ -247,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',
|
||||
@@ -328,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
|
||||
|
||||
@@ -76,7 +76,7 @@ export default css`
|
||||
display: none;
|
||||
}
|
||||
|
||||
.details__body {
|
||||
:not(.details--open) .details__body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
50
src/components/nav-group/nav-group.component.ts
Normal file
50
src/components/nav-group/nav-group.component.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { html } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import styles from './nav-group.styles.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
* @summary A nav group is a way of grouping `<wa-nav-item>`s together.
|
||||
* @documentation https://shoelace.style/components/nav-group
|
||||
* @status experimental
|
||||
* @since 3.0
|
||||
*
|
||||
* @csspart base - The base wrapper div around the content
|
||||
* @csspart nav-items - The wrapper around the default slot where nav items are placed.
|
||||
* @csspart details - When `<wa-nav-group>` is expandable, wraps the nav-items in a `<wa-details>`.
|
||||
* @csspart details__base - Maps to the `wa-details::part(base)`
|
||||
* @csspart details__header - Maps to the `wa-details::part(header)`
|
||||
* @csspart details__summary - Maps to the `wa-details::part(summary)`
|
||||
* @csspart details__summary-icon - Maps to the `wa-details::part(summary-icon)`
|
||||
* @csspart details__content - Maps to the `wa-details::part(content)`
|
||||
*
|
||||
* @slot - The default slot where `<wa-nav-item>`s are placed.
|
||||
* @slot heading - Displayed above the nav items.
|
||||
* @slot summary - Displayed inside the `<wa-details>` element.
|
||||
* @slot expand-icon - The expand icon for `<wa-details>`.
|
||||
* @slot collapse-icon - The collapse icon for `<wa-details>`.
|
||||
*
|
||||
*/
|
||||
export default class WaNavGroup extends WebAwesomeElement {
|
||||
static styles: CSSResultGroup = styles;
|
||||
|
||||
/**
|
||||
* The label to display above the nav items slotted in.
|
||||
*/
|
||||
@property({ reflect: true }) label = '';
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div part="base" class="base" role="navigation" aria-labelledby="label">
|
||||
<p id="label" part="label" class="label">
|
||||
<slot name="label">${this.label}</slot>
|
||||
</p>
|
||||
|
||||
<div class="nav-items" part="nav-items" aria-labelledby="label" role="list">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
34
src/components/nav-group/nav-group.styles.ts
Normal file
34
src/components/nav-group/nav-group.styles.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
--gap: 6px;
|
||||
}
|
||||
|
||||
.base {
|
||||
color: var(--wa-color-text-normal);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: var(--wa-font-weight-heading);
|
||||
color: var(--wa-color-neutral-text-on-surface);
|
||||
padding-inline-start: var(--wa-space-xs);
|
||||
margin: 0;
|
||||
/** This is a cheap way to have labels have a bottom margin withot needing slot test controllers. */
|
||||
line-height: calc(1em + 32px);
|
||||
}
|
||||
|
||||
.nav-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
10
src/components/nav-group/nav-group.test.ts
Normal file
10
src/components/nav-group/nav-group.test.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import '../../../dist/webawesome.js';
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
|
||||
describe('<wa-nav-group>', () => {
|
||||
it('should render a component', async () => {
|
||||
const el = await fixture(html` <wa-nav-group></wa-nav-group> `);
|
||||
|
||||
expect(el).to.exist;
|
||||
});
|
||||
});
|
||||
12
src/components/nav-group/nav-group.ts
Normal file
12
src/components/nav-group/nav-group.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import WaNavGroup from './nav-group.component.js';
|
||||
|
||||
export * from './nav-group.component.js';
|
||||
export default WaNavGroup;
|
||||
|
||||
WaNavGroup.define('wa-nav-group');
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'wa-nav-group': WaNavGroup;
|
||||
}
|
||||
}
|
||||
131
src/components/nav-item/nav-item.component.ts
Normal file
131
src/components/nav-item/nav-item.component.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { html } from 'lit';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { LocalizeController } from '@shoelace-style/localize';
|
||||
import { property, state } from 'lit/decorators.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
import styles from './nav-item.styles.js';
|
||||
import WaDetails from '../details/details.component.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
* @summary 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 navigations.
|
||||
* @documentation https://shoelace.style/components/nav-item
|
||||
* @status experimental
|
||||
* @since 3.0
|
||||
*
|
||||
* @event wa-focus - Emitted on focus.
|
||||
* @event wa-blur - Emitted on blur.
|
||||
*
|
||||
* @slot - The link's label.
|
||||
* @slot prefix - A presentational prefix icon or similar element.
|
||||
* @slot suffix - A presentational suffix icon or similar element.
|
||||
*
|
||||
*/
|
||||
export default class WaNavItem extends WebAwesomeElement {
|
||||
static styles: CSSResultGroup = styles;
|
||||
|
||||
static dependencies = {
|
||||
'wa-details': WaDetails
|
||||
};
|
||||
|
||||
private readonly localize = new LocalizeController(this);
|
||||
|
||||
/** maps to the underlying `<a>`'s href */
|
||||
@property({ reflect: true }) href = '';
|
||||
|
||||
/** maps to aria-current="<current>". Generally this value will either be "page", "true", or "false" */
|
||||
@property({ reflect: true }) current: 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false' = 'false';
|
||||
|
||||
/** Tells the browser where to open the link. Only used when `href` is present. */
|
||||
@property({ reflect: true }) target: '_blank' | '_parent' | '_self' | '_top';
|
||||
|
||||
/**
|
||||
* When using `href`, this attribute will map to the underlying link's `rel` attribute. Unlike regular links, the
|
||||
* default is `noreferrer noopener` to prevent security exploits. However, if you're using `target` to point to a
|
||||
* specific tab/window, this will prevent that from working correctly. You can remove or change the default value by
|
||||
* setting the attribute to an empty string or a value of your choice, respectively.
|
||||
*/
|
||||
@property({ reflect: true }) rel = 'noreferrer noopener';
|
||||
|
||||
/** Tells the browser to download the linked file as this filename. Only used when `href` is present. */
|
||||
@property({ reflect: true }) download?: string;
|
||||
|
||||
/**
|
||||
* The text to display in the summary of the `<wa-details>` element when the nav item is expandable.
|
||||
*/
|
||||
@property({ reflect: true }) label = '';
|
||||
|
||||
/**
|
||||
* If true, will add a `<wa-details>` element into the shadowRoot that you can slot `<wa-nav-items>` into.
|
||||
*/
|
||||
@property({ reflect: true, type: Boolean }) expandable: boolean = false;
|
||||
|
||||
@state() hasFocus: boolean = false;
|
||||
|
||||
private handleBlur() {
|
||||
this.hasFocus = false;
|
||||
this.emit('wa-blur');
|
||||
}
|
||||
|
||||
private handleFocus() {
|
||||
this.hasFocus = true;
|
||||
this.emit('wa-focus');
|
||||
}
|
||||
|
||||
render() {
|
||||
const isRtl = this.localize.dir() === 'rtl';
|
||||
return html`
|
||||
<div class="base" part="base" role="listitem" aria-current=${this.expandable ? 'false' : this.current}>
|
||||
${when(
|
||||
this.expandable,
|
||||
() => html`
|
||||
<wa-details
|
||||
class="details"
|
||||
part="details"
|
||||
exportparts="
|
||||
base:details__base,
|
||||
header:details__header,
|
||||
header:control,
|
||||
summary:details__summary,
|
||||
summary-icon:details__summary-icon,
|
||||
content:details__content
|
||||
"
|
||||
>
|
||||
<div slot="summary" part="label">
|
||||
<slot name="label">${this.label}</slot>
|
||||
</div>
|
||||
|
||||
<slot slot="expand-icon" name="expand-icon">
|
||||
<wa-icon library="system" name=${isRtl ? 'chevron-left' : 'chevron-right'}></wa-icon>
|
||||
</slot>
|
||||
<slot slot="collapse-icon" name="collapse-icon">
|
||||
<wa-icon library="system" name=${isRtl ? 'chevron-left' : 'chevron-right'}></wa-icon>
|
||||
</slot>
|
||||
|
||||
<div class="nav-items" part="nav-items" aria-labelledby="heading" role="list">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</wa-details>
|
||||
`,
|
||||
() => html`
|
||||
<a
|
||||
class="control"
|
||||
part="control"
|
||||
href=${ifDefined(this.href)}
|
||||
target=${ifDefined(this.target)}
|
||||
download=${ifDefined(this.download)}
|
||||
rel=${ifDefined(this.rel)}
|
||||
@blur=${this.handleBlur}
|
||||
@focus=${this.handleFocus}
|
||||
>
|
||||
<slot part="prefix" name="prefix"></slot>
|
||||
<div class="content" part="content"><slot></slot></div>
|
||||
<slot part="suffix" name="suffix"></slot>
|
||||
</a>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
117
src/components/nav-item/nav-item.styles.ts
Normal file
117
src/components/nav-item/nav-item.styles.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
:host {
|
||||
width: auto;
|
||||
cursor: pointer;
|
||||
list-style-type: none;
|
||||
|
||||
--background-color: transparent;
|
||||
--background-color-hover: var(--wa-color-neutral-fill-muted-alt);
|
||||
--background-color-active: transparent;
|
||||
|
||||
--text-color: var(--wa-color-text-normal);
|
||||
--text-color-hover: var(--wa-color-text-normal);
|
||||
--text-color-active: var(--wa-color-text-normal);
|
||||
|
||||
--border-color: transparent;
|
||||
--border-color-active: transparent;
|
||||
--border-color-hover: color-mix(in oklab, var(--background-color-hover), var(--wa-color-tint-hover));
|
||||
}
|
||||
|
||||
:host(:is([current='page'], [current='true'])) {
|
||||
--background-color: var(--wa-color-brand-fill-vivid);
|
||||
--text-color: var(--wa-color-brand-text-on-vivid);
|
||||
--background-color-hover: color-mix(in oklab, var(--wa-color-brand-fill-vivid), var(--wa-color-tint-hover));
|
||||
--border-color-hover: color-mix(in oklab, var(--wa-color-brand-fill-vivid), var(--wa-color-tint-hover));
|
||||
--text-color-hover: var(--wa-color-brand-text-on-vivid);
|
||||
}
|
||||
|
||||
.base {
|
||||
/* when we change to "display: list-item" apparently it sets box-sizing to content-box... */
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.control,
|
||||
.details::part(header) {
|
||||
padding: var(--wa-space-square-xs);
|
||||
border-radius: var(--wa-corners-1x);
|
||||
font: inherit;
|
||||
font-weight: var(--wa-font-weight-action);
|
||||
text-decoration: none;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
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;
|
||||
line-height: var(--wa-font-height-compact);
|
||||
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border: 1px solid var(--border-color);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.suffix {
|
||||
align-self: end;
|
||||
}
|
||||
|
||||
.control::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.control:focus {
|
||||
outline: transparent;
|
||||
}
|
||||
|
||||
.details::part(header):is(:hover),
|
||||
.control:is(:hover) {
|
||||
background-color: var(--background-color-hover);
|
||||
color: var(--text-color-hover);
|
||||
border-color: var(--border-color-hover);
|
||||
}
|
||||
|
||||
.details::part(header):focus-visible,
|
||||
.control:focus-visible {
|
||||
outline: var(--wa-focus-ring);
|
||||
outline-offset: var(--wa-focus-ring-offset);
|
||||
outline-color: var(--background-color-hover);
|
||||
}
|
||||
|
||||
.details::part(base) {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.details::part(content) {
|
||||
padding-top: 6px;
|
||||
padding-inline-end: 0px;
|
||||
padding-inline-start: 2em;
|
||||
}
|
||||
|
||||
.nav-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
10
src/components/nav-item/nav-item.test.ts
Normal file
10
src/components/nav-item/nav-item.test.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import '../../../dist/webawesome.js';
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
|
||||
describe('<wa-nav-item>', () => {
|
||||
it('should render a component', async () => {
|
||||
const el = await fixture(html` <wa-nav-item></wa-nav-item> `);
|
||||
|
||||
expect(el).to.exist;
|
||||
});
|
||||
});
|
||||
12
src/components/nav-item/nav-item.ts
Normal file
12
src/components/nav-item/nav-item.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import WaNavItem from './nav-item.component.js';
|
||||
|
||||
export * from './nav-item.component.js';
|
||||
export default WaNavItem;
|
||||
|
||||
WaNavItem.define('wa-nav-item');
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'wa-nav-item': WaNavItem;
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,13 @@ html {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
font-family: var(--wa-font-family-body);
|
||||
font-size: var(--wa-font-size-root);
|
||||
font-weight: var(--wa-font-weight-normal);
|
||||
|
||||
@@ -27,10 +27,13 @@ export { default as WaIconButton } from './components/icon-button/icon-button.js
|
||||
export { default as WaImageComparer } from './components/image-comparer/image-comparer.js';
|
||||
export { default as WaInclude } from './components/include/include.js';
|
||||
export { default as WaInput } from './components/input/input.js';
|
||||
export { default as WaLayout } from './components/layout/layout.js';
|
||||
export { default as WaMenu } from './components/menu/menu.js';
|
||||
export { default as WaMenuItem } from './components/menu-item/menu-item.js';
|
||||
export { default as WaMenuLabel } from './components/menu-label/menu-label.js';
|
||||
export { default as WaMutationObserver } from './components/mutation-observer/mutation-observer.js';
|
||||
export { default as WaNavGroup } from './components/nav-group/nav-group.js';
|
||||
export { default as WaNavItem } from './components/nav-item/nav-item.js';
|
||||
export { default as WaOption } from './components/option/option.js';
|
||||
export { default as WaPopup } from './components/popup/popup.js';
|
||||
export { default as WaProgressBar } from './components/progress-bar/progress-bar.js';
|
||||
|
||||
Reference in New Issue
Block a user