mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 20:19:13 +00:00
create layouts
This commit is contained in:
51
docs/_includes/layout-example.njk
Normal file
51
docs/_includes/layout-example.njk
Normal file
@@ -0,0 +1,51 @@
|
||||
<!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">
|
||||
|
||||
{# Favicons #}
|
||||
<link rel="icon" href="{{ assetUrl('images/logo.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) }}" />
|
||||
|
||||
{# Shoelace #}
|
||||
<link rel="stylesheet" href="/dist/themes/light.css" />
|
||||
<link rel="stylesheet" href="/dist/themes/dark.css" />
|
||||
<script type="module" src="/dist/shoelace-autoloader.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('sl-theme-dark', theme === 'dark' || (theme === 'auto' && prefersDark));
|
||||
})();
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}
|
||||
{{ content | safe }}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,64 @@
|
||||
{% include "layout-templates/reset.css.njk" %}
|
||||
|
||||
.grid {
|
||||
font-size: 1.35rem;
|
||||
text-align: center;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: var(--sl-color-blue-100);
|
||||
}
|
||||
|
||||
aside {
|
||||
min-width: 250px;
|
||||
max-width: 250px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
main {
|
||||
background-color: var(--sl-color-green-100);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--sl-color-blue-200);
|
||||
}
|
||||
|
||||
.banner {
|
||||
background-color: var(--sl-color-amber-100);
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: var(--sl-color-blue-100);
|
||||
}
|
||||
|
||||
.banner, .header {
|
||||
min-width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
[slot=header] {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
[slot=aside] {
|
||||
background-color: var(--sl-color-yellow-100);
|
||||
}
|
||||
|
||||
[slot=menu] {
|
||||
background-color: var(--sl-color-pink-100);
|
||||
}
|
||||
|
||||
[slot=main-header] {
|
||||
background-color: var(--sl-color-red-200);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
[slot=main-footer] {
|
||||
background-color: var(--sl-color-lime-200);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<sl-layout main-id="main-content" class="sl-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">Inline header</header>
|
||||
|
||||
<main class="grid" id="main-content">
|
||||
<div style="width: 20ch; margin: 0 auto; background-color: white;">Main</div>
|
||||
</main>
|
||||
|
||||
<footer class="grid" slot="main-footer">Inline footer</footer>
|
||||
|
||||
<aside class="grid" slot="aside">Aside</aside>
|
||||
<footer class="grid" slot="footer">Footer</footer>
|
||||
</sl-layout>
|
||||
|
||||
{% include "layout-widget.njk" %}
|
||||
241
docs/_includes/layout-templates/app.css.njk
Normal file
241
docs/_includes/layout-templates/app.css.njk
Normal file
@@ -0,0 +1,241 @@
|
||||
{% include "layout-templates/reset.css.njk" %}
|
||||
|
||||
main {
|
||||
min-height: 100%;
|
||||
padding: 1rem 2rem;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
sl-layout {
|
||||
background-color: var(--sl-color-neutral-50);
|
||||
color: var(--sl-color-neutral-800);
|
||||
}
|
||||
|
||||
|
||||
sl-layout::part(header) {
|
||||
/** Because headers are sticky, this keeps text from leaking through. */
|
||||
background-color: var(--sl-color-neutral-0);
|
||||
}
|
||||
|
||||
sl-layout[view="mobile"] {
|
||||
background-color: var(--sl-color-neutral-0);
|
||||
--menu-width: 0px;
|
||||
}
|
||||
|
||||
sl-layout[view="mobile"]::part(header) {
|
||||
padding: 0.25rem;
|
||||
border-bottom: 1px solid var(--sl-color-neutral-300);
|
||||
}
|
||||
|
||||
sl-layout[view="mobile"]::part(navigation) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
sl-layout[view="desktop"]::part(main) {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
sl-layout[view="desktop"] {
|
||||
--menu-width: 250px;
|
||||
}
|
||||
|
||||
|
||||
sl-layout[view="desktop"]::part(navigation) {
|
||||
padding-top: 1.9rem;
|
||||
}
|
||||
|
||||
sl-layout[view="desktop"] > [slot="header"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
sl-layout[view="desktop"]::part(header) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
sl-layout[view="desktop"] main {
|
||||
background-color: var(--sl-color-neutral-0);
|
||||
box-shadow: 0px 0px 3px 1px rgba(0,0,0,0.05);
|
||||
border: 1px solid var(--sl-color-neutral-200);
|
||||
border-top-left-radius: 8px;
|
||||
}
|
||||
|
||||
/* Navigation / Lists */
|
||||
.app-navigation {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.app-navigation__list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: "";
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.app-navigation__list li {
|
||||
position: relative;
|
||||
padding: 0px 8px;
|
||||
}
|
||||
|
||||
/* We use a blank marker to work around a safari bug with "list-style-type: none;" */
|
||||
.app-navigation__list li::marker {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.app-navigation__list li.is-active sl-button {
|
||||
--background-color: var(--sl-color-primary-600);
|
||||
--background-color-active: var(--sl-color-primary-700);
|
||||
--text-color: var(--sl-color-neutral-0);
|
||||
--text-color-active: var(--sl-color-neutral-0);
|
||||
}
|
||||
|
||||
.app-navigation__list li sl-button {
|
||||
--text-color: var(--sl-color-neutral-600);
|
||||
}
|
||||
|
||||
/* Highlights */
|
||||
.highlight {
|
||||
font-size: 0.85em;
|
||||
padding: 0.4em 0.6em;
|
||||
border-radius: 6px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.highlight--success {
|
||||
background-color: var(--sl-color-success-50);
|
||||
color: var(--sl-color-success-700);
|
||||
}
|
||||
|
||||
.highlight--danger {
|
||||
background-color: var(--sl-color-danger-50);
|
||||
color: var(--sl-color-danger-700);
|
||||
}
|
||||
|
||||
/* Text */
|
||||
.text--light {
|
||||
color: var(--sl-color-neutral-600);
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.sl-card--muted::part(base) {
|
||||
--border-color: transparent;
|
||||
background-color: transparent;
|
||||
display: grid;
|
||||
height: 100%;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.sl-card--muted::part(body) {
|
||||
display: grid;
|
||||
align-content: flex-end;
|
||||
gap: var(--padding);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.sl-button--card {
|
||||
--border-radius: 8px;
|
||||
--padding: 1rem 0px;
|
||||
}
|
||||
|
||||
.sl-button--card.sl-button--muted {
|
||||
--background-color: var(--sl-color-neutral-100);
|
||||
}
|
||||
|
||||
.sl-button--card::part(base) {
|
||||
border-radius: var(--border-radius);
|
||||
padding: var(--padding);
|
||||
}
|
||||
|
||||
.sl-button--card::part(label) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sl-button--muted {
|
||||
--text-color: var(--sl-color-neutral-700);
|
||||
--text-color-active: var(--sl-color-neutral-700);
|
||||
--background-color: transparent;
|
||||
--background-color-active: var(--sl-color-neutral-200);
|
||||
--border-color: transparent;
|
||||
--border-color-active: var(--sl-color-neutral-200);
|
||||
}
|
||||
|
||||
.sl-button--muted::part(base) {
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.sl-button--muted:is(:focus-within)::part(base) {
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color-active);
|
||||
border-color: var(--border-color-active);
|
||||
}
|
||||
|
||||
.sl-button--muted:is(:hover)::part(base) {
|
||||
background-color: var(--background-color-active);
|
||||
color: var(--text-color-active);
|
||||
border-color: var(--border-color-active);
|
||||
}
|
||||
|
||||
.sl-button--logo::part(base) {
|
||||
font-size: 1.5rem;
|
||||
color: var(--sl-color-neutral-700);
|
||||
}
|
||||
|
||||
.sl-button--square::part(base) {
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.sl-button--stretch {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sl-button--stretch::part(label) {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.sl-button--nav-footer {
|
||||
--border-color: var(--sl-color-neutral-300);
|
||||
--sl-spacing-large: 8px;
|
||||
}
|
||||
|
||||
sl-layout[view="desktop"] .sl-button--nav-footer::part(base) {
|
||||
--border-color: transparent;
|
||||
border-top-color: var(--sl-color-neutral-300);
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
table {
|
||||
max-width: 100%;
|
||||
border: none;
|
||||
border-collapse: collapse;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
table tr {
|
||||
border-bottom: 1px solid var(--sl-color-neutral-300);
|
||||
}
|
||||
|
||||
table th {
|
||||
font-weight: var(--sl-font-weight-semibold);
|
||||
text-align: left;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
table td {
|
||||
line-height: var(--sl-line-height-normal);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
* > table {
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
449
docs/_includes/layout-templates/app.html.njk
Normal file
449
docs/_includes/layout-templates/app.html.njk
Normal file
@@ -0,0 +1,449 @@
|
||||
<sl-layout main-id="main-content" class="sl-theme-light">
|
||||
<sl-button href="#" variant="text" style="padding: 0 0.4rem" class="sl-button--logo sl-button--stretch sl-button--muted" size="large" slot="navigation-header">
|
||||
<sl-icon name="music-note" slot="prefix" style="font-size: 2rem;"></sl-icon>
|
||||
Musicify
|
||||
</sl-button>
|
||||
|
||||
<nav class="app-navigation" slot="navigation">
|
||||
<ul class="app-navigation__list">
|
||||
<li>
|
||||
<sl-button class="sl-button--muted sl-button--stretch" href="#" variant="text">
|
||||
<sl-icon name="search" slot="prefix"></sl-icon>
|
||||
Search
|
||||
</sl-button>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<sl-button class="sl-button--muted sl-button--stretch" href="#" variant="text">
|
||||
<sl-icon name="bell" slot="prefix"></sl-icon>
|
||||
Notifications
|
||||
</sl-button>
|
||||
</li>
|
||||
|
||||
<li><sl-divider style=""></sl-divider></li>
|
||||
|
||||
<li class="is-active">
|
||||
<sl-button class="sl-button--muted sl-button--stretch" href="#" variant="text">
|
||||
<sl-icon name="house-door" slot="prefix"></sl-icon>
|
||||
Home
|
||||
</sl-button>
|
||||
</li>
|
||||
<li>
|
||||
<sl-button class="sl-button--muted sl-button--stretch" href="#" variant="text">
|
||||
<sl-icon name="music-note-list" slot="prefix"></sl-icon>
|
||||
Playlists
|
||||
</sl-button>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<sl-button class="sl-button--muted sl-button--stretch" href="#" variant="text">
|
||||
<sl-icon name="file-earmark-music" slot="prefix"></sl-icon>
|
||||
Tracks
|
||||
</sl-button>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<sl-button class="sl-button--muted sl-button--stretch" href="#" variant="text">
|
||||
<sl-icon name="gear" slot="prefix"></sl-icon>
|
||||
Settings
|
||||
</sl-button>
|
||||
</li>
|
||||
|
||||
|
||||
<li style="margin-top: auto;">
|
||||
<sl-button class="sl-button--muted sl-button--stretch" href="#" variant="text">
|
||||
<sl-icon name="question-circle" slot="prefix"></sl-icon>
|
||||
Help
|
||||
</sl-button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- Hacky override to make padding 8px -->
|
||||
<sl-button slot="navigation-footer" outline class="sl-button--square sl-button--stretch sl-button--muted sl-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);">
|
||||
<sl-avatar shape="rounded" style="--size: 36px;"></sl-avatar>
|
||||
<div style="text-overflow: ellipsis; max-width: 100%; overflow: hidden; text-align: start;">Really really really long name</div>
|
||||
<sl-icon name="chevron-right"></sl-icon>
|
||||
</div>
|
||||
</sl-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>
|
||||
|
||||
<sl-select value="monthly">
|
||||
<sl-option value="daily">Daily</sl-option>
|
||||
<sl-option value="weekly">Weekly</sl-option>
|
||||
<sl-option value="monthly">Monthly</sl-option>
|
||||
<sl-option value="yearly">Yearly</sl-option>
|
||||
</sl-select>
|
||||
</div>
|
||||
|
||||
<sl-divider></sl-divider>
|
||||
|
||||
|
||||
<div style="margin-top: 1rem; display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-auto-rows: 1fr; gap: var(--sl-spacing-large); text-align: center;">
|
||||
<sl-card class="sl-card--muted" style="--padding: 8px;">
|
||||
<h3 slot="header">Total listening time</h3>
|
||||
|
||||
<p>
|
||||
<strong><sl-format-number value="35000"></sl-format-number></strong> minutes
|
||||
</p>
|
||||
<p>
|
||||
<mark class="highlight highlight--success">
|
||||
+16%
|
||||
</mark>
|
||||
|
||||
<small class="text--light">from last month</small>
|
||||
</p>
|
||||
</sl-card>
|
||||
|
||||
<sl-card class="sl-card--muted" style="--padding: 8px;">
|
||||
<h3 slot="header">Total songs played</h3>
|
||||
|
||||
<p>
|
||||
<strong><sl-format-number value="302"></sl-format-number></strong> songs
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<mark class="highlight highlight--danger">
|
||||
-0.3%
|
||||
</mark>
|
||||
|
||||
<small class="text--light">from last month</small>
|
||||
</p>
|
||||
</sl-card>
|
||||
|
||||
<sl-card class="sl-card--muted" style="--padding: 8px;">
|
||||
<h3 slot="header">Average listening session</h3>
|
||||
|
||||
<p>
|
||||
<strong><sl-format-number value="36"></sl-format-number></strong> minutes
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<mark class="highlight highlight--success">
|
||||
+11.4%
|
||||
</mark>
|
||||
|
||||
<small class="text--light">from last month</small>
|
||||
</p>
|
||||
</sl-card>
|
||||
|
||||
<sl-card class="sl-card--muted" style="--padding: 8px;">
|
||||
<h3 slot="header">Average track listening time</h3>
|
||||
|
||||
<p>
|
||||
<strong><sl-format-number value="2"></sl-format-number></strong> minutes,
|
||||
<strong><sl-format-number value="42"></sl-format-number></strong> seconds
|
||||
</p>
|
||||
<p>
|
||||
<mark class="highlight highlight--success">
|
||||
-6.2%
|
||||
</mark>
|
||||
|
||||
<small class="text--light">from last month</small>
|
||||
</p>
|
||||
</sl-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;">
|
||||
|
||||
<sl-button variant="neutral" class="sl-button--card sl-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(--sl-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>
|
||||
|
||||
<sl-icon name="chevron-right" style="justify-self: flex-end;"></sl-icon>
|
||||
</article>
|
||||
</div>
|
||||
</sl-button>
|
||||
|
||||
|
||||
<sl-button variant="neutral" class="sl-button--card sl-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(--sl-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>
|
||||
|
||||
<sl-icon name="chevron-right" style="justify-self: flex-end;"></sl-icon>
|
||||
</article>
|
||||
</div>
|
||||
</sl-button>
|
||||
|
||||
<sl-button variant="neutral" class="sl-button--card sl-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(--sl-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>
|
||||
|
||||
<sl-icon name="chevron-right" style="justify-self: flex-end;"></sl-icon>
|
||||
</article>
|
||||
</div>
|
||||
</sl-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>
|
||||
<sl-format-date date="2020-07-15T09:17:00-04:00"></sl-format-date>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
No Strangers to Love
|
||||
</td>
|
||||
|
||||
<td>
|
||||
You Know the Rules
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Rick Barry
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<sl-format-date date="2020-07-15T09:17:00-04:00"></sl-format-date>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
No Strangers to Love
|
||||
</td>
|
||||
|
||||
<td>
|
||||
You Know the Rules
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Rick Barry
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<sl-format-date date="2020-07-15T09:17:00-04:00"></sl-format-date>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
No Strangers to Love
|
||||
</td>
|
||||
|
||||
<td>
|
||||
You Know the Rules
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Rick Barry
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<sl-format-date date="2020-07-15T09:17:00-04:00"></sl-format-date>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
No Strangers to Love
|
||||
</td>
|
||||
|
||||
<td>
|
||||
You Know the Rules
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Rick Barry
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<sl-format-date date="2020-07-15T09:17:00-04:00"></sl-format-date>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
No Strangers to Love
|
||||
</td>
|
||||
|
||||
<td>
|
||||
You Know the Rules
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Rick Barry
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<sl-format-date date="2020-07-15T09:17:00-04:00"></sl-format-date>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
No Strangers to Love
|
||||
</td>
|
||||
|
||||
<td>
|
||||
You Know the Rules
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Rick Barry
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<sl-format-date date="2020-07-15T09:17:00-04:00"></sl-format-date>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
No Strangers to Love
|
||||
</td>
|
||||
|
||||
<td>
|
||||
You Know the Rules
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Rick Barry
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<sl-format-date date="2020-07-15T09:17:00-04:00"></sl-format-date>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
No Strangers to Love
|
||||
</td>
|
||||
|
||||
<td>
|
||||
You Know the Rules
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Rick Barry
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<sl-format-date date="2020-07-15T09:17:00-04:00"></sl-format-date>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
No Strangers to Love
|
||||
</td>
|
||||
|
||||
<td>
|
||||
You Know the Rules
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Rick Barry
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<sl-format-date date="2020-07-15T09:17:00-04:00"></sl-format-date>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
No Strangers to Love
|
||||
</td>
|
||||
|
||||
<td>
|
||||
You Know the Rules
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Rick Barry
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<sl-format-date date="2020-07-15T09:17:00-04:00"></sl-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>
|
||||
|
||||
</sl-layout>
|
||||
66
docs/_includes/layout-templates/documentation.css.njk
Normal file
66
docs/_includes/layout-templates/documentation.css.njk
Normal file
@@ -0,0 +1,66 @@
|
||||
{% include "layout-templates/reset.css.njk" %}
|
||||
|
||||
.grid {
|
||||
font-size: 1.35rem;
|
||||
text-align: center;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: var(--sl-color-blue-100);
|
||||
}
|
||||
|
||||
aside {
|
||||
min-width: 250px;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
main {
|
||||
background-color: var(--sl-color-green-100);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--sl-color-blue-200);
|
||||
}
|
||||
|
||||
.banner {
|
||||
background-color: var(--sl-color-amber-100);
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: var(--sl-color-blue-100);
|
||||
}
|
||||
|
||||
.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(--sl-color-yellow-100);
|
||||
}
|
||||
|
||||
[slot=menu] {
|
||||
height: 100%;
|
||||
background-color: var(--sl-color-pink-100);
|
||||
}
|
||||
|
||||
[slot=main-header] {
|
||||
background-color: var(--sl-color-red-200);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
[slot=main-footer] {
|
||||
background-color: var(--sl-color-lime-200);
|
||||
}
|
||||
|
||||
11
docs/_includes/layout-templates/documentation.html.njk
Normal file
11
docs/_includes/layout-templates/documentation.html.njk
Normal file
@@ -0,0 +1,11 @@
|
||||
<sl-layout main-id="main-content" class="sl-theme-light">
|
||||
<header class="grid" slot="header">Header</header>
|
||||
<aside class="grid" slot="menu">Menu</aside>
|
||||
<main class="grid" id="main-content">
|
||||
Main
|
||||
</main>
|
||||
<aside class="grid" slot="aside">Aside</aside>
|
||||
<footer class="grid" slot="footer">Footer</footer>
|
||||
</sl-layout>
|
||||
|
||||
{% include "layout-widget.njk" %}
|
||||
1
docs/_includes/layout-templates/hero.css.njk
Normal file
1
docs/_includes/layout-templates/hero.css.njk
Normal file
@@ -0,0 +1 @@
|
||||
{% include "layout-templates/reset.css.njk" %}
|
||||
2
docs/_includes/layout-templates/hero.html.njk
Normal file
2
docs/_includes/layout-templates/hero.html.njk
Normal file
@@ -0,0 +1,2 @@
|
||||
<sl-layout main-id="main-content" class="sl-theme-light">
|
||||
</sl-layout>
|
||||
37
docs/_includes/layout-templates/reset.css.njk
Normal file
37
docs/_includes/layout-templates/reset.css.njk
Normal file
@@ -0,0 +1,37 @@
|
||||
/* Reset */
|
||||
|
||||
*, *:before, *:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1,h2,h3,h4,h5,h6 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Gives us a nice fade-in to prevent layout jank */
|
||||
:not(:defined) {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
:defined {
|
||||
opacity: 1;
|
||||
transition: opacity 100ms ease-in-out;
|
||||
}
|
||||
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>
|
||||
|
||||
<sl-dropdown id="js-layout-widget" class="layout-widget" stay-open-on-select>
|
||||
<sl-button slot="trigger" caret>Dropdown</sl-button>
|
||||
<sl-menu></sl-menu>
|
||||
</sl-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("sl-menu-item"), {
|
||||
type: "checkbox",
|
||||
textContent: `${type} ${slot}`
|
||||
})
|
||||
|
||||
menuItem.setAttribute("value", `${type}-${slot}`)
|
||||
return menuItem
|
||||
}
|
||||
|
||||
document.querySelectorAll("sl-layout > [slot]").forEach((el) => {
|
||||
const slot = el.getAttribute("slot");
|
||||
docFrag.append(makeMenuItem("toggle", slot), makeMenuItem("overflow", slot), document.createElement("sl-divider"))
|
||||
})
|
||||
|
||||
docFrag.append(makeMenuItem("toggle", "main"), makeMenuItem("overflow", "main"))
|
||||
layoutWidget.querySelector("sl-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(`sl-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("sl-select", handleSelect);
|
||||
{% if in_playground %}
|
||||
</script>
|
||||
{% else %}
|
||||
</script>
|
||||
{% endif %}
|
||||
<!-- playground-hide-end -->
|
||||
126
docs/_includes/playground.njk
Normal file
126
docs/_includes/playground.njk
Normal file
@@ -0,0 +1,126 @@
|
||||
<style>
|
||||
playground-ide {
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
--playground-preview-width: 75%;
|
||||
max-height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
playground-ide::part(preview) {
|
||||
background-color: var(--sl-color-neutral-50);
|
||||
min-height: 100vh;
|
||||
max-height: 100vh;
|
||||
}
|
||||
playground-ide::part(editor) {
|
||||
overflow: auto;
|
||||
height: 100vh;
|
||||
max-height: calc(100vh - var(--playground-preview-height, 44px));
|
||||
max-height: calc(100dvh - var(--playground-preview-height, 44px));
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
/* Playgrounds have a weird like 1px overflow I can't track down. */
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="module">
|
||||
import 'https://esm.run/playground-elements';
|
||||
import * as prettier from "https://unpkg.com/prettier@3.0.2/standalone.mjs";
|
||||
import prettierPluginBabel from "https://unpkg.com/prettier@3.0.2/plugins/babel.mjs";
|
||||
import prettierPluginEstree from "https://unpkg.com/prettier@3.0.2/plugins/estree.mjs";
|
||||
import prettierPluginHtml from "https://unpkg.com/prettier@3.0.2/plugins/html.mjs";
|
||||
import prettierPluginPostCss from "https://unpkg.com/prettier@3.0.2/plugins/postcss.mjs";
|
||||
|
||||
const types = ["html", "css", "js"]
|
||||
|
||||
const [html,css,js] = await Promise.allSettled(types.map(async (type) => {
|
||||
let innerHTML = document.querySelector(`#playground-${type}`).innerHTML
|
||||
|
||||
let parser = type
|
||||
let plugins = {
|
||||
html: [prettierPluginHtml],
|
||||
css: [prettierPluginPostCss],
|
||||
js: [prettierPluginHtml, prettierPluginBabel, prettierPluginEstree]
|
||||
}
|
||||
|
||||
innerHTML = innerHTML.replaceAll(">", ">")
|
||||
innerHTML = innerHTML.replaceAll("<", "<")
|
||||
|
||||
if (type === "js") {
|
||||
parser = "babel"
|
||||
}
|
||||
|
||||
const str = await prettier.format(innerHTML, {
|
||||
parser,
|
||||
plugins: plugins[type],
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
|
||||
return str
|
||||
}))
|
||||
|
||||
|
||||
const files = {}
|
||||
|
||||
types.forEach((type) => {
|
||||
const filename = `index.${type}`
|
||||
|
||||
files[filename] = {}
|
||||
|
||||
if (type === "html") {
|
||||
files[filename].selected = true
|
||||
files[filename].content = html.value
|
||||
} else if (type === "css") {
|
||||
files[filename].content = css.value
|
||||
} else if (type === "js") {
|
||||
files[filename].content = js.value
|
||||
}
|
||||
})
|
||||
|
||||
// So we can't target the iFrame with CSS parts, so this is a hack to get full height iframe in Chrome.
|
||||
await customElements.whenDefined("playground-ide").then(() => {
|
||||
setTimeout(() => {
|
||||
const ide = document.querySelector("playground-ide")
|
||||
|
||||
const iframe = ide.shadowRoot.querySelector("playground-preview").shadowRoot.querySelector("iframe")
|
||||
iframe.setAttribute("style", "position: absolute; top: 0px; right: 0px; bottom: 0px; left: 0px; height: 100%; width: 100%;")
|
||||
|
||||
ide.config = {
|
||||
files
|
||||
}
|
||||
|
||||
}, 10)
|
||||
})
|
||||
</script>
|
||||
|
||||
<script id="playground-html" type="text/plain">
|
||||
<!doctype html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="./index.css">
|
||||
</head>
|
||||
<body>
|
||||
{% include html_file %}
|
||||
|
||||
<script type="module" src="./index.js"></script>
|
||||
</body>
|
||||
</script>
|
||||
|
||||
<script id="playground-js" type="text/plain">
|
||||
import { setBasePath } from "/dist/shoelace.js"
|
||||
setBasePath("/dist")
|
||||
</script>
|
||||
|
||||
<script id="playground-css" type="text/plain">
|
||||
@import "/dist/themes/light.css";
|
||||
@import "/dist/themes/dark.css";
|
||||
|
||||
{% include css_file %}
|
||||
</script>
|
||||
|
||||
<playground-ide line-numbers resizable sandbox-base-url={{ rootUrl("/assets", true) }}>
|
||||
</playground-ide>
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
20
docs/assets/playground-service-worker-proxy.html
Normal file
20
docs/assets/playground-service-worker-proxy.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!doctype html><html>
|
||||
<head></head>
|
||||
<body>
|
||||
<script type="module">
|
||||
/**
|
||||
* If you plan to update this file, update it via unpkg version and make sure to also update playground-service-worker.js
|
||||
* https://unpkg.com/browse/playground-elements@0.18.1/playground-service-worker-proxy.html
|
||||
* @license
|
||||
* Copyright 2019 Google LLC
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
(async()=>{try{parent.window.console.warn("Playground sandbox is executing with the same origin as its parent.","This is a security risk.","https://github.com/google/playground-elements#sandbox-security")}catch{}const{scope:e,port:t}=await new Promise((e=>{const t=a=>{1===a.data.type&&(window.removeEventListener("message",t),e(a.data))};window.addEventListener("message",t)})),a=await navigator.serviceWorker.register(new URL("playground-service-worker.js",import.meta.url).href,{scope:e}),n=()=>{var e,t;return null!==(t=null!==(e=a.installing)&&void 0!==e?e:a.waiting)&&void 0!==t?t:a.active};let r=null;const s=async(e=!1)=>{const a=n();if(!e&&a===r)return;if(r=a,null===a)return void console.error("No playground service worker found.");if(await(async e=>{if("activated"!==e.state)return new Promise((t=>{const a=new AbortController;e.addEventListener("statechange",(()=>{"activated"===e.state&&(t(),a.abort())}),{signal:a.signal})}))})(a),a!==r)return;const{port1:s,port2:o}=new MessageChannel,i={type:3,port:s};t.postMessage(i,[s]);const d={type:2,port:o};a.postMessage(d,[o])};s(),a.addEventListener("updatefound",(()=>{s()})),navigator.serviceWorker.addEventListener("message",(e=>{e.source===r&&5===e.data.type&&s(!0)})),window.addEventListener("message",(async e=>{if(6===e.data.type){const e=n();await a.update(),n()===e&&console.error("Playground service worker update failed.")}}))})();</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
25
docs/assets/playground-service-worker.js
Normal file
25
docs/assets/playground-service-worker.js
Normal file
@@ -0,0 +1,25 @@
|
||||
!function(){
|
||||
/**
|
||||
* If you plan to update this file, update it via unpkg version and make sure to also update playground-service-worker-proxy.html
|
||||
* https://unpkg.com/browse/playground-elements@0.18.1/playground-service-worker.js
|
||||
* @license
|
||||
* Copyright 2019 Google LLC
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
const e=Symbol("Comlink.proxy"),t=Symbol("Comlink.endpoint"),n=Symbol("Comlink.releaseProxy"),s=Symbol("Comlink.thrown"),a=e=>"object"==typeof e&&null!==e||"function"==typeof e,r=new Map([["proxy",{canHandle:t=>a(t)&&t[e],serialize(e){const{port1:t,port2:n}=new MessageChannel;return i(e,t),[n,[n]]},deserialize:e=>(e.start(),l(e,[],undefined))}],["throw",{canHandle:e=>a(e)&&s in e,serialize({value:e}){let t;return t=e instanceof Error?{isError:!0,value:{message:e.message,name:e.name,stack:e.stack}}:{isError:!1,value:e},[t,[]]},deserialize(e){if(e.isError)throw Object.assign(Error(e.value.message),e.value);throw e.value}}]]);function i(t,n=self){n.addEventListener("message",(function a(r){if(!r||!r.data)return;const{id:c,type:l,path:u}=Object.assign({path:[]},r.data),h=(r.data.argumentList||[]).map(f);let g;try{const n=u.slice(0,-1).reduce(((e,t)=>e[t]),t),s=u.reduce(((e,t)=>e[t]),t);switch(l){case"GET":g=s;break;case"SET":n[u.slice(-1)[0]]=f(r.data.value),g=!0;break;case"APPLY":g=s.apply(n,h);break;case"CONSTRUCT":g=function(t){return Object.assign(t,{[e]:!0})}(new s(...h));break;case"ENDPOINT":{const{port1:e,port2:n}=new MessageChannel;i(t,n),g=function(e,t){return p.set(e,t),e}(e,[e])}break;case"RELEASE":g=void 0;break;default:return}}catch(e){g={value:e,[s]:0}}Promise.resolve(g).catch((e=>({value:e,[s]:0}))).then((e=>{const[t,s]=d(e);n.postMessage(Object.assign(Object.assign({},t),{id:c}),s),"RELEASE"===l&&(n.removeEventListener("message",a),o(n))}))})),n.start&&n.start()}function o(e){(function(e){return"MessagePort"===e.constructor.name})(e)&&e.close()}function c(e){if(e)throw Error("Proxy has been released and is not useable")}function l(e,s=[],a=function(){}){let r=!1;const i=new Proxy(a,{get(t,a){if(c(r),a===n)return()=>h(e,{type:"RELEASE",path:s.map((e=>e.toString()))}).then((()=>{o(e),r=!0}));if("then"===a){if(0===s.length)return{then:()=>i};const t=h(e,{type:"GET",path:s.map((e=>e.toString()))}).then(f);return t.then.bind(t)}return l(e,[...s,a])},set(t,n,a){c(r);const[i,o]=d(a);return h(e,{type:"SET",path:[...s,n].map((e=>e.toString())),value:i},o).then(f)},apply(n,a,i){c(r);const o=s[s.length-1];if(o===t)return h(e,{type:"ENDPOINT"}).then(f);if("bind"===o)return l(e,s.slice(0,-1));const[p,d]=u(i);return h(e,{type:"APPLY",path:s.map((e=>e.toString())),argumentList:p},d).then(f)},construct(t,n){c(r);const[a,i]=u(n);return h(e,{type:"CONSTRUCT",path:s.map((e=>e.toString())),argumentList:a},i).then(f)}});return i}function u(e){const t=e.map(d);return[t.map((e=>e[0])),(n=t.map((e=>e[1])),Array.prototype.concat.apply([],n))];var n}const p=new WeakMap;function d(e){for(const[t,n]of r)if(n.canHandle(e)){const[s,a]=n.serialize(e);return[{type:"HANDLER",name:t,value:s},a]}return[{type:"RAW",value:e},p.get(e)||[]]}function f(e){switch(e.type){case"HANDLER":return r.get(e.name).deserialize(e.value);case"RAW":return e.value}}function h(e,t,n){return new Promise((s=>{const a=[,,,,].fill(0).map((()=>Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(16))).join("-");e.addEventListener("message",(function t(n){n.data&&n.data.id&&n.data.id===a&&(e.removeEventListener("message",t),s(n.data))})),e.start&&e.start(),e.postMessage(Object.assign({id:a},t),n)}))}
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
class g{constructor(){this.settled=!1,this.promise=new Promise(((e,t)=>{this._resolve=e,this._reject=t}))}resolve(e){this.settled=!0,this._resolve(e)}reject(e){this.settled=!0,this._reject(e)}}
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2021 Google LLC
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/const m=new Map,v={setFileAPI(e,t){let n=m.get(t);(void 0===n||n.settled)&&(n=new g,m.set(t,n)),n.resolve(e)}};
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2019 Google LLC
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/self.addEventListener("fetch",(e=>{const t=e.request.url;if(t.startsWith(self.registration.scope)){const{filePath:n,sessionId:s}=(e=>{const t=self.registration.scope,n=e.substring(t.length).split("?")[0],s=n.indexOf("/");let a,r;return-1===s?console.warn("Invalid sample file URL: "+e):(a=n.slice(0,s),r=n.slice(s+1)),{sessionId:a,filePath:r}})(t);void 0!==s&&e.respondWith((async(e,t,n)=>{const s=await(async e=>{let t=m.get(e);if(void 0!==t)return t.promise;const n=await(async e=>{for(const t of await self.clients.matchAll({includeUncontrolled:!0})){const n=new URL(t.url).hash;if(new URLSearchParams(n.slice(1)).get("playground-session-id")===e)return t}})(e);if(void 0===n)return;return t=new g,m.set(e,t),n.postMessage({type:5}),t.promise})(n);if(void 0===s)return new Response("Playground project not available",{status:503});const a=await s.getFile(t);if("status"in a){const{body:e,status:t}=a;return new Response(e,{status:t})}const{content:r,contentType:i}=a,o=new Headers;return o.set("Origin-Agent-Cluster","?1"),i&&o.set("Content-Type",i),new Response(r,{headers:o})})(0,n,s))}})),self.addEventListener("activate",(e=>{e.waitUntil(self.clients.claim())})),self.addEventListener("install",(()=>{self.skipWaiting()})),self.addEventListener("message",(e=>{if(2===e.data.type){const t={type:4,version:"1dae6563"};e.data.port.postMessage(t),i(v,e.data.port)}}))}();
|
||||
@@ -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
|
||||
|
||||
@@ -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', {
|
||||
@@ -182,21 +191,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 };
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
22
docs/pages/components/layout.md
Normal file
22
docs/pages/components/layout.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
meta:
|
||||
title: Layout
|
||||
description:
|
||||
layout: component
|
||||
---
|
||||
|
||||
```html preview
|
||||
<sl-layout></sl-layout>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### First Example
|
||||
|
||||
TODO
|
||||
|
||||
### Second Example
|
||||
|
||||
TODO
|
||||
|
||||
[component-metadata:sl-layout]
|
||||
8
docs/pages/layouts/advanced-documentation-playground.njk
Normal file
8
docs/pages/layouts/advanced-documentation-playground.njk
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
layout: layout-example.njk
|
||||
---
|
||||
|
||||
{% set html_file = "layout-templates/advanced-documentation.html.njk" %}
|
||||
{% set css_file = "layout-templates/advanced-documentation.css.njk" %}
|
||||
|
||||
{% include "playground.njk" %}
|
||||
10
docs/pages/layouts/advanced-documentation.njk
Normal file
10
docs/pages/layouts/advanced-documentation.njk
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
layout: layout-example.njk
|
||||
---
|
||||
|
||||
<style>
|
||||
{% include "layout-templates/advanced-documentation.css.njk" %}
|
||||
</style>
|
||||
|
||||
{% include "layout-templates/advanced-documentation.html.njk" %}
|
||||
|
||||
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.njk" %}
|
||||
{% set css_file = "layout-templates/app.css.njk" %}
|
||||
|
||||
{% 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.njk" %}
|
||||
</style>
|
||||
|
||||
{% include "layout-templates/app.html.njk" %}
|
||||
10
docs/pages/layouts/documentation-playground.njk
Normal file
10
docs/pages/layouts/documentation-playground.njk
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
layout: layout-example.njk
|
||||
---
|
||||
|
||||
|
||||
{% set html_file = "layout-templates/documentation.html.njk" %}
|
||||
{% set css_file = "layout-templates/documentation.css.njk" %}
|
||||
|
||||
{% set in_playground = true %}
|
||||
{% include "playground.njk" %}
|
||||
10
docs/pages/layouts/documentation.njk
Normal file
10
docs/pages/layouts/documentation.njk
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
layout: layout-example.njk
|
||||
---
|
||||
|
||||
<style>
|
||||
{% include "layout-templates/documentation.css.njk" %}
|
||||
</style>
|
||||
|
||||
{% include "layout-templates/documentation.html.njk" %}
|
||||
|
||||
8
docs/pages/layouts/hero-playground.njk
Normal file
8
docs/pages/layouts/hero-playground.njk
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
layout: layout-example.njk
|
||||
---
|
||||
|
||||
{% set html_file = "layout-templates/hero.html.njk" %}
|
||||
{% set css_file = "layout-templates/hero.css.njk" %}
|
||||
|
||||
{% 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/documentation.css.njk" %}
|
||||
</style>
|
||||
|
||||
{% include "layout-templates/documentation.html.njk" %}
|
||||
11
docs/pages/layouts/index.md
Normal file
11
docs/pages/layouts/index.md
Normal file
@@ -0,0 +1,11 @@
|
||||
- [Documentation Layout](/layouts/documentation/index.html)
|
||||
- [Documentation Playground](/layouts/documentation-playground/index.html)
|
||||
|
||||
- [Advanced Documentation Layout](/layouts/advanced-documentation/index.html)
|
||||
- [Advanced Documentation Playground](/layouts/advanced-documentation-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)
|
||||
@@ -249,7 +249,17 @@ 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',
|
||||
|
||||
206
src/components/layout/layout.component.ts
Normal file
206
src/components/layout/layout.component.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import SlVisuallyHidden from "../visually-hidden/visually-hidden.component.js"
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './layout.styles.js';
|
||||
import type { CSSResultGroup, PropertyValueMap } from 'lit';
|
||||
import { when } from "lit/directives/when.js";
|
||||
import SlDrawer from "../drawer/drawer.component.js";
|
||||
import SlButton from "../button/button.component.js";
|
||||
|
||||
/**
|
||||
* @summary Short summary of the component's intended use.
|
||||
* @documentation https://shoelace.style/components/layout
|
||||
* @status experimental
|
||||
* @since 2.0
|
||||
*
|
||||
* @csspart base - The component's base wrapper.
|
||||
* @cssproperty --example - An example CSS custom property.
|
||||
*/
|
||||
export default class SlLayout extends ShoelaceElement {
|
||||
static styles: CSSResultGroup = styles;
|
||||
static dependencies = {
|
||||
'sl-button': SlButton,
|
||||
'sl-visually-hidden': SlVisuallyHidden,
|
||||
'sl-drawer': SlDrawer
|
||||
}
|
||||
|
||||
@property({ attribute: "main-id" }) mainId: string = ""
|
||||
@property({ attribute: "view", reflect: true }) view: "mobile" | "desktop" = "mobile"
|
||||
@property({ attribute: "hide-nav-button", reflect: true, type: Boolean }) hideNavButton = false
|
||||
|
||||
@property({ attribute: "mobile-breakpoint" }) mobileBreakpoint = 768
|
||||
@property({ attribute: "navigation-placement" }) 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<any> | Map<PropertyKey, unknown>): void {
|
||||
if (changedProperties.has("view")) {
|
||||
this.hideNavigation()
|
||||
}
|
||||
super.update(changedProperties)
|
||||
}
|
||||
|
||||
headerResizeObserver = this.slotResizeObserver("header");
|
||||
subHeaderResizeObserver = this.slotResizeObserver("sub-header");
|
||||
bannerResizeObserver = this.slotResizeObserver("banner");
|
||||
footerResizeObserver = this.slotResizeObserver("footer");
|
||||
|
||||
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`)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@query("[part~='header']") header: HTMLElement
|
||||
@query("[part~='sub-header']") subHeader: HTMLElement
|
||||
@query("[part~='footer']") footer: HTMLElement
|
||||
@query("[part~='banner']") banner: HTMLElement
|
||||
@query("[part~='navigation-drawer']") navigationDrawer: SlDrawer
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
showNavigation () {
|
||||
this.navigationDrawer?.show()
|
||||
}
|
||||
|
||||
hideNavigation () {
|
||||
this.navigationDrawer?.hide()
|
||||
}
|
||||
|
||||
toggleNavigation () {
|
||||
if (this.navigationDrawer) {
|
||||
this.navigationDrawer.open = !this.navigationDrawer.open
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<sl-visually-hidden class="skip-links" part="skip-links">
|
||||
<slot name="skip-links">
|
||||
${when(this.mainId, () => html`
|
||||
<sl-button variant="primary" href=${`#${this.mainId}`} part="skip-link" class="skip-link">
|
||||
Skip to main
|
||||
</sl-button>
|
||||
`)}
|
||||
</slot>
|
||||
</sl-visually-hidden>
|
||||
|
||||
<div class="base" part="base">
|
||||
<div class="banner" part="banner">
|
||||
<slot name="banner"></slot>
|
||||
</div>
|
||||
|
||||
<div class="header" part="header">
|
||||
<slot name="nav-button">
|
||||
<sl-icon-button
|
||||
name="list"
|
||||
variant="text"
|
||||
size="large"
|
||||
part="nav-button"
|
||||
@click=${this.showNavigation}
|
||||
part="icon-button"
|
||||
>
|
||||
</sl-icon-button>
|
||||
</slot>
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
|
||||
<div class="sub-header" part="sub-header">
|
||||
<slot name="sub-header"></slot>
|
||||
</div>
|
||||
|
||||
<div class="body" part="body">
|
||||
<div class="menu" part="menu">
|
||||
<slot name="menu">
|
||||
<!--
|
||||
We generally expect there to be a <nav> here so we wrap it. If a user wants to override it, they
|
||||
can provide a <div slot="menu"> to circumvent our prebuilt <nav>
|
||||
-->
|
||||
<nav name="navigation" class="navigation" part="navigation">
|
||||
<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></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>
|
||||
|
||||
<sl-drawer placement=${this.navigationPlacement} part="navigation-drawer" 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>
|
||||
</sl-drawer>
|
||||
|
||||
<div part="dialog" class="dialog">
|
||||
<slot name="dialog"></slot>
|
||||
</div>
|
||||
|
||||
`
|
||||
}
|
||||
}
|
||||
165
src/components/layout/layout.styles.ts
Normal file
165
src/components/layout/layout.styles.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
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;
|
||||
--sub-header-height: 0px;
|
||||
}
|
||||
|
||||
[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"
|
||||
"sub-header"
|
||||
"body"
|
||||
"footer";
|
||||
}
|
||||
|
||||
/* Grid areas */
|
||||
[part~="banner"] {
|
||||
grid-area: banner;
|
||||
}
|
||||
|
||||
[part~="header"] {
|
||||
grid-area: header;
|
||||
}
|
||||
|
||||
[part~="sub-header"] {
|
||||
grid-area: sub-header;
|
||||
}
|
||||
|
||||
[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~="sub-header"] {
|
||||
position: sticky;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
[part~="banner"] {
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
[part~="header"] {
|
||||
top: var(--banner-height);
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, auto) minmax(0, 1fr);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:host([hide-nav-button]) [part~="nav-button"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host([hide-nav-button]) [part~="header"] {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
[part~="sub-header"] {
|
||||
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;
|
||||
}
|
||||
|
||||
.skip-links {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
padding: 0.25rem;
|
||||
|
||||
/* This looks silly, but without this our skip links get flagged by a11y checkers. */
|
||||
background-color: var(--sl-color-neutral-0);
|
||||
}
|
||||
|
||||
[part~="menu"],
|
||||
[part~="aside"] {
|
||||
position: sticky;
|
||||
top: calc(var(--banner-height) + var(--header-height) + var(--sub-header-height));
|
||||
z-index: 4;
|
||||
height: calc(100dvh - var(--header-height) - var(--banner-height) - var(--sub-header-height));
|
||||
max-height: calc(100dvh - var(--header-height) - var(--banner-height) - var(--sub-header-height));
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
[part~="menu"] {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
[part~="navigation"] {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-rows: minmax(0, auto) minmax(0, 1fr) minmax(0, auto);
|
||||
}
|
||||
|
||||
[part~="nav-button"] {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
`;
|
||||
10
src/components/layout/layout.test.ts
Normal file
10
src/components/layout/layout.test.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
|
||||
describe('<sl-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 SlLayout from "./layout.component.js"
|
||||
export * from"./layout.component.js"
|
||||
export default SlLayout
|
||||
SlLayout.define("sl-layout")
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'sl-layout': SlLayout;
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ export { default as SlTooltip } from './components/tooltip/tooltip.js';
|
||||
export { default as SlTree } from './components/tree/tree.js';
|
||||
export { default as SlTreeItem } from './components/tree-item/tree-item.js';
|
||||
export { default as SlVisuallyHidden } from './components/visually-hidden/visually-hidden.js';
|
||||
export { default as SlLayout } from './components/layout/layout.js';
|
||||
/* plop:component */
|
||||
|
||||
// Utilities
|
||||
|
||||
Reference in New Issue
Block a user