Rename Layout to Page (#65)

* working on layouts

* fix buildS

* first layout converted to sportawesome

* working on playgrounds

* continued work on layouts

* working on astro layout

* light-pen 3

* rename layout to page

* prettier

* add vercel.json

* add vercel.json

* add vercel.json

* add vercel.json

* add vercel.json

* add sandbox-settings

* add sandbox-settings

* add sandbox-settings

* maybe now?

* remove sandbox-settings

* remove vercel.json

* rename to pagE

* fix grid for navigation

* prettier

* fix stuff

* fix search

* prettier
This commit is contained in:
Konnor Rogers
2024-03-14 12:47:28 -04:00
committed by GitHub
parent 2e725a2d93
commit 8e5e039af8
32 changed files with 3535 additions and 1034 deletions

View File

@@ -171,6 +171,7 @@
"valpha",
"valuenow",
"valuetext",
"viewports",
"Vuejs",
"WCAG",
"webawesome",

View File

@@ -0,0 +1,69 @@
<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-page > [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-page > [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);
</script>

View File

@@ -0,0 +1,35 @@
---
import PageLayout from "../layouts/PageLayout.astro"
import Themer from "./Themer.astro"
const { html, css, js, title, description } = Astro.props
---
<script>
import "light-pen"
</script>
<PageLayout frontmatter={{
description,
title
}}>
<div style="display:grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr);">
<Themer />
<light-pen style="height: 100%;" resize-position="30" preserve-whitespace>
<script type="text/plain" slot="html" set:html={
`<link id="theme-stylesheet" type="text/css" href="/dist/themes/default.css" rel="stylesheet">
<link id="applied-stylesheet" type="text/css" href="/dist/themes/applied.css" rel="stylesheet">
<script type="module" defer>import "/dist/webawesome.js"&lt;/script>
${html}`}>
</script>
<script type="text/plain" slot="css" set:html={`${css}`}>
</script>
<script type="text/plain" slot="js" set:html={''}>
</script>
</light-pen>
</div>
</PageLayout>

File diff suppressed because it is too large Load Diff

View File

@@ -44,6 +44,7 @@ const version = customElementsManifest().package.version
{/* <Search /> */}
<meta name="wa-version" content={version} />
<meta name="turbo-cache-control" content="no-preview">
<link id="theme-stylesheet" rel="stylesheet" href="/dist/themes/default.css" />

View File

@@ -567,3 +567,4 @@ const pagefindTranslations = {
</style>

View File

@@ -0,0 +1,118 @@
---
title: Page
description: Layouts offer an easy way to scaffold pages using minimal markup.
layout: '../../../layouts/ComponentLayout.astro'
---
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.
[![Screenshot of Layout Anatomy showing various slots](/assets/images/layout.png)](/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-page disable-sticky="header aside"> ... </wa-page>
```
## 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-page>`, 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-page>`.
```css
html,
body {
min-height: 100%;
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-page>
...
<span slot="skip-to-content">Zum Inhalt springen</span>
...
</wa-page>
```
## 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-page mobile-breakpoint="600"> ... </wa-page>
```
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-page mobile-breakpoint="600">
...
<wa-button data-toggle-nav>Menu</wa-button>
...
</wa-page>
```
Alternatively, you can apply `nav-state="open"` and `nav-state="closed"` to the layout component to show and hide the navigation, respectively.
```html
<wa-page nav-state="open"> ... </wa-page>
```
## 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)

View File

@@ -0,0 +1,20 @@
---
title: Page Templates
description: 'A collection of page templates'
---
<script type="module">
window.Turbo.session.drive = false
</script>
- [Example Layout](/page-templates/example)
- [Example Playground](/page-templates/example-playground)
- [Advanced Example Layout](/page-templates/advanced-example)
- [Advanced Example Playground](/page-templates/advanced-example-playground)
- [Music Awesome Layout](/page-templates/music-awesome)
- [Music Awesome Playground](/page-templates/music-awesome-playground)
- [Sport Awesome Layout](/page-templates/sport-awesome)
- [Sport Awesome Playground](/page-templates/sport-awesome-playground)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,94 @@
---
import Themer from '../components/Themer.astro';
import { customElementsManifest } from '../js/cem.js';
const { frontmatter } = Astro.props
const version = customElementsManifest().version
---
<!DOCTYPE html>
<html
lang="en"
data-shoelace-version={version}
>
<head>
{/* Metadata */}
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content={ frontmatter.description } />
<title>{ frontmatter.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="/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={frontmatter.image} />
{/* OpenGraph */}
<meta property="og:url" content={frontmatter.url} />
<meta property="og:title" content={frontmatter.title} />
<meta property="og:description" content={frontmatter.description} />
<!-- TODO: frontmatter.image should have a fallback -->
<meta property="og:image" content={frontmatter.image} />
{/* WebAwesome */}
<link id="theme-stylesheet" 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 is:global>
*, *:before, *:after {
box-sizing: border-box;
}
html, body {
min-height: 100%;
height: 100%;
margin: 0;
}
body {
padding: 0;
}
[hidden] {
display: none !important;
}
body #knobs {
position: sticky;
top: 0rem;
left: 0rem;
border-radius: 0;
box-shadow: none;
width: 100%;
min-height: 99vh;
max-height: 99vh;
margin-bottom: 0;
}
body #knobs .logo-controls {
display: none;
}
</style>
</head>
<body>
<slot />
</body>
</html>

View File

@@ -0,0 +1,47 @@
---
import * as SportAwesome from "./sport-awesome/sport-awesome.astro"
import * as MusicAwesome from "./music-awesome/music-awesome.astro"
import * as AdvancedExample from "./advanced-example/advanced-example.astro"
import * as Example from "./example/example.astro"
import Playground from "../../components/Playground.astro"
export const templates = [
{
params: {slug: "sport-awesome"},
exports: SportAwesome
},
{
params: {slug: "music-awesome"},
exports: MusicAwesome
},
{
params: {slug: "advanced-example"},
exports: AdvancedExample
},
{
params: {slug: "example"},
exports: Example
},
]
export const playgrounds = templates.map((template) => {
const newTemplate = {
...template,
params: { slug: template.params.slug + "-playground" },
}
return newTemplate
})
export function getStaticPaths() {
return [...templates, ...playgrounds]
}
const { slug } = Astro.params;
const template = templates.find((page) => page.params.slug === slug);
const playground = playgrounds.find((page) => page.params.slug === slug);
if (!template && !playground) return Astro.redirect("/404");
---
{template && <template.exports.default />}
{playground && <Playground html={playground.exports.html} css={playground.exports.css} />}

View File

@@ -0,0 +1,14 @@
html {
height: auto;
}
body {
height: 100%;
}
html,
body {
min-height: 100%;
padding: 0 !important;
margin: 0;
}

View File

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

View File

@@ -0,0 +1,16 @@
<wa-page 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-page>

View File

@@ -0,0 +1,25 @@
---
import PageLayout from "../../../layouts/PageLayout.astro"
import * as fs from "node:fs"
import * as path from 'node:path';
export const dir = path.join(process.cwd(), "src", "pages", "page-templates", "advanced-example")
export const title = "Advanced Example"
export const description = "Advanced Example"
export const html = fs.readFileSync(path.resolve(dir, "_advanced-example.html"), { encoding: "utf-8" }).replaceAll(/<\/script>/g, "&lt;/script>")
export const cssReset = fs.readFileSync(path.resolve(dir, "../_reset.css"), { encoding: "utf-8" })
export const css = cssReset + "\n" + fs.readFileSync(path.resolve(dir, "_advanced-example.css"), { encoding: "utf-8" })
---
<style set:html={css}></style>
<PageLayout frontmatter={{
title,
description,
}}>
<Fragment set:html={html} />
</PageLayout>

View File

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

View File

@@ -0,0 +1,47 @@
<wa-page>
<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-page>
<style>
wa-page {
--menu-width: 260px;
outline: dashed 1px dodgerblue;
max-width: 1280px;
margin: 0 auto;
}
wa-page::part(menu) {
border-right: solid 1px #ececec;
}
wa-page::part(main-content) {
padding: 2rem;
}
</style>
<script>
const dialog = document.getElementById('dialog');
dialog.nextElementSibling.addEventListener('click', () => {
dialog.open = true;
});
</script>

View File

@@ -0,0 +1,25 @@
---
import PageLayout from "../../../layouts/PageLayout.astro"
import * as fs from "node:fs"
import * as path from 'node:path';
export const title = "Example"
export const description = "Example"
export const dir = path.join(process.cwd(), "src", "pages", "page-templates", "example")
export const html = fs.readFileSync(path.resolve(dir, "_example.html"), { encoding: "utf-8" }).replaceAll(/<\/script>/g, "&lt;/script>")
export const cssReset = fs.readFileSync(path.resolve(dir, "../_reset.css"), { encoding: "utf-8" })
export const css = cssReset + "\n" + fs.readFileSync(path.resolve(dir, "_example.css"), { encoding: "utf-8" })
---
<style set:html={css}></style>
<PageLayout frontmatter={{
title,
description,
}}>
<Fragment set:html={html} />
</PageLayout>

View File

@@ -0,0 +1,198 @@
main {
min-height: 100%;
padding: 1rem 2rem;
}
/* Layout */
wa-page {
background-color: var(--wa-color-surface-lowered);
color: var(--wa-color-text-normal);
}
wa-card :is(p, h3) {
margin: 0;
}
wa-page::part(header) {
/** Because headers are sticky, this keeps text from leaking through. */
background-color: var(--wa-color-surface-default);
}
wa-page::part(drawer__panel) {
height: 100%;
}
wa-page[view='mobile'] {
background-color: var(--wa-color-surface-default);
--menu-width: 0px;
}
wa-page[view='mobile']::part(header) {
padding: 0.25rem;
border-bottom: 1px solid var(--wa-color-neutral-70);
}
wa-page[view='desktop']::part(main) {
padding-top: 1rem;
}
wa-page[view='desktop']::part(navigation) {
padding-top: 1.9rem;
}
wa-page[view='desktop'] > [slot='header'] {
display: none;
}
wa-page[view='desktop']::part(header) {
display: none;
}
wa-page[view='desktop'] main {
background-color: var(--wa-color-surface-default);
box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.05);
border: 1px solid var(--wa-color-neutral-border-highlight);
border-top-left-radius: 8px;
}
/* Navigation */
.nav-list {
height: 100%;
list-style-type: '';
display: flex;
flex-direction: column;
gap: 8px;
}
.nav-list wa-button {
width: 100%;
&::part(base) {
justify-content: start;
}
}
/* 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-subtle);
color: var(--wa-color-success-text-on-fill);
}
.highlight--danger {
background-color: var(--wa-color-danger-fill-subtle);
color: var(--wa-color-danger-text-on-fill);
}
/* Text */
.text--light {
color: var(--wa-color-text-quiet);
}
/* 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;
&::part(base) {
/** by default, the `background` of outline buttons is "none" */
background: var(--background);
}
}
.wa-button--card.wa-button--muted {
--background: var(--wa-color-neutral-fill-subtle);
}
.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 {
--border-color: transparent;
--background: transparent;
--background-hover: var(--wa-color-neutral-fill-highlight);
--border-color-hover: var(--wa-color-neutral-border-highlight);
}
.wa-button--logo::part(base) {
font-size: 1.5rem;
color: var(--wa-color-text-on-fill);
}
.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-border-highlight);
--wa-spacing-large: 8px;
}
wa-page[view='desktop'] .wa-button--nav-footer::part(base) {
--border-color: transparent;
border-top-color: var(--wa-color-border-highlight);
}
/* Tables */
table {
max-width: 100%;
border: none;
border-collapse: collapse;
color: inherit;
}
table tr {
border-bottom: 1px solid var(--wa-color-border-highlight);
}
table th {
font-weight: var(--wa-font-weight-heading);
text-align: left;
padding: 0.75rem 1rem;
}
table td {
line-height: var(--wa-line-height-regular);
padding: 1rem;
}
* > table {
max-width: 100%;
overflow-x: auto;
}

View File

@@ -0,0 +1,454 @@
<wa-page main-id="main-content" class="wa-theme-light">
<header slot="header" style="display: flex; align-items: center; justify-content: space-between">
<wa-button
href="#"
variant="text"
style="padding: 0 0.4rem"
class="wa-button--logo wa-button--muted"
size="large"
slot="navigation-header"
>
<wa-icon name="music" slot="prefix" style="font-size: 2rem"></wa-icon>
Music Awesome
</wa-button>
<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" slot="prefix" style="font-size: 2rem"></wa-icon>
Music Awesome
</wa-button>
<nav style="padding: 1rem" slot="navigation">
<ul class="nav-list" role="list" style="">
<li>
<wa-button class="wa-button--muted" href="#" outline>
<wa-icon name="search" slot="prefix"></wa-icon>
Search
</wa-button>
</li>
<li>
<wa-button class="wa-button--muted" href="#" outline>
<wa-icon name="bell" slot="prefix"></wa-icon>
Notifications
</wa-button>
</li>
<wa-divider></wa-divider>
<li>
<wa-button href="#" variant="brand" aria-current="page">
<wa-icon name="house" slot="prefix"></wa-icon>
Home
</wa-button>
</li>
<li>
<wa-button class="wa-button--muted" href="#" outline>
<wa-icon name="play" slot="prefix"></wa-icon>
Playlists
</wa-button>
</li>
<li>
<wa-button class="wa-button--muted" href="#" outline>
<wa-icon name="compact-disc" slot="prefix"></wa-icon>
Tracks
</wa-button>
</li>
<li>
<wa-button class="wa-button--muted" href="#" outline>
<wa-icon name="gear" slot="prefix"></wa-icon>
Settings
</wa-button>
</li>
<li style="margin-top: auto">
<wa-button class="wa-button--muted" href="#" outline>
<wa-icon name="question-circle" slot="prefix"></wa-icon>
Help
</wa-button>
</li>
</ul>
</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: center; gap: 8px">
<h2 style="margin: 0">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 class="wa-button--card wa-button--muted" href="#" outline>
<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 class="wa-button--card wa-button--muted" outline 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 class="wa-button--card wa-button--muted" outline 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-page>

View File

@@ -0,0 +1,26 @@
---
import PageLayout from "../../../layouts/PageLayout.astro"
import * as fs from "node:fs"
import * as path from 'node:path';
export const title = "Music Awesome"
export const description = "Music Awesome"
export const dir = path.join(process.cwd(), "src", "pages", "page-templates", "music-awesome")
export const html = fs.readFileSync(path.resolve(dir, "_music-awesome.html"), { encoding: "utf-8" }).replaceAll(/<\/script>/g, "&lt;/script>")
export const cssReset = fs.readFileSync(path.resolve(dir, "../_reset.css"), { encoding: "utf-8" })
export const css = cssReset + "\n" + fs.readFileSync(path.resolve(dir, "_music-awesome.css"), { encoding: "utf-8" })
---
<style set:html={css}></style>
<PageLayout frontmatter={{
title,
description,
}}>
<Fragment set:html={html} />
</PageLayout>

View File

@@ -0,0 +1,197 @@
.wa-button--muted {
--border-color: transparent;
--background: transparent;
--background-hover: var(--wa-color-neutral-fill-subtle);
--border-color-hover: var(--wa-color-neutral-border-subtle);
}
/** 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 {
display: flex;
flex-direction: column;
}
.navigation--desktop {
gap: 2rem;
}
.navigation--top {
flex-direction: row;
}
.navigation--top wa-button {
font-size: 1.4rem;
font-weight: bold;
--border-color: transparent;
&::part(base) {
color: var(--wa-color-brand-text-on-spot);
}
&:hover::part(base) {
color: var(--wa-color-text-normal);
}
}
.navigation--top [aria-current='page'] {
&::part(base) {
text-decoration: underline;
text-decoration-color: var(--wa-color-danger-spot);
text-decoration-thickness: 4px;
text-underline-offset: 8px;
}
}
.header {
display: flex;
border-bottom: var(--wa-panel-border-width) var(--wa-panel-border-style) var(--wa-color-brand-fill-highlight);
background-color: var(--wa-color-surface-default);
}
.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-spot-darker);
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 [aria-current='page'] {
text-decoration: underline;
text-underline-offset: 8px;
text-decoration-thickness: 4px;
text-decoration-color: var(--wa-color-brand-outline-muted-alt);
}
.navigation-list {
display: grid;
grid-template-columns: auto;
place-content: center;
margin: 0;
list-style-type: '';
}
.navigation--desktop wa-nav-item[current='page']:hover {
text-decoration-color: var(--wa-color-brand-border-highlight);
}
.navigation--extra {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
margin-inline-start: auto;
}
wa-page[view='desktop'] [data-toggle-nav],
wa-page[view='desktop']::part(navigation) {
display: none;
}
wa-page[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-border);
}
.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 {
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;
}
}

View File

@@ -0,0 +1,370 @@
<wa-page
main-id="main-content"
class="wa-theme-light preview-container"
mobile-breakpoint="1000"
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">
<nav class="navigation navigation--top navigation--desktop">
<wa-button class="wa-button--muted" outline href="#" aria-current="page">Home</wa-button>
<wa-button class="wa-button--muted" outline href="#">Schedule</wa-button>
<wa-button class="wa-button--muted" outline href="#">Roster</wa-button>
<wa-button class="wa-button--muted" outline href="#">Stats</wa-button>
<wa-button class="wa-button--muted" outline href="#">Videos</wa-button>
</nav>
<nav class="navigation navigation--top navigation--social">
<wa-button outline class="wa-button--muted social-link" href="#">
<wa-icon name="instagram" family="brands"></wa-icon>
</wa-button>
<wa-button outline class="wa-button--muted social-link" href="#"
><wa-icon name="facebook" family="brands"></wa-icon
></wa-button>
<wa-button outline class="wa-button--muted" data-toggle-nav href="#"><wa-icon name="list"></wa-icon></wa-button>
</nav>
</div>
</header>
<a href="#" class="logo" slot="navigation-header"> Sport <span class="logo__accent">Awesome</span> </a>
<nav slot="navigation">
<ul role="list" class="navigation-list">
<li>
<wa-button class="wa-button--muted" outline href="#" aria-current="page">
<wa-icon name="house" slot="prefix"></wa-icon>
Home
</wa-button>
</li>
<li>
<wa-button class="wa-button--muted" outline href="#">
<wa-icon name="calendar" slot="prefix"></wa-icon>
Schedule
</wa-button>
</li>
<li>
<wa-button class="wa-button--muted" outline href="#">
<wa-icon name="people" slot="prefix"></wa-icon>
Roster
</wa-button>
</li>
<li>
<wa-button class="wa-button--muted" outline href="#">
<wa-icon name="chart-simple" slot="prefix"></wa-icon>
Stats
</wa-button>
</li>
<li>
<wa-button class="wa-button--muted" outline href="#">
<wa-icon name="video" slot="prefix"></wa-icon>
Videos
</wa-button>
</li>
</ul>
<wa-divider></wa-divider>
<ul role="list" class="navigation-list">
<li>
<wa-button class="wa-button--muted" outline href="#">
<wa-icon name="instagram" family="brands" slot="prefix"></wa-icon>
Instagram
</wa-button>
</li>
<li>
<wa-button class="wa-button--muted" outline href="#">
<wa-icon name="facebook" family="brands" slot="prefix"></wa-icon>
Facebook
</wa-button>
</li>
</ul>
</nav>
<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-spot);
background-color: var(--wa-color-brand-spot-darker);
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-danger-text-on-spot);
background-color: var(--wa-color-danger-spot-darker);
padding: var(--wa-space-s);
text-align: center;
font-weight: bold;
"
>
Tryouts!
</div>
<div
style="
background-color: var(--wa-color-neutral-fill-highlight);
color: var(--wa-color-neutral-text-on-fill);
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-page>

View File

@@ -0,0 +1,25 @@
---
import PageLayout from "../../../layouts/PageLayout.astro"
import * as fs from "node:fs"
import * as path from 'node:path';
export const title = "Sport Awesome"
export const description = "Sport Awesome"
export const dir = path.join(process.cwd(), "src", "pages", "page-templates", "sport-awesome")
export const html = fs.readFileSync(path.resolve(dir, "_sport-awesome.html"), { encoding: "utf-8" }).replaceAll(/<\/script>/g, "&lt;/script>")
export const cssReset = fs.readFileSync(path.resolve(dir, "../_reset.css"), { encoding: "utf-8" })
export const css = cssReset + "\n" + fs.readFileSync(path.resolve(dir, "_sport-awesome.css"), { encoding: "utf-8" })
---
<style set:html={css}></style>
<PageLayout frontmatter={{
title,
description,
}}>
<Fragment set:html={html} />
</PageLayout>

87
package-lock.json generated
View File

@@ -65,6 +65,7 @@
"husky": "^8.0.3",
"jsdom": "^22.1.0",
"jsonata": "^2.0.3",
"light-pen": "^3.0.1",
"lint-staged": "^14.0.1",
"lunr": "^2.3.9",
"markdown-it-container": "^3.0.0",
@@ -10946,6 +10947,12 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/form-associated-helpers": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/form-associated-helpers/-/form-associated-helpers-0.0.4.tgz",
"integrity": "sha512-Fy3dCmzxbaB08nRIr8GVBo5duQSN/NouCU4JjQ/Pd3bipsKMsV2RhPb3uGujEYYk/OIMDf9EPwRI5pQq6PsZpw==",
"dev": true
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
@@ -14302,6 +14309,58 @@
"node": ">=10.13.0"
}
},
"node_modules/light-pen": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/light-pen/-/light-pen-3.0.2.tgz",
"integrity": "sha512-XKq2owaReoBpl6HIZMPlr2L+fDABTteEp7QdAmH10t3Gh1jX670Xneicl/KkeE/vjjvm+aNuznkJvgfMNqpa8Q==",
"dev": true,
"dependencies": {
"form-associated-helpers": "^0.0.4",
"lit": "^2.8.0",
"prism-esm": "1.29.0-fix.4",
"web-component-define": "^2.0.11"
}
},
"node_modules/light-pen/node_modules/@lit/reactive-element": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz",
"integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==",
"dev": true,
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.0.0"
}
},
"node_modules/light-pen/node_modules/lit": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz",
"integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==",
"dev": true,
"dependencies": {
"@lit/reactive-element": "^1.6.0",
"lit-element": "^3.3.0",
"lit-html": "^2.8.0"
}
},
"node_modules/light-pen/node_modules/lit-element": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz",
"integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==",
"dev": true,
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.1.0",
"@lit/reactive-element": "^1.3.0",
"lit-html": "^2.8.0"
}
},
"node_modules/light-pen/node_modules/lit-html": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz",
"integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==",
"dev": true,
"dependencies": {
"@types/trusted-types": "^2.0.2"
}
},
"node_modules/lighthouse-logger": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz",
@@ -20693,6 +20752,15 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prism-esm": {
"version": "1.29.0-fix.4",
"resolved": "https://registry.npmjs.org/prism-esm/-/prism-esm-1.29.0-fix.4.tgz",
"integrity": "sha512-7/xHdroa6z9BhaGIpHA8SWIpefUz5l2eg2KT8pczFatyBiuni89q0jnKrScJbtFt1QkLwCRw36J1x8zUEGc5HA==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/prismjs": {
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
@@ -25954,6 +26022,25 @@
"defaults": "^1.0.3"
}
},
"node_modules/web-component-define": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/web-component-define/-/web-component-define-2.0.11.tgz",
"integrity": "sha512-oMOngNGL5k55NyaQNnPz0UnV5QtPuESvSuxpBgmHHfNjb8sgVm4akAFq7HO4I5bhGHX8Of+0XnUZ3whBo0chgw==",
"dev": true,
"dependencies": {
"@lit/reactive-element": "^1.6.1",
"@open-wc/dedupe-mixin": "^1.3.1"
}
},
"node_modules/web-component-define/node_modules/@lit/reactive-element": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz",
"integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==",
"dev": true,
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.0.0"
}
},
"node_modules/web-namespaces": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",

View File

@@ -121,6 +121,7 @@
"husky": "^8.0.3",
"jsdom": "^22.1.0",
"jsonata": "^2.0.3",
"light-pen": "^3.0.1",
"lint-staged": "^14.0.1",
"lunr": "^2.3.9",
"markdown-it-container": "^3.0.0",

View File

@@ -84,7 +84,8 @@ export default css`
display: none;
}
.details__body {
/* Overflows get clipped during the closing animation if we don't wait until the close is gone. */
:not(.details--open) .details__body {
overflow: hidden;
}

View File

@@ -0,0 +1,279 @@
import { html } from 'lit';
import { live } from 'lit/directives/live.js';
import { property, query } from 'lit/decorators.js';
import styles from './page.styles.js';
import WaDrawer from '../drawer/drawer.component.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import type { CSSResultGroup, PropertyValueMap } from 'lit';
/**
* @summary Pages offer an easy way to scaffold pages using minimal markup.
* @documentation https://shoelace.style/components/page
* @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 navigation-header - The header for a navigation area. On mobile this will be the header for `<wa-drawer>`.
* @csspart navigation-footer - The footer for a navigation area. On mobile this will be the footer for `<wa-drawer>`.
* @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-links - Wrapper around skip-link
* @csspart skip-link - The "skip to main content" link
* @csspart footer - The footer of the page. This is always below the initial viewport size.
* @csspart dialog-wrapper - A wrapper around elements such as dialogs or other modal-like elements.
*
* @cssproperty [--menu-width=auto] - The width of the page's "menu" section.
* @cssproperty [--main-width=1fr] - The width of the page's "main" section.
* @cssproperty [--aside-width=auto] - The wide of the page's "aside" section.
* @cssproperty [--banner-height=0px] - The height of the banner. This gets calculated when the page 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 page 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 page initializes. If the height is known, you can set it here to prevent shifting when the page loads.
*/
export default class WaPage 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 page 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';
pageResizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
if (entry.contentBoxSize) {
const contentBoxSize = entry.borderBoxSize[0];
const pageWidth = contentBoxSize.inlineSize;
const oldView = this.view;
if (pageWidth >= this.mobileBreakpoint) {
this.view = 'desktop';
} else {
this.view = 'mobile';
}
this.requestUpdate('view', oldView);
this.style.setProperty(`--page-width`, `${pageWidth}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.pageResizeObserver.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.pageResizeObserver.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">
<!-- Add fallback divs so that CSS grid works properly. -->
<slot name=${this.view === 'desktop' ? 'navigation-header' : '___'}><div></div></slot>
<slot name=${this.view === 'desktop' ? 'navigation' : '____'}></slot>
<slot name=${this.view === 'desktop' ? 'navigation-footer' : '___'}><div></div></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 part="navigation-header" slot="label" name=${this.view === 'mobile' ? 'navigation-header' : '___'}></slot>
<slot name=${this.view === 'mobile' ? 'navigation' : '____'}></slot>
<slot
part="navigation-footer"
slot="footer"
name=${this.view === 'mobile' ? 'navigation-footer' : '___'}
></slot>
</wa-drawer>
`;
}
}

View File

@@ -0,0 +1,174 @@
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;
}
:host([view='mobile']) [part~='navigation'] {
display: none;
}
[part~='navigation'] {
height: 100%;
display: grid;
grid-template-columns: minmax(0, 1fr);
grid-template-rows: minmax(0, auto) minmax(0, 1fr) minmax(0, auto);
}
`;

View File

@@ -0,0 +1,10 @@
import '../../../dist/webawesome.js';
import { expect, fixture, html } from '@open-wc/testing';
describe('<wa-page>', () => {
it('should render a component', async () => {
const el = await fixture(html` <wa-page></wa-page> `);
expect(el).to.exist;
});
});

View File

@@ -0,0 +1,10 @@
import WaPage from './page.component.js';
export * from './page.component.js';
export default WaPage;
WaPage.define('wa-page');
declare global {
interface HTMLElementTagNameMap {
'wa-page': WaPage;
}
}

View File

@@ -32,6 +32,7 @@ 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 WaOption } from './components/option/option.js';
export { default as WaPage } from './components/page/page.js';
export { default as WaPopup } from './components/popup/popup.js';
export { default as WaProgressBar } from './components/progress-bar/progress-bar.js';
export { default as WaProgressRing } from './components/progress-ring/progress-ring.js';