Compare commits

..

29 Commits

Author SHA1 Message Date
Dave Gandy
f278ce1d6d evening up line breaks 2024-03-12 12:12:08 -05:00
lindsaym-fa
0bbf9bb275 hide astro dev toolbar for layouts 2024-03-12 13:09:15 -04:00
lindsaym-fa
4deb928682 add configurable drawer background color 2024-03-12 13:04:49 -04:00
lindsaym-fa
4066e8c591 Merge remote-tracking branch 'origin/konnorrogers/layout-with-astro' into demo-layout 2024-03-12 12:33:25 -04:00
konnorrogers
8eb6ee6609 fix grid for navigation 2024-03-12 12:19:51 -04:00
lindsaym-fa
620525891d add nav toggle to advanced layout 2024-03-12 11:41:56 -04:00
konnorrogers
384147502f rename to pagE 2024-03-12 10:36:55 -04:00
lindsaym-fa
730e20bd38 remove extraneous classes 2024-03-12 10:03:32 -04:00
konnorrogers
9b3669c428 Merge branch 'next' of https://github.com/shoelace-style/webawesome into konnorrogers/layout-with-astro 2024-02-28 15:08:03 -05:00
konnorrogers
b003273175 remove vercel.json 2024-02-27 16:09:35 -05:00
konnorrogers
faae5c21fd remove sandbox-settings 2024-02-27 15:51:44 -05:00
konnorrogers
af89149939 maybe now? 2024-02-27 15:40:40 -05:00
konnorrogers
9b5ceb989c add sandbox-settings 2024-02-27 15:31:35 -05:00
konnorrogers
7862c374dc add sandbox-settings 2024-02-27 15:28:43 -05:00
konnorrogers
03508ec523 add sandbox-settings 2024-02-27 15:21:00 -05:00
konnorrogers
aadf4830c6 add vercel.json 2024-02-27 14:36:40 -05:00
konnorrogers
cd282e50ef add vercel.json 2024-02-27 14:30:35 -05:00
konnorrogers
57ddd25662 add vercel.json 2024-02-27 14:23:25 -05:00
konnorrogers
a37f2d594e add vercel.json 2024-02-27 14:18:25 -05:00
konnorrogers
6fbf921f4b add vercel.json 2024-02-27 14:14:39 -05:00
konnorrogers
c11f3d468b prettier 2024-02-27 13:41:55 -05:00
konnorrogers
306eefa44b rename layout to page 2024-02-27 13:39:55 -05:00
konnorrogers
cd237d3057 light-pen 3 2024-02-26 16:24:34 -05:00
konnorrogers
c7757b8cbe working on astro layout 2024-02-22 14:33:49 -05:00
konnorrogers
4b8a86f0e2 continued work on layouts 2024-02-13 18:40:37 -05:00
konnorrogers
41de947779 working on playgrounds 2024-02-09 16:32:45 -05:00
konnorrogers
11337197d7 first layout converted to sportawesome 2024-02-07 13:41:12 -05:00
konnorrogers
f1739309eb fix buildS 2024-02-07 12:46:46 -05:00
konnorrogers
5007924dbd working on layouts 2024-02-06 16:24:25 -05:00
158 changed files with 2525 additions and 1882 deletions

View File

@@ -35,7 +35,7 @@ This Code of Conduct applies within all project spaces, and it also applies when
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at support@fontawesome.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at cory@abeautifulsite.net. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: [claviska]

2
.github/SECURITY.md vendored
View File

@@ -2,6 +2,6 @@
We take security issues in Web Awesome very seriously and appreciate your efforts to disclose your findings responsibly.
To report a security issue, email [support@fontawesome.com](mailto:support@fontawesome.com) and include "WEB AWESOME SECURITY" in the subject line.
To report a security issue, email [cory@fontawesome.com](mailto:cory@abeautifulsite.net) and include "WEB AWESOME SECURITY" in the subject line.
We'll respond as soon as possible and keep you updated throughout the process.

View File

@@ -1,5 +1,4 @@
*.hbs
*.mdx
.cache
.github
cspell.json

View File

@@ -1,5 +1,7 @@
# Web Awesome
A forward-thinking library of web components.
- Works with all frameworks 🧩
- Works with CDNs 🚛
- Fully customizable with CSS 🎨

View File

@@ -21,6 +21,7 @@
"cdndir",
"chatbubble",
"checkmark",
"claviska",
"Clippy",
"codebases",
"codepen",
@@ -85,6 +86,7 @@
"Kool",
"labelledby",
"Laravel",
"LaViska",
"linkify",
"listbox",
"listitem",

View File

@@ -1,3 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.63 3.625C11.63 4.27911 11.2435 4.84296 10.6865 5.10064L14 8L17.2622 7.34755C17.0968 7.10642 17 6.81452 17 6.5C17 5.67157 17.6716 5 18.5 5C19.3284 5 20 5.67157 20 6.5C20 7.31157 19.3555 7.9726 18.5504 7.99917L15.0307 15.8207C14.7077 16.5384 13.9939 17 13.2068 17H6.79317C6.00615 17 5.29229 16.5384 4.96933 15.8207L1.44963 7.99917C0.64452 7.9726 0 7.31157 0 6.5C0 5.67157 0.671573 5 1.5 5C2.32843 5 3 5.67157 3 6.5C3 6.81452 2.9032 7.10642 2.73777 7.34755L6 8L9.31702 5.09761C8.76346 4.83855 8.38 4.27656 8.38 3.625C8.38 2.72754 9.10754 2 10.005 2C10.9025 2 11.63 2.72754 11.63 3.625Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 722 B

View File

@@ -33,23 +33,22 @@ export default defineConfig({
server: {
open: true,
port: 4000,
host: true
host: true,
fs: {
strict: false
}
},
vite: {
server: {
watch: {
ignored: ['./public/pagefind/**/*.*'] // HERE
}
},
plugins: [
FullReload([
path.relative(__dirname, '../dist/custom-elements.json')
// path.relative(__dirname, './public/**/*.*')
path.relative(__dirname, '../dist/custom-elements.json'),
path.relative(__dirname, './public/**/*.*')
])
]
},
outDir: '../_site',
site: 'https://shoelace.style',
compressHTML: false,
markdown: {
syntaxHighlight: 'prism',
remarkPlugins: [

View File

@@ -0,0 +1,389 @@
---
---
<script>
;(() => {
// Append the search dialog to the body
const siteSearch = document.createElement('div');
const scrollbarWidth = Math.abs(window.innerWidth - document.documentElement.clientWidth);
siteSearch.classList.add('search');
siteSearch.innerHTML = `
<div class="search__overlay"></div>
<dialog id="search-dialog" class="search__dialog">
<div class="search__content">
<div class="search__header">
<div id="search-combobox" class="search__input-wrapper">
<wa-icon name="search"></wa-icon>
<input
id="search-input"
class="search__input"
type="search"
placeholder="Search"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
enterkeyhint="go"
spellcheck="false"
maxlength="100"
role="combobox"
aria-autocomplete="list"
aria-expanded="true"
aria-controls="search-listbox"
aria-haspopup="listbox"
aria-activedescendant
>
<button type="button" class="search__clear-button" aria-label="Clear entry" tabindex="-1" hidden>
<wa-icon name="circle-xmark" variant="regular"></wa-icon>
</button>
</div>
</div>
<div class="search__body">
<ul
id="search-listbox"
class="search__results"
role="listbox"
aria-label="Search results"
></ul>
<div class="search__empty">No matching pages</div>
</div>
<footer class="search__footer">
<small><kbd><wa-icon label="Up" name="arrow-up"></wa-icon></kbd> <kbd><wa-icon label="Down" name="arrow-down"></wa-icon></kbd> Navigate</small>
<small><kbd><wa-icon label="Enter" name="arrow-turn-down-left"></wa-icon></kbd> Select</small>
<small><kbd>Esc</kbd> Close</small>
</footer>
</div>
</dialog>
`;
const overlay = siteSearch.querySelector('.search__overlay');
const dialog = siteSearch.querySelector('.search__dialog');
const input = siteSearch.querySelector('.search__input');
const clearButton = siteSearch.querySelector('.search__clear-button');
const results = siteSearch.querySelector('.search__results');
const version = document.documentElement.getAttribute('data-wa-version');
const key = `search_${version}`;
const searchDebounce = 50;
const animationDuration = 150;
let isShowing = false;
let searchTimeout;
let searchIndex;
let map;
const loadSearchIndex = new Promise(resolve => {
const cache = localStorage.getItem(key);
const wait = 'requestIdleCallback' in window ? requestIdleCallback : requestAnimationFrame;
// Cleanup older search indices (everything before this version)
try {
const items = { ...localStorage };
Object.keys(items).forEach(k => {
if (key > k) {
localStorage.removeItem(k);
}
});
} catch {
/* do nothing */
}
// Look for a cached index
try {
if (cache) {
const data = JSON.parse(cache);
searchIndex = window.lunr.Index.load(data.searchIndex);
map = data.map;
return resolve();
}
} catch {
/* do nothing */
}
// Wait until idle to fetch the index
wait(() => {
fetch('/assets/search.json')
.then(res => res.json())
.then(data => {
if (!window.lunr) {
console.error('The Lunr search client has not yet been loaded.');
}
searchIndex = window.lunr.Index.load(data.searchIndex);
map = data.map;
// Cache the search index for this version
if (version) {
try {
localStorage.setItem(key, JSON.stringify(data));
} catch (err) {
console.warn(`Unable to cache the search index: ${err}`);
}
}
resolve();
});
});
});
async function show() {
isShowing = true;
document.body.append(siteSearch);
document.body.classList.add('search-visible');
document.body.style.setProperty('--docs-search-scroll-lock-size', `${scrollbarWidth}px`);
clearButton.hidden = true;
requestAnimationFrame(() => input.focus());
updateResults();
dialog.showModal();
await Promise.all([
dialog.animate(
[
{ opacity: 0, transform: 'scale(.9)', transformOrigin: 'top' },
{ opacity: 1, transform: 'scale(1)', transformOrigin: 'top' }
],
{ duration: animationDuration }
).finished,
overlay.animate([{ opacity: 0 }, { opacity: 1 }], { duration: animationDuration }).finished
]);
dialog.addEventListener('mousedown', handleMouseDown);
dialog.addEventListener('keydown', handleKeyDown);
}
async function hide() {
isShowing = false;
await Promise.all([
dialog.animate(
[
{ opacity: 1, transform: 'scale(1)', transformOrigin: 'top' },
{ opacity: 0, transform: 'scale(.9)', transformOrigin: 'top' }
],
{ duration: animationDuration }
).finished,
overlay.animate([{ opacity: 1 }, { opacity: 0 }], { duration: animationDuration }).finished
]);
dialog.close();
input.blur(); // otherwise Safari will scroll to the bottom of the page on close
input.value = '';
document.body.classList.remove('search-visible');
document.body.style.removeProperty('--docs-search-scroll-lock-size');
siteSearch.remove();
updateResults();
dialog.removeEventListener('mousedown', handleMouseDown);
dialog.removeEventListener('keydown', handleKeyDown);
}
function handleInput() {
clearButton.hidden = input.value === '';
// Debounce search queries
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => updateResults(input.value), searchDebounce);
}
function handleClear() {
clearButton.hidden = true;
input.value = '';
input.focus();
updateResults();
}
function handleMouseDown(event) {
if (!event.target.closest('.search__content')) {
hide();
}
}
function handleKeyDown(event) {
// Close when pressing escape
if (event.key === 'Escape') {
event.preventDefault(); // prevent <dialog> from closing immediately so it can animate
event.stopImmediatePropagation();
hide();
return;
}
// Handle keyboard selections
if (['ArrowDown', 'ArrowUp', 'Home', 'End', 'Enter'].includes(event.key)) {
event.preventDefault();
const currentEl = results.querySelector('[data-selected="true"]');
const items = [...results.querySelectorAll('li')];
const index = items.indexOf(currentEl);
let nextEl;
if (items.length === 0) {
return;
}
switch (event.key) {
case 'ArrowUp':
nextEl = items[Math.max(0, index - 1)];
break;
case 'ArrowDown':
nextEl = items[Math.min(items.length - 1, index + 1)];
break;
case 'Home':
nextEl = items[0];
break;
case 'End':
nextEl = items[items.length - 1];
break;
case 'Enter':
currentEl?.querySelector('a')?.click();
break;
}
// Update the selected item
items.forEach(item => {
if (item === nextEl) {
input.setAttribute('aria-activedescendant', item.id);
item.setAttribute('data-selected', 'true');
nextEl.scrollIntoView({ block: 'nearest' });
} else {
item.setAttribute('data-selected', 'false');
}
});
}
}
async function updateResults(query = '') {
try {
await loadSearchIndex;
const hasQuery = query.length > 0;
const searchTerms = query
.split(' ')
.map((term, index, arr) => {
// Search API: https://lunrjs.com/guides/searching.html
if (index === arr.length - 1) {
// The last term is not mandatory and 1x fuzzy. We also duplicate it with a wildcard to match partial words
// as the user types.
return `${term}~1 ${term}*`;
} else {
// All other terms are mandatory and 1x fuzzy
return `+${term}~1`;
}
})
.join(' ');
const matches = hasQuery ? searchIndex.search(searchTerms) : [];
const hasResults = hasQuery && matches.length > 0;
siteSearch.classList.toggle('search--has-results', hasQuery && hasResults);
siteSearch.classList.toggle('search--no-results', hasQuery && !hasResults);
input.setAttribute('aria-activedescendant', '');
results.innerHTML = '';
matches.forEach((match, index) => {
const page = map[match.ref];
const li = document.createElement('li');
const a = document.createElement('a');
const displayTitle = page.title ?? '';
const displayDescription = page.description ?? '';
const displayUrl = page.url.replace(/^\//, '').replace(/\/$/, '');
let icon = 'file-text';
a.setAttribute('role', 'option');
a.setAttribute('id', `search-result-item-${match.ref}`);
if (page.url.includes('getting-started/')) {
icon = 'lightbulb';
}
if (page.url.includes('resources/')) {
icon = 'book';
}
if (page.url.includes('components/')) {
icon = 'puzzle-piece';
}
if (page.url.includes('tokens/')) {
icon = 'swatchbook';
}
if (page.url.includes('utilities/')) {
icon = 'wrench';
}
if (page.url.includes('tutorials/')) {
icon = 'gamepad';
}
li.classList.add('search__result');
li.setAttribute('role', 'option');
li.setAttribute('id', `search-result-item-${match.ref}`);
li.setAttribute('data-selected', index === 0 ? 'true' : 'false');
a.href = page.url;
a.innerHTML = `
<div class="search__result-icon" aria-hidden="true">
<wa-icon name="${icon}"></wa-icon>
</div>
<div class="search__result__details">
<div class="search__result-title"></div>
<div class="search__result-description"></div>
<div class="search__result-url"></div>
</div>
`;
a.querySelector('.search__result-title').textContent = displayTitle;
a.querySelector('.search__result-description').textContent = displayDescription;
a.querySelector('.search__result-url').textContent = displayUrl;
li.appendChild(a);
results.appendChild(li);
});
} catch {
// Ignore query errors as the user types
}
}
// Show the search dialog when clicking on data-plugin="search"
document.addEventListener('click', event => {
const searchButton = event.target.closest('[data-plugin="search"]');
if (searchButton) {
show();
}
});
// Show the search dialog when slash (or CMD+K) is pressed and focus is not inside a form element
document.addEventListener('keydown', event => {
if (
!isShowing &&
(event.key === '/' || (event.key === 'k' && (event.metaKey || event.ctrlKey))) &&
!event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))
) {
event.preventDefault();
show();
}
});
// Purge cache when we press CMD+CTRL+R
document.addEventListener('keydown', event => {
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key === 'r') {
localStorage.clear();
}
});
input.addEventListener('input', handleInput);
clearButton.addEventListener('click', handleClear);
// Close when a result is selected
results.addEventListener('click', event => {
if (event.target.closest('a')) {
hide();
}
});
// We're using Turbo, so when a user searches for something, visits a result, and presses the back button, the search
// UI will still be visible but not interactive. This removes the search UI when Turbo renders a page so they don't
// get trapped.
window.addEventListener('turbo:render', () => {
document.body.classList.remove('search-visible');
document.querySelectorAll('.search__overlay, .search__dialog').forEach(el => el.remove());
});
})();
</script>

View File

@@ -328,7 +328,7 @@ wa-select[label="Signet"]::part(form-control-help-text) {
</div>
</form>
<wa-dialog id="icon-chooser" label="Browse Icons" with-header>
<wa-dialog id="icon-chooser" label="Browse Icons">
<div style="display: grid; grid-template-rows: minmax(0, auto) minmax(0, 1fr); height: 100%; gap: 1rem;">
<div style="display: flex; gap: 1.25rem;">
<wa-input name="icon-search" placeholder="Search Icons" clearable style="flex: 1 1 auto;">

View File

@@ -8,7 +8,6 @@ import Search from "../Search.astro"
import '../../styles/global.css'
import '../../styles/syntax-highlight.css'
import '../../styles/code-previews.css'
import '../../styles/font-awesome.css'
import { customElementsManifest } from '../../js/cem';
const version = customElementsManifest().package.version

View File

@@ -52,220 +52,143 @@ const pagefindTranslations = {
<script>
class SiteSearch extends HTMLElement {
connectedCallback() {
// super.connectedCallback();
const openBtn = () => this.querySelector<HTMLButtonElement>('button[data-open-modal]')!;
const closeBtn = () => this.querySelector<HTMLButtonElement>('button[data-close-modal]')!;
const dialog = () => this.querySelector('dialog')!;
const dialogFrame = () => this.querySelector('.dialog-frame')!;
translations = {}
stripTrailingSlash = (path: string) => path.replace(/(.)\/(#.*)?$/, '$1$2');
/** Close the modal if a user clicks on a link or outside of the modal. */
const onClick = (event: MouseEvent) => {
const isLink = 'href' in (event.target || {});
if (
isLink ||
(document.body.contains(event.target as Node) &&
!dialogFrame().contains(event.target as Node))
) {
closeModal();
}
};
const openModal = (event?: MouseEvent) => {
dialog().showModal();
document.body.toggleAttribute('data-search-modal-open', true);
this.querySelector('input')?.focus();
event?.stopPropagation();
window.addEventListener('click', onClick);
};
const closeModal = () => dialog().close();
openBtn().addEventListener('click', openModal);
openBtn().disabled = false;
closeBtn().addEventListener('click', closeModal);
dialog().addEventListener('close', () => {
document.body.toggleAttribute('data-search-modal-open', false);
window.removeEventListener('click', onClick);
});
// Listen for `/` and `cmd + k` keyboard shortcuts.
window.addEventListener('keydown', (e) => {
const isInput =
document.activeElement instanceof HTMLElement &&
(['input', 'select', 'textarea'].includes(document.activeElement.tagName.toLowerCase()) ||
document.activeElement.isContentEditable);
if (e.metaKey === true && e.key === 'k') {
dialog().open ? closeModal() : openModal();
e.preventDefault();
} else if (e.key === '/' && !dialog().open && !isInput) {
openModal();
e.preventDefault();
}
});
let translations = {};
connectedCallback () {
this.translations = {};
try {
translations = JSON.parse(this.dataset.translations || '{}');
this.translations = JSON.parse(this.dataset.translations || '{}');
} catch {}
const shouldStrip = this.dataset.stripTrailingSlash !== undefined;
const stripTrailingSlash = (path: string) => path.replace(/(.)\/(#.*)?$/, '$1$2');
const formatURL = shouldStrip ? stripTrailingSlash : (path: string) => path;
document.querySelector("#starlight__search").innerHTML = ""
this.openBtn.addEventListener('click', this.openModal);
this.openBtn.disabled = false;
this.closeBtn.addEventListener('click', this.closeModal);
this.dialog.addEventListener('close', this.handleClose)
window.addEventListener('turbo:load', () => {
document.querySelector("#starlight__search").innerHTML = ""
// Listen for `/` and `cmd + k` keyboard shortcuts.
window.addEventListener('keydown', this.handleKeyDown);
const onIdle = window.requestIdleCallback || ((cb) => setTimeout(cb, 1));
onIdle(async () => {
if (import.meta.env.DEV) {
// Generate a fake search in dev by calling a JSON endpoint that generates the search.
await fetch(import.meta.env.BASE_URL.replace(/\/$/, '') + '/pagefind-dev.json')
}
window.addEventListener('DOMContentLoaded', this.loadPageFind);
window.addEventListener('turbo:load', this.loadPageFind);
// @ts-expect-error — Missing types for @pagefind/default-ui package.
const { PagefindUI } = await import('@pagefind/default-ui');
}
document.querySelector("#starlight__search").innerHTML = ""
new PagefindUI({
element: '#starlight__search',
baseUrl: import.meta.env.BASE_URL,
bundlePath: import.meta.env.BASE_URL.replace(/\/$/, '') + '/pagefind/',
showImages: false,
translations,
showSubResults: true,
processResult: (result: { url: string; sub_results: Array<{ url: string }> }) => {
result.url = formatURL(result.url);
result.sub_results = result.sub_results.map((sub_result) => {
sub_result.url = formatURL(sub_result.url);
return sub_result;
});
},
});
disconnectedCallback () {
this.openBtn.removeEventListener('click', this.openModal);
this.closeBtn.removeEventListener('click', this.closeModal);
this.dialog.removeEventListener('close', this.handleClose)
// Listen for `/` and `cmd + k` keyboard shortcuts.
window.removeEventListener('keydown', this.handleKeyDown);
window.removeEventListener('DOMContentLoaded', this.loadPageFind);
window.removeEventListener('turbo:load', this.loadPageFind);
}
/** Close the modal if a user clicks on a link or outside of the modal. */
onClick = (event: MouseEvent) => {
const isLink = 'href' in (event.target || {});
if (
isLink ||
(document.body.contains(event.target as Node) &&
!this.dialogFrame.contains(event.target as Node))
) {
this.closeModal();
}
};
closeModal = () => this.dialog.close();
openModal = (event?: MouseEvent) => {
this.dialog.showModal();
document.body.toggleAttribute('data-search-modal-open', true);
this.querySelector('input')?.focus();
event?.stopPropagation();
window.addEventListener('click', this.onClick);
};
get openBtn () {
return this.querySelector<HTMLButtonElement>('button[data-open-modal]')!;
}
get closeBtn () {
return this.querySelector<HTMLButtonElement>('button[data-close-modal]')!;
}
get dialog () {
return this.querySelector('dialog')!;
}
get dialogFrame () {
return this.querySelector('.dialog-frame')!;
}
handleClose = () => {
document.body.toggleAttribute('data-search-modal-open', false);
window.removeEventListener('click', this.onClick);
};
handleKeyDown = (e: KeyboardEvent) => {
const isInput =
document.activeElement instanceof HTMLElement &&
(['input', 'select', 'textarea'].includes(document.activeElement.tagName.toLowerCase()) ||
document.activeElement.isContentEditable);
if (e.metaKey === true && e.key === 'k') {
this.dialog.open ? this.closeModal() : this.openModal();
e.preventDefault();
} else if (e.key === '/' && !this.dialog.open && !isInput) {
this.openModal();
e.preventDefault();
}
}
loadPageFind = () => {
const onIdle = window.requestIdleCallback || ((cb) => setTimeout(cb, 1));
onIdle(async () => {
if (import.meta.env.DEV) {
// Generate a fake search in dev by calling a JSON endpoint that generates the search.
await fetch(import.meta.env.BASE_URL.replace(/\/$/, '') + '/pagefind-dev.json')
}
const search = document.querySelector("#starlight__search")
// Clear out search between page loads.
if (search) {
search.firstElementChild?.remove()
}
// @ts-expect-error — Missing types for @pagefind/default-ui package.
const { PagefindUI } = await import('@pagefind/default-ui');
new PagefindUI({
element: '#starlight__search',
baseUrl: import.meta.env.BASE_URL,
bundlePath: import.meta.env.BASE_URL.replace(/\/$/, '') + '/pagefind/',
showImages: false,
translations: this.translations,
showSubResults: true,
processResult: (result: { url: string; sub_results: Array<{ url: string }> }) => {
result.url = this.formatURL(result.url);
result.sub_results = result.sub_results.map((sub_result) => {
sub_result.url = this.formatURL(sub_result.url);
return sub_result;
});
},
});
});
let links: (HTMLAnchorElement | HTMLButtonElement)[] = []
function findSelectionIndex () {
let index = links.findIndex((link) => link.getAttribute("aria-selected") === "true")
return index
}
function selectPrevious () {
selectInDirection("backward")
}
function selectInDirection (direction: "forward" | "backward") {
const index = findSelectionIndex()
const step = direction === "forward" ? 1 : -1
const isOutOfRange = !(index + step < links.length && index + step >= 0)
if (isOutOfRange) {
return
}
const prevLink = links[index]
if (prevLink) {
prevLink.removeAttribute("aria-selected")
let prevResult = prevLink.closest(".pagefind-ui__result-nested, .pagefind-ui__button")
if (!prevResult) {
prevResult = prevLink.closest(".pagefind-ui__result-title")
}
if (prevResult) {
prevResult.classList.remove("is-active")
}
}
const nextLink = links[index + step]
nextLink.setAttribute("aria-selected", "true")
nextLink.scrollIntoView({ block: "center" })
let nextResult = nextLink.closest(".pagefind-ui__result-nested, .pagefind-ui__button")
if (!nextResult) {
nextResult = nextLink.closest(".pagefind-ui__result-title")
}
if (nextResult) {
nextResult.classList.add("is-active")
}
}
function selectNext () {
selectInDirection("forward")
}
function handleKeyDown(e) {
if (e.key === "Enter") {
e.preventDefault()
const currentItem = links[findSelectionIndex()]
// When we're on the page find UI button, we want to jump to the previous result before showing more results.
if (currentItem.tagName === "BUTTON") {
selectPrevious()
}
currentItem.click()
}
if (e.key === "ArrowUp") {
e.preventDefault()
selectPrevious()
return
}
if (e.key === "ArrowDown") {
e.preventDefault()
selectNext()
return
}
}
const observerOptions = {
childList: true,
subtree: true,
};
const search = document.querySelector("#starlight__search")?.closest("dialog")
function debounce(this: any, func: (...args: any[]) => unknown, timeout = 300){
let timer: ReturnType<typeof setTimeout>;
return (...args: any[]) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
const setFirstSelection = debounce(() => {
if (links.length > 0 && links[0].tagName !== "BUTTON" && findSelectionIndex() === -1) {
selectNext()
}
}, 30)
if (search) {
search.addEventListener("keydown", handleKeyDown)
const resultObserver = new MutationObserver(() => {
links = Array.from(document.querySelectorAll(".pagefind-ui__result-link"))
.concat(Array.from(document.querySelectorAll(".pagefind-ui__button")))
for (const link of links) {
if (!link.hasAttribute("aria-selected")) {
link.removeAttribute("aria-selected")
}
link.setAttribute("tabindex", "-1")
}
setFirstSelection()
})
resultObserver.observe(search, observerOptions)
}
}
}
customElements.define('site-search', SiteSearch);
// :is(.pagefind-ui__result-title, .pagefind-ui__result-nested) .pagefind-ui__result-link {}
get formatURL () {
return this.shouldStrip ? this.stripTrailingSlash : (path: string) => path;
}
get shouldStrip () {
return this.dataset.stripTrailingSlash != null;
}
}
customElements.define('site-search', SiteSearch);
</script>
<style>
@@ -435,12 +358,6 @@ const pagefindTranslations = {
height: 100%;
}
#starlight__search .pagefind-ui__results-area {
overflow: auto;
max-height: 70vh;
padding: 8px;
}
#starlight__search .pagefind-ui__results > * + * {
margin-top: var(--sl-search-result-spacing);
}
@@ -463,17 +380,15 @@ const pagefindTranslations = {
#starlight__search .pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)):hover,
#starlight__search
.pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)):is(.is-active, :focus-within),
.pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)):focus-within,
#starlight__search .pagefind-ui__result-nested:hover,
#starlight__search .pagefind-ui__result-nested:is(.is-active, :focus-within),
#starlight__search .pagefind-ui__button.is-active {
#starlight__search .pagefind-ui__result-nested:focus-within {
outline: 1px solid var(--sl-color-accent-high);
}
#starlight__search
.pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)):is(.is-active, :focus-within),
#starlight__search .pagefind-ui__result-nested:is(.is-active, :focus-within),
#starlight__search .pagefind-ui__button.is-active {
.pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)):focus-within,
#starlight__search .pagefind-ui__result-nested:focus-within {
background-color: var(--sl-color-accent-low);
}
@@ -564,7 +479,4 @@ const pagefindTranslations = {
background-color: transparent;
font-weight: 600;
}
</style>

View File

@@ -9,15 +9,6 @@ layout: ../../../layouts/ComponentLayout.astro
<wa-icon slot="icon" name="circle-info" variant="regular"></wa-icon>
This is a standard alert. You can customize its content and even the icon.
</wa-alert>
<wa-alert open>
<wa-icon slot="icon" name="circle-info" variant="regular"></wa-icon>
<div style="border: solid 2px tomato; height: 2rem;"></div>
</wa-alert>
<wa-alert open>
<div style="border: solid 2px tomato; height: 2rem;"></div>
</wa-alert>
```
```jsx:react

View File

@@ -5,7 +5,7 @@ layout: ../../../layouts/ComponentLayout.astro
---
```html:preview
<wa-card with-image with-footer class="card-overview">
<wa-card class="card-overview">
<img
slot="image"
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
@@ -62,7 +62,7 @@ const css = `
const App = () => (
<>
<WaCard with-image with-footer className="card-overview">
<WaCard className="card-overview">
<img
slot="image"
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
@@ -129,7 +129,7 @@ const App = () => (
Headers can be used to display titles and more.
```html:preview
<wa-card with-header class="card-header">
<wa-card class="card-header">
<div slot="header">
Header Title
<wa-icon-button name="gear" variant="solid" label="Settings"></wa-icon-button>
@@ -185,7 +185,7 @@ const css = `
const App = () => (
<>
<WaCard with-header className="card-header">
<WaCard className="card-header">
<div slot="header">
Header Title
<WaIconButton name="gear" variant="solid"></WaIconButton>
@@ -203,7 +203,7 @@ const App = () => (
Footers can be used to display actions, summaries, or other relevant content.
```html:preview
<wa-card with-footer class="card-footer">
<wa-card class="card-footer">
This card has a footer. You can put all sorts of things in it!
<div slot="footer">
@@ -244,7 +244,7 @@ const css = `
const App = () => (
<>
<WaCard with-footer className="card-footer">
<WaCard className="card-footer">
This card has a footer. You can put all sorts of things in it!
<div slot="footer">
<WaRating></WaRating>
@@ -264,7 +264,7 @@ const App = () => (
Cards accept an `image` slot. The image is displayed atop the card and stretches to fit.
```html:preview
<wa-card with-image class="card-image">
<wa-card class="card-image">
<img
slot="image"
src="https://images.unsplash.com/photo-1547191783-94d5f8f6d8b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=80"
@@ -291,7 +291,7 @@ const css = `
const App = () => (
<>
<WaCard with-image className="card-image">
<WaCard className="card-image">
<img
slot="image"
src="https://images.unsplash.com/photo-1547191783-94d5f8f6d8b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=80"

View File

@@ -7,7 +7,7 @@ layout: ../../../layouts/ComponentLayout.astro
<!-- cspell:dictionaries lorem-ipsum -->
```html:preview
<wa-dialog label="Dialog" with-header with-footer class="dialog-overview">
<wa-dialog label="Dialog" class="dialog-overview">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-dialog>
@@ -34,7 +34,7 @@ const App = () => {
return (
<>
<WaDialog label="Dialog" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" open={open} onWaAfterHide={() => setOpen(false)}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
@@ -54,7 +54,7 @@ const App = () => {
Use the `--width` custom property to set the dialog's width.
```html:preview
<wa-dialog label="Dialog" with-header with-footer class="dialog-width" style="--width: 50vw;">
<wa-dialog label="Dialog" class="dialog-width" style="--width: 50vw;">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-dialog>
@@ -81,7 +81,7 @@ const App = () => {
return (
<>
<WaDialog label="Dialog" with-header with-footer open={open} style={{ '--width': '50vw' }} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" open={open} style={{ '--width': '50vw' }} onWaAfterHide={() => setOpen(false)}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
@@ -99,7 +99,7 @@ const App = () => {
By design, a dialog's height will never exceed that of the viewport. As such, dialogs will not scroll with the page ensuring the header and footer are always accessible to the user.
```html:preview
<wa-dialog label="Dialog" with-header with-footer class="dialog-scrolling">
<wa-dialog label="Dialog" class="dialog-scrolling">
<div style="height: 150vh; border: dashed 2px var(--wa-color-surface-border); padding: 0 1rem;">
<p>Scroll down and give it a try! 👇</p>
</div>
@@ -128,7 +128,7 @@ const App = () => {
return (
<>
<WaDialog label="Dialog" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" open={open} onWaAfterHide={() => setOpen(false)}>
<div
style={{
height: '150vh',
@@ -155,7 +155,7 @@ const App = () => {
The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/components/icon-button) if needed.
```html:preview
<wa-dialog label="Dialog" with-header with-footer class="dialog-header-actions">
<wa-dialog label="Dialog" class="dialog-header-actions">
<wa-icon-button class="new-window" slot="header-actions" name="arrow-up-right-from-square" variant="solid"></wa-icon-button>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand">Close</wa-button>
@@ -186,7 +186,7 @@ const App = () => {
return (
<>
<WaDialog label="Dialog" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" open={open} onWaAfterHide={() => setOpen(false)}>
<WaIconButton
class="new-window"
slot="header-actions"
@@ -214,7 +214,7 @@ To keep the dialog open in such cases, you can cancel the `wa-request-close` eve
You can use `event.detail.source` to determine what triggered the request to close. This example prevents the dialog from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it.
```html:preview
<wa-dialog label="Dialog" with-header with-footer class="dialog-deny-close">
<wa-dialog label="Dialog" class="dialog-deny-close">
This dialog will not close when you click on the overlay.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-dialog>
@@ -255,7 +255,7 @@ const App = () => {
return (
<>
<WaDialog label="Dialog" with-header with-footer open={open} onWaRequestClose={handleRequestClose} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" open={open} onWaRequestClose={handleRequestClose} onWaAfterHide={() => setOpen(false)}>
This dialog will not close when you click on the overlay.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
@@ -273,7 +273,7 @@ const App = () => {
By default, the dialog's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the dialog. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.
```html:preview
<wa-dialog label="Dialog" with-header with-footer class="dialog-focus">
<wa-dialog label="Dialog" class="dialog-focus">
<wa-input autofocus placeholder="I will have focus when the dialog is opened"></wa-input>
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-dialog>
@@ -302,7 +302,7 @@ const App = () => {
return (
<>
<WaDialog label="Dialog" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" open={open} onWaAfterHide={() => setOpen(false)}>
<WaInput autofocus placeholder="I will have focus when the dialog is opened" />
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close

View File

@@ -7,7 +7,7 @@ layout: ../../../layouts/ComponentLayout.astro
<!-- cspell:dictionaries lorem-ipsum -->
```html:preview
<wa-drawer label="Drawer" with-header with-footer class="drawer-overview">
<wa-drawer label="Drawer" class="drawer-overview">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-drawer>
@@ -34,7 +34,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
@@ -54,7 +54,7 @@ const App = () => {
By default, drawers slide in from the end. To make the drawer slide in from the start, set the `placement` attribute to `start`.
```html:preview
<wa-drawer label="Drawer" placement="start" with-header with-footer class="drawer-placement-start">
<wa-drawer label="Drawer" placement="start" class="drawer-placement-start">
This drawer slides in from the start.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-drawer>
@@ -81,7 +81,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" placement="start" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" placement="start" open={open} onWaAfterHide={() => setOpen(false)}>
This drawer slides in from the start.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
@@ -99,7 +99,7 @@ const App = () => {
To make the drawer slide in from the top, set the `placement` attribute to `top`.
```html:preview
<wa-drawer label="Drawer" placement="top" with-header with-footer class="drawer-placement-top">
<wa-drawer label="Drawer" placement="top" class="drawer-placement-top">
This drawer slides in from the top.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-drawer>
@@ -126,7 +126,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" placement="top" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" placement="top" open={open} onWaAfterHide={() => setOpen(false)}>
This drawer slides in from the top.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
@@ -144,7 +144,7 @@ const App = () => {
To make the drawer slide in from the bottom, set the `placement` attribute to `bottom`.
```html:preview
<wa-drawer label="Drawer" placement="bottom" with-header with-footer class="drawer-placement-bottom">
<wa-drawer label="Drawer" placement="bottom" class="drawer-placement-bottom">
This drawer slides in from the bottom.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-drawer>
@@ -171,7 +171,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" placement="bottom" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" placement="bottom" open={open} onWaAfterHide={() => setOpen(false)}>
This drawer slides in from the bottom.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
@@ -184,12 +184,84 @@ const App = () => {
};
```
### Contained to an Element
By default, drawers slide out of their [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport. To make a drawer slide out of a parent element, add the `contained` attribute to the drawer and apply `position: relative` to its parent.
Unlike normal drawers, contained drawers are not modal. This means they do not show an overlay, they do not trap focus, and they are not dismissible with [[Escape]]. This is intentional to allow users to interact with elements outside of the drawer.
```html:preview
<div
style="position: relative; border: solid 2px var(--wa-color-surface-border); height: 300px; padding: 1rem; margin-bottom: 1rem;"
>
The drawer will be contained to this box. This content won't shift or be affected in any way when the drawer opens.
<wa-drawer label="Drawer" contained class="drawer-contained" style="--size: 50%;">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-drawer>
</div>
<wa-button>Toggle Drawer</wa-button>
<script>
const drawer = document.querySelector('.drawer-contained');
const openButton = drawer.parentElement.nextElementSibling;
const closeButton = drawer.querySelector('wa-button[variant="brand"]');
openButton.addEventListener('click', () => (drawer.open = !drawer.open));
closeButton.addEventListener('click', () => drawer.hide());
</script>
```
```jsx:react
import { useState } from 'react';
import WaButton from '@shoelace-style/shoelace/dist/react/button';
import WaDrawer from '@shoelace-style/shoelace/dist/react/drawer';
const App = () => {
const [open, setOpen] = useState(false);
return (
<>
<div
style={{
position: 'relative',
border: 'solid 2px var(--wa-color-surface-border)',
height: '300px',
padding: '1rem',
marginBottom: '1rem'
}}
>
The drawer will be contained to this box. This content won't shift or be affected in any way when the drawer
opens.
<WaDrawer
label="Drawer"
contained
no-modal
open={open}
onWaAfterHide={() => setOpen(false)}
style={{ '--size': '50%' }}
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
</WaButton>
</WaDrawer>
</div>
<WaButton onClick={() => setOpen(true)}>Open Drawer</WaButton>
</>
);
};
```
### Custom Size
Use the `--size` custom property to set the drawer's size. This will be applied to the drawer's width or height depending on its `placement`.
```html:preview
<wa-drawer label="Drawer" with-header with-footer class="drawer-custom-size" style="--size: 50vw;">
<wa-drawer label="Drawer" class="drawer-custom-size" style="--size: 50vw;">
This drawer is always 50% of the viewport.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-drawer>
@@ -216,7 +288,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)} style={{ '--size': '50vw' }}>
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)} style={{ '--size': '50vw' }}>
This drawer is always 50% of the viewport.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
@@ -234,7 +306,7 @@ const App = () => {
By design, a drawer's height will never exceed 100% of its container. As such, drawers will not scroll with the page to ensure the header and footer are always accessible to the user.
```html:preview
<wa-drawer label="Drawer" with-header with-footer class="drawer-scrolling">
<wa-drawer label="Drawer" class="drawer-scrolling">
<div style="height: 150vh; border: dashed 2px var(--wa-color-surface-border); padding: 0 1rem;">
<p>Scroll down and give it a try! 👇</p>
</div>
@@ -263,7 +335,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)}>
<div
style={{
height: '150vh',
@@ -289,7 +361,7 @@ const App = () => {
The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/components/icon-button) if needed.
```html:preview
<wa-drawer label="Drawer" with-header with-footer class="drawer-header-actions">
<wa-drawer label="Drawer" class="drawer-header-actions">
<wa-icon-button class="new-window" slot="header-actions" name="arrow-up-right-from-square" variant="solid"></wa-icon-button>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand">Close</wa-button>
@@ -320,7 +392,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)}>
<WaIconButton slot="header-actions" name="arrow-up-right-from-square" onClick={() => window.open(location.href)} />
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
@@ -343,7 +415,7 @@ To keep the drawer open in such cases, you can cancel the `wa-request-close` eve
You can use `event.detail.source` to determine what triggered the request to close. This example prevents the drawer from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it.
```html:preview
<wa-drawer label="Drawer" with-header with-footer class="drawer-deny-close">
<wa-drawer label="Drawer" class="drawer-deny-close">
This drawer will not close when you click on the overlay.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-drawer>
@@ -384,7 +456,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" with-header with-footer open={open} onWaRequestClose={handleRequestClose} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" open={open} onWaRequestClose={handleRequestClose} onWaAfterHide={() => setOpen(false)}>
This drawer will not close when you click on the overlay.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Save &amp; Close
@@ -402,7 +474,7 @@ const App = () => {
By default, the drawer's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the drawer. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.
```html:preview
<wa-drawer label="Drawer" with-header with-footer class="drawer-focus">
<wa-drawer label="Drawer" class="drawer-focus">
<wa-input autofocus placeholder="I will have focus when the drawer is opened"></wa-input>
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-drawer>
@@ -431,7 +503,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)}>
<WaInput autofocus placeholder="I will have focus when the drawer is opened" />
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close

View File

@@ -115,4 +115,4 @@ Alternatively, you can apply `nav-state="open"` and `nav-state="closed"` to the
### Docs Layout
- TODO - Menu + main + aside + footer (docs)
- TODO - Menu + main + aside + footer (docs)

View File

@@ -6,7 +6,7 @@ description: TODO
## Card
```html:preview
<wa-card with-image class="card-overview">
<wa-card class="card-overview">
<img
slot="image"
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"

View File

@@ -1,14 +1,43 @@
---
title: 'Web Awesome'
title: 'Web Awesome: A forward-thinking library of web components.'
description: Hand-crafted custom elements for any occasion.
---
import Logo from '../../components/Logo.astro';
<div class="splash">
<div class="splash-start">
<Logo />
</div>
<div class="splash-end">
<div>
# <wa-visually-hidden>Web Awesome:</wa-visually-hidden>
- Works with all frameworks 🧩
- Works with CDNs 🚛
- Fully customizable with CSS 🎨
- Includes a dark theme 🌛
- Built with accessibility in mind ♿️
- First-class [React support](/frameworks/react) ⚛️
- Built-in localization 💬
- Open source 😸
- [More awesome than ever](https://blog.fontawesome.com/shoelace-joins-font-awesome/) ![Awesome emoji](/assets/images/awesome.svg)
</div>
<img
class="splash-image"
src="/assets/images/undraw-content-team.svg"
alt="Cartoon of people assembling components while standing on a giant laptop."
/>
</div>
</div>
<div class="badges">
[![jsDelivr](https://data.jsdelivr.com/v1/package/npm/@shoelace-style/shoelace/badge)](https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace)
[![npm](https://img.shields.io/npm/dw/@shoelace-style/shoelace?label=npm&style=flat-square)](https://www.npmjs.com/package/@shoelace-style/shoelace)
[![License](https://img.shields.io/badge/license-MIT-232323.svg?style=flat-square)](https://github.com/shoelace-style/shoelace/blob/next/LICENSE.md)
[![Discord](https://img.shields.io/badge/Discord-Join%20the%20chat-5965f2.svg?style=flat-square&logo=discord&logoColor=white)](https://discord.gg/mg8f26C)
[![Twitter](https://img.shields.io/badge/Twitter-Follow-00acee.svg?style=flat-square&logo=twitter&logoColor=white)](https://twitter.com/shoelace_style)
@@ -19,8 +48,14 @@ import Logo from '../../components/Logo.astro';
Add the following code to your page.
```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/themes/default.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/autoloader.js"></script>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/themes/default.css"
/>
<script
type="module"
src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/autoloader.js"
></script>
```
Now you have access to all of Web Awesome's components! Try adding a button:

View File

@@ -17,4 +17,5 @@ Im fully aware that I may not get it right every time for every user, so I in
This is the path forward. Together, we will continue to make Web Awesome accessible to as many users as possible.
The Web Awesome team
Cory LaViska<br>
_Creator of Web Awesome_

View File

@@ -37,7 +37,7 @@ The [community chat](https://discord.gg/mg8f26C) is open to the public and power
## Twitter
Follow [@shoelace_style](https://twitter.com/shoelace_style) on Twitter for general updates and announcements about Web Awesome. This is a great place to say "hi" or to share something you're working on.
Follow [@shoelace_style](https://twitter.com/shoelace_style) on Twitter for general updates and announcements about Web Awesome. This is a great place to say "hi" or to share something you're working on. You're also welcome to follow [@claviska](https://twitter.com/claviska), the creator, for tweets about web components, web development, and life.
**Please avoid using Twitter for support questions.** The [discussion forum](https://github.com/shoelace-style/shoelace/discussions) is a much better place to share code snippets, screenshots, and other troubleshooting info. You'll have much better luck there, as more users will have a chance to help you.

View File

@@ -281,7 +281,7 @@ Internally, each component uses the [BEM methodology](http://getbem.com/) for cl
### Boolean Props
Boolean props should _always_ default to `false`, otherwise there's no way for the user to unset them using only attributes. To keep the API as friendly and consistent as possible, use a property such as `noValue` and a corresponding kebab-case attribute such as `no-value`.
Boolean props should _always_ default to `false`, otherwise there's no way for the user to unset them using only attributes. To keep the API as friendly and consistent as possible, use a property such as `noHeader` and a corresponding kebab-case attribute such as `no-header`.
When naming boolean props that hide or disable things, prefix them with `no-`, e.g. `no-spin-buttons` and avoid using other verbs such as `hide-` and `disable-` for consistency.

View File

@@ -0,0 +1,37 @@
import * as pagefind from 'pagefind';
import * as path from 'node:path';
// clean up once complete
import { getCollection } from 'astro:content';
export async function generateSearch() {
const { index } = await pagefind.createIndex({});
if (!index) return;
// Get all `src/content/docs/` entries
let allContent = await getCollection('docs');
allContent = allContent.filter(doc => {
return doc.data.pagefind !== false;
});
await Promise.allSettled(
allContent.map(async entry => {
const { category, title, description } = entry.data;
return await index?.addCustomRecord({
content: entry.body,
language: 'en',
url: entry.slug,
meta: {
category: category || '',
title,
description: description || ''
}
});
})
);
const { errors } = await index.writeFiles({
outputPath: path.join(process.cwd(), 'public', 'pagefind')
});
}

View File

@@ -94,10 +94,7 @@ const {
To import this component from <a href="https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace">the CDN</a>
using a script tag:
</p>
<div class="code-preview">
<pre><code id="code-block-importing-script" class="language-html" set:html={highlight("html", `<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${version}/${cdndir}/${component.path}"></script>`)}></code></pre>
<wa-copy-button from="code-block-importing-script" class="copy-code-button"></wa-copy-button>
</div>
<pre><code class="language-html" set:html={highlight("html", `<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${version}/${cdndir}/${component.path}"></script>`)}></code></pre>
</wa-tab-panel>
<wa-tab-panel name="import">
@@ -105,32 +102,23 @@ const {
To import this component from <a href="https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace">the CDN</a>
using a JavaScript import:
</p>
<div class="code-preview">
<pre><code id="code-block-importing-cdn" class="language-js" set:html={
highlight("js", `import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${version}/${cdndir}/${component.path}';`)
}></code></pre>
<wa-copy-button from="code-block-importing-cdn" class="copy-code-button"></wa-copy-button>
</div>
<pre><code class="language-js" set:html={
highlight("js", `import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${version}/${cdndir}/${component.path}';`)
}></code></pre>
</wa-tab-panel>
<wa-tab-panel name="bundler">
<p>
To import this component using <a href={ rootUrl('/getting-started/installation#bundling') }>a bundler</a>:
</p>
<div class="code-preview">
<pre><code id="code-block-importing-bundler" class="language-js" set:html={highlight("js", `import '@shoelace-style/shoelace/${npmdir}/${component.path}';`)}></code></pre>
<wa-copy-button from="code-block-importing-bundler" class="copy-code-button"></wa-copy-button>
</div>
<pre><code class="language-js" set:html={highlight("js", `import '@shoelace-style/shoelace/${npmdir}/${component.path}';`)}></code></pre>
</wa-tab-panel>
<wa-tab-panel name="react">
<p>
To import this component as a <a href="/frameworks/react">React component</a>:
</p>
<div class="code-preview">
<pre><code id="code-block-importing-react" class="language-js" set:html={highlight("js", `import ${component.name} from '@shoelace-style/shoelace/${npmdir}/react/${component.tagNameWithoutPrefix}';`)}></code></pre>
<wa-copy-button from="code-block-importing-react" class="copy-code-button"></wa-copy-button>
</div>
<pre><code class="language-js" set:html={highlight("js", `import ${component.name} from '@shoelace-style/shoelace/${npmdir}/react/${component.tagNameWithoutPrefix}';`)}></code></pre>
</wa-tab-panel>
</wa-tab-group>

View File

@@ -1,4 +1,4 @@
.grid {
wa-page > * {
font-size: 1.35rem;
text-align: center;
display: grid;
@@ -6,58 +6,84 @@
padding: 1rem;
}
header {
wa-page[view='mobile'] {
--menu-width: 0px;
}
wa-page[view='desktop'] [data-toggle-nav] {
display: none;
}
[data-toggle-nav] {
font-size: 1.5rem;
}
[slot='banner'],
[slot='header'] {
min-width: 100%;
height: 100%;
}
[slot='banner'] {
background-color: var(--wa-color-yellow-95);
}
[slot='header'] {
display: grid;
align-items: center;
justify-content: center;
background-color: var(--wa-color-blue-90);
}
aside {
wa-page[view='mobile'] [slot='header'] {
grid-template-columns: 1.5rem 1fr;
}
wa-page[view='desktop'] [slot='header'] {
grid-template-columns: 1fr;
}
[slot='menu'],
[slot='navigation'],
[slot='aside'] {
min-width: 250px;
max-width: 250px;
height: 100%;
}
[slot='menu'],
[slot='navigation'],
[slot='navigation-header'],
[slot='navigation-footer'] {
background-color: var(--wa-color-red-80);
}
[slot='aside'] {
background-color: var(--wa-color-yellow-90);
}
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);
}
[slot='footer'] {
background-color: var(--wa-color-blue-80);
}
wa-page::part(drawer) {
--size: 300px;
--panel-background: var(--wa-color-red-80);
}
astro-dev-toolbar {
display: none;
}

View File

@@ -1,16 +1,22 @@
<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>
<header slot="banner">banner</header>
<aside class="grid" slot="menu">menu</aside>
<header slot="header">
<wa-icon-button name="bars" data-toggle-nav></wa-icon-button>
header
</header>
<header class="grid" slot="main-header">main-header</header>
<aside slot="navigation">menu</aside>
<main class="grid" id="main-content">main</main>
<header slot="main-header">main-header</header>
<footer class="grid" slot="main-footer">main-footer</footer>
<main id="main-content">main</main>
<aside class="grid" slot="aside">aside</aside>
<footer class="grid" slot="footer">footer</footer>
</wa-page>
<footer slot="main-footer">main-footer</footer>
<aside slot="aside">aside</aside>
<footer slot="footer">footer</footer>
</wa-page>

View File

@@ -15,7 +15,7 @@
fun.
</p>
<wa-dialog id="dialog" label="Dialog" with-header> I'm just a lowly dialog. </wa-dialog>
<wa-dialog id="dialog"> I'm just a lowly dialog. </wa-dialog>
<wa-button>Open Dialog</wa-button>
</wa-page>

View File

@@ -196,3 +196,7 @@ table td {
max-width: 100%;
overflow-x: auto;
}
astro-dev-toolbar {
display: none;
}

View File

@@ -133,7 +133,7 @@
text-align: center;
"
>
<wa-card with-header class="wa-card--muted" style="--padding: 8px">
<wa-card class="wa-card--muted" style="--padding: 8px">
<h3 slot="header">Total listening time</h3>
<p>
@@ -146,7 +146,7 @@
</p>
</wa-card>
<wa-card with-header class="wa-card--muted" style="--padding: 8px">
<wa-card class="wa-card--muted" style="--padding: 8px">
<h3 slot="header">Total songs played</h3>
<p>
@@ -160,7 +160,7 @@
</p>
</wa-card>
<wa-card with-header class="wa-card--muted" style="--padding: 8px">
<wa-card class="wa-card--muted" style="--padding: 8px">
<h3 slot="header">Average listening session</h3>
<p>
@@ -174,7 +174,7 @@
</p>
</wa-card>
<wa-card with-header class="wa-card--muted" style="--padding: 8px">
<wa-card class="wa-card--muted" style="--padding: 8px">
<h3 slot="header">Average track listening time</h3>
<p>

View File

@@ -195,3 +195,7 @@ wa-page[view='mobile'] .navigation--desktop {
display: flex;
}
}
astro-dev-toolbar {
display: none;
}

View File

@@ -1,58 +1,19 @@
// import { APIContext } from "astro";
import * as pagefind from 'pagefind';
import * as path from 'node:path';
// clean up once complete
import { getCollection } from 'astro:content';
export async function generateSearch() {
const { index } = await pagefind.createIndex({});
if (!index) return;
let json: Array<{ url: string; content: string }> = [];
// Get all `src/content/docs/` entries
let allContent = await getCollection('docs');
allContent = allContent.filter(doc => {
return doc.data.pagefind !== false;
});
await Promise.allSettled(
allContent.map(async entry => {
const { category, title, description } = entry.data;
const resp = await fetch('http://localhost:4000/' + entry.slug);
const html = await resp.text();
// json.push({
// content: html,
// url: entry.slug
// });
return await index?.addHTMLFile({
content: html,
url: entry.slug
});
})
);
const { errors } = await index.writeFiles({
outputPath: path.join(process.cwd(), 'public', 'pagefind')
});
return json;
}
let json: Record<string, unknown> = {};
import { generateSearch } from '../js/generate-search';
if (process.env.DEV_SEARCH !== 'generated') {
await generateSearch();
// If you're debugging search, comment the next line.
process.env.DEV_SEARCH = 'generated';
// setTimeout(() => {
// process.env.DEV_SEARCH = ""
// }, 200)
}
export async function GET() {
return new Response(JSON.stringify(json), {
return new Response(null, {
status: 200,
headers: {
'Content-Type': 'application/json'

View File

@@ -66,10 +66,10 @@ function generateCodeBlock(node: Node) {
language = 'plaintext';
}
node.value = html`<div class="code-preview">
<pre><code id='code-block-${++count}' class="language-${language}">${highlight(language, node.value)}</code></pre>
${copyButton(`code-block-${count}`)}
</div>`;
node.value = html`<pre><code id='code-block-${++count}' class="language-${language}">${highlight(
language,
node.value
)}</code>${copyButton(`code-block-${count}`)}</pre>`;
}
function generatePreviewCodeBlock(node: Node, reactCode: string) {

View File

@@ -1,285 +0,0 @@
:root {
--sl-font: 'cera-round-pro', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
}
div.ec-line {
/* Fixes extra spacing on code blocks */
margin-top: 0px !important;
}
/* Dark mode colors. */
:root {
--sl-color-text: #ffffff;
--sl-color-accent-low: #1d242f;
--sl-color-accent: #506b92;
--sl-color-accent-high: #bfc9d8;
--sl-color-white: #ffffff;
--sl-color-gray-1: #e3efff;
--sl-color-gray-2: #b4c3d9;
--sl-color-gray-3: #708db5;
--sl-color-gray-4: #3e597e;
--sl-color-gray-5: #1f395b;
--sl-color-gray-6: #0e2748;
--sl-color-black: #0d1928;
--fa-dk-teal: #0ca678;
--fa-gravy: #c3c6d1;
--fa-md-gravy: rgb(97, 109, 138);
--fa-dk-red: #e03131;
--fa-dk-yellow: rgb(250, 176, 5);
--fa-yellow: #ffd43b;
--link-color: rgb(20, 110, 190);
--balloon-border-radius: 2px;
--balloon-color: rgb(24 49 83);
--balloon-font-size: 12px;
--balloon-move: 4px;
--balloon-text-color: #fff;
}
/* Light mode colors. */
:root[data-theme='light'] {
--sl-color-text: rgb(24, 49, 83);
--sl-color-accent-low: #d0d7e3;
--sl-color-accent: #526d94;
--sl-color-accent-high: #273344;
--sl-color-white: #0d1928;
--sl-color-gray-1: #0e2748;
--sl-color-gray-2: #1f395b;
--sl-color-gray-3: #3e597e;
--sl-color-gray-4: #708db5;
--sl-color-gray-5: #b4c3d9;
--sl-color-gray-6: #e3efff;
--sl-color-gray-7: #f1f7ff;
--sl-color-black: #ffffff;
--fa-dk-teal: #0ca678;
--fa-gravy: #c3c6d1;
--fa-md-gravy: rgb(97, 109, 138);
--fa-dk-red: #e03131;
--fa-dk-yellow: rgb(250, 176, 5);
--fa-yellow: #ffd43b;
--link-color: rgb(20, 110, 190);
--balloon-border-radius: 2px;
--balloon-color: rgb(24 49 83);
--balloon-font-size: 12px;
--balloon-move: 4px;
--balloon-text-color: #fff;
}
.icon-padding {
padding: 0 8px 0 8px;
}
.remove-margins {
margin: 0px !important;
}
.action-button {
border-radius: 999rem;
text-decoration: none;
padding: 1rem 1.25rem;
background: var(--sl-color-text-accent);
color: var(--sl-color-black) !important;
}
.action-button > i {
vertical-align: middle;
}
/* table, */
/* tr, */
/* td, */
/* th { */
/* border: none !important; */
/* } */
/**/
/* table thead { */
/* text-align: left; */
/* font-weight: bold; */
/* color: rgb(24, 49, 83); */
/* border-bottom: 2px solid rgb(195, 198, 209); */
/* } */
/**/
/* table tr { */
/* vertical-align: top; */
/* } */
/**/
/* td { */
/* border-bottom: 1px solid rgb(224, 226, 232); */
/* } */
/**/
/* table tr:nth-child(odd) td { */
/* background: #fff; */
/* border-bottom: 1px solid rgb(224, 226, 232) !important; */
/* } */
/**/
/* table tr:nth-child(even) td { */
/* background: #fff; */
/* border-bottom: 1px solid rgb(224, 226, 232) !important; */
/* } */
.frame.is-terminal pre {
margin: 0px !important;
/* margin-top: 1px !important; */
background: rgb(24 49 83) !important;
}
.frame.has-title pre {
margin: 0px !important;
}
figcaption.header {
background: rgb(0, 28, 64) !important;
}
img {
border-bottom-left-radius: 16px;
border-bottom-right-radius: 16px;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
}
.site-title > img {
border-radius: 0px;
color: rgb(20, 110, 190);
}
.starlight-aside {
border-bottom-left-radius: 16px;
border-bottom-right-radius: 16px;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
}
.starlight-aside__content > ul {
list-style-type: none;
}
.starlight-aside__content > ul > li:before {
display: inline-block;
font-style: normal;
font-feature-settings: normal;
font-variant: normal;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
font-family: 'Font Awesome 6 Pro', 'Font Awesome 5 Pro';
font-weight: 900;
content: '\f058';
margin-right: calc(1em * 1);
margin-right: 16px;
margin-left: -2em;
}
.text-center {
text-align: center;
}
.display-block {
display: block;
}
.muted {
color: rgb(97, 109, 138);
}
img + em {
display: block;
color: rgb(97, 109, 138);
text-align: center;
font-size: 0.8em;
}
p > em {
color: rgb(97, 109, 138);
text-align: center;
font-size: 0.8em;
}
h1 h2 h3 {
font-weight: 600 !important;
}
.theme-ravenclaw {
--fa-secondary-opacity: 1;
--fa-primary-color: rgb(4, 56, 161);
--fa-secondary-color: rgb(108, 108, 108);
}
.fa-ul {
padding-left: 0;
margin-left: 2.14285714em;
list-style-type: none;
}
.fa-ul > li {
position: relative;
}
.fa-li {
position: absolute;
left: -2.14285714em;
width: 2.14285714em;
top: 0.14285714em;
text-align: center;
}
@media (min-width: 50em) {
:root {
--sl-text-h1: var(--sl-text-4xl);
--sl-text-h2: var(--sl-text-3xl);
--sl-text-h3: var(--sl-text-2xl);
--sl-text-h4: var(--sl-text-1xl);
}
}
img {
/* border: 2px solid rgb(24, 49, 83); */
}
.card {
border-bottom-left-radius: 16px;
border-bottom-right-radius: 16px;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
padding: 0px 20px 10px 20px !important;
border: 2px solid rgb(24, 49, 83) !important;
margin-top: 0px !important;
}
.hero > img {
border: 0px;
}
a.site-title > img {
border: 0px;
}
.grid-container {
display: grid;
grid-template-columns: auto auto auto;
padding: 10px;
}
.grid-button {
padding: 10px 32px 10px 32px;
margin: 10px;
vertical-align: top;
}
.return-button {
border-radius: 999rem;
text-decoration: none;
padding: 0.5rem 1rem;
background: var(--sl-color-text-accent);
color: white !important;
}
.right-sidebar-panel h2 a {
display: inline-block;
}
.right-sidebar-container ul {
margin: 0;
}

View File

@@ -22,10 +22,6 @@
--docs-shadow-x-large: 0 4px 16px hsl(240 3.8% 46.1% / 24%);
}
html {
scrollbar-gutter: stable;
}
/* Utils */
html.wa-theme-dark .only-light,
html:not(.wa-theme-dark) .only-dark {
@@ -48,26 +44,10 @@ html:not(.wa-theme-dark) .only-dark {
padding: 0 !important;
}
.sidebar-content ul,
.sidebar-content details,
.sidebar-content summary {
padding: unset;
margin: unset;
}
.sidebar-content summary {
text-indent: 0.5rem;
padding: 0.2em var(--sl-sidebar-item-padding-inline);
margin-block: 0.5rem;
}
.sidebar-content a {
text-decoration: unset;
}
.main-frame main h1 {
margin: 0;
margin-block-start: 1rem;
@media screen and (max-width: 900px) {
:root {
--docs-content-padding: 1rem;
}
}
html {
@@ -188,6 +168,11 @@ th.table-description {
}
/* Code blocks */
pre:not(:last-child) {
margin-bottom: 1.5rem;
}
pre {
position: relative;
overflow: auto;
@@ -206,6 +191,71 @@ pre > code {
position: relative;
}
pre .token.comment {
color: var(--wa-color-base-40);
}
pre .token.prolog,
pre .token.doctype,
pre .token.cdata,
pre .token.operator,
pre .token.punctuation {
color: var(--wa-color-base-40);
}
.namespace {
opacity: 0.7;
}
pre .token.property,
pre .token.keyword,
pre .token.tag,
pre .token.url {
color: var(--wa-color-brand-text-on-fill);
}
pre .token.symbol,
pre .token.deleted {
color: var(--wa-color-danger-text-on-fill);
}
pre .token.boolean,
pre .token.constant,
pre .token.selector,
pre .token.attr-name,
pre .token.string,
pre .token.char,
pre .token.builtin,
pre .token.inserted {
color: var(--wa-color-success-text-on-fill);
}
pre .token.atrule,
pre .token.attr-value,
pre .token.number,
pre .token.variable {
color: var(--wa-color-warning-text-on-fill);
}
pre .token.function,
pre .token.class-name,
pre .token.regex {
color: var(--wa-color-danger-text-on-fill);
}
pre .token.important {
color: var(--wa-color-danger-text-on-fill);
}
pre .token.important,
pre .token.bold {
font-weight: bold;
}
pre .token.italic {
font-style: italic;
}
/* Copy code button */
.copy-code-button {
position: absolute;
@@ -219,30 +269,594 @@ pre > code {
}
.copy-code-button::part(button) {
background-color: var(--wa-color-primary-fill-subtle);
background-color: var(--wa-color-neutral-fill-subtle);
border-radius: 0 var(--wa-corners-s) 0 var(--wa-corners-s);
padding: 0.75rem;
}
.copy-code-button::part(button):hover {
background-color: color-mix(in oklab, var(--wa-color-primary-fill-subtle), var(--wa-color-mix-hover));
background-color: color-mix(in oklab, var(--wa-color-neutral-fill-subtle), var(--wa-color-mix-hover));
}
.copy-code-button::part(button):active {
background-color: color-mix(in oklab, var(--wa-color-primary-fill-subtle), var(--wa-color-mix-active));
background-color: color-mix(in oklab, var(--wa-color-neutral-fill-subtle), var(--wa-color-mix-active));
}
:is(.code-preview__source, pre, .code-preview) > .copy-code-button {
:is(.code-preview__source, pre) .copy-code-button {
opacity: 0;
scale: 0.75;
}
:is(.code-preview__source, pre, .code-preview):hover > .copy-code-button,
:is(.code-preview__source, pre):hover .copy-code-button,
.copy-code-button:focus-within {
opacity: 1;
scale: 1;
}
/* Callouts */
.callout {
margin-bottom: var(--docs-content-vertical-spacing);
}
.callout > :first-child {
margin-top: 0;
}
.callout > :last-child {
margin-bottom: 0;
}
.callout a {
color: inherit;
}
.callout p {
margin-top: 0;
}
/* Aside */
.content aside {
float: right;
min-width: 300px;
max-width: 50%;
background: var(--wa-color-surface-lowered);
border-radius: var(--wa-corners-s);
padding: var(--wa-space-m);
margin-left: var(--wa-space-m);
}
.content aside > :first-child {
margin-top: 0;
}
.content aside > :last-child {
margin-bottom: 0;
}
@media screen and (max-width: 600px) {
.content aside {
float: none;
width: calc(100% + (var(--docs-content-padding) * 2));
max-width: none;
margin: var(--docs-content-vertical-spacing) calc(-1 * var(--docs-content-padding));
}
}
/* Sidebar */
#sidebar {
position: fixed;
flex: 0;
top: 0;
left: 0;
bottom: 0;
z-index: 20;
width: var(--docs-sidebar-width);
background-color: var(--docs-background-color);
border-right: solid var(--wa-border-width-s) var(--wa-color-surface-border);
border-radius: 0;
padding: 2rem;
margin: 0;
overflow: auto;
scrollbar-width: thin;
transition: var(--docs-sidebar-transition-speed) translate ease-in-out;
}
#sidebar::-webkit-scrollbar {
width: 4px;
}
#sidebar::-webkit-scrollbar-thumb {
background: transparent;
border-radius: 9999px;
}
#sidebar:hover::-webkit-scrollbar-thumb {
background: var(--wa-color-base-60);
}
#sidebar:hover::-webkit-scrollbar-track {
background: var(--wa-color-base-95);
}
#sidebar > header {
margin-bottom: 1.5rem;
}
#sidebar > header h1 {
margin: 0;
}
#sidebar > header svg {
margin-bottom: var(--wa-space-s);
}
#sidebar > header a {
display: block;
}
#sidebar nav a {
text-decoration: none;
}
#sidebar nav h2 {
font-size: var(--wa-font-size-m);
font-weight: var(--wa-font-weight-medium);
border-bottom: solid var(--wa-border-width-s) var(--wa-color-surface-border);
margin: var(--wa-space-xl) 0 var(--wa-space-xs) 0;
}
#sidebar ul {
padding: 0;
margin: 0;
}
#sidebar ul li {
list-style: none;
padding: 0;
margin: 0.125rem 0.5rem;
}
#sidebar ul ul ul {
margin-left: 0.75rem;
}
#sidebar ul li a {
line-height: 1.33;
color: inherit;
display: inline-block;
padding: 0;
}
#sidebar ul li a:not(.active-link):hover {
color: var(--wa-color-text-link);
}
#sidebar nav .active-link {
color: var(--wa-color-text-link);
border-bottom: dashed 1px var(--wa-color-text-link);
}
#sidebar > header img {
display: block;
width: 100%;
height: auto;
margin: 0 auto;
}
@media screen and (max-width: 900px) {
#sidebar {
translate: -100%;
}
.sidebar-open #sidebar {
translate: 0;
}
}
.sidebar-version {
font-size: var(--wa-font-size-s);
color: var(--wa-color-text-quiet);
text-align: right;
margin-top: calc(-1 * var(--wa-space-s));
margin-bottom: calc(-1 * var(--wa-space-s));
}
.sidebar-buttons {
display: flex;
justify-content: space-between;
}
/* Main content */
main {
position: relative;
padding: var(--docs-content-vertical-spacing) var(--docs-content-padding)
calc(var(--docs-content-vertical-spacing) * 2) var(--docs-content-padding);
/* margin-left: var(--docs-sidebar-width); */
margin: 0;
}
.sidebar-open .content {
margin-left: 0;
}
.content__body > :last-child {
margin-bottom: 0;
}
@media screen and (max-width: 900px) {
main {
margin-left: 0;
}
}
/* Component layouts */
.content {
display: grid;
grid-template-columns: 100%;
gap: 2rem;
position: relative;
max-width: var(--docs-content-max-width);
margin: 0 auto;
}
.content--with-toc {
/* There's a 2rem gap, so we need to remove it from the column */
grid-template-columns: calc(75% - 2rem) min(25%, var(--docs-content-toc-max-width));
max-width: calc(var(--docs-content-max-width) + var(--docs-content-toc-max-width));
}
.content__body {
order: 1;
width: 100%;
}
.content:not(.content--with-toc) .content__toc {
display: none;
}
.content__toc {
order: 2;
display: flex;
flex-direction: column;
margin-top: 0;
}
.content__toc ul {
position: sticky;
top: 5rem;
max-height: calc(100vh - 6rem);
font-size: var(--wa-font-size-s);
line-height: 1.33;
border-left: var(--wa-border-style) var(--wa-border-width-s) var(--wa-color-surface-border);
list-style: none;
padding: 1rem 0;
margin: 0;
padding-left: 1rem;
overflow-y: auto;
}
.content__toc li {
padding: 0 0 0 0.5rem;
margin: 0;
}
.content__toc li[data-level='3'] {
margin-left: 1rem;
}
/* We don't use them, but just in case */
.content__toc li[data-level='4'],
.content__toc li[data-level='5'],
.content__toc li[data-level='6'] {
margin-left: 2rem;
}
.content__toc li:not(:last-of-type) {
margin-bottom: 0.6rem;
}
.content__toc a {
color: var(--wa-color-text-normal);
text-decoration: none;
}
.content__toc a:hover {
color: var(--wa-color-text-link);
}
.content__toc a.active {
color: var(--wa-color-brand-text-on-surface);
border-bottom: dashed 1px var(--wa-color-brand-text-on-surface);
}
.content__toc .top a {
font-weight: var(--wa-font-weight-medium);
color: var(--wa-color-text-quiet);
}
@media screen and (max-width: 1024px) {
.content {
grid-template-columns: 100%;
gap: 0;
}
.content__toc {
position: relative;
order: 1;
}
.content__toc ul {
display: flex;
justify-content: start;
gap: 1rem 1.5rem;
position: static;
border: none;
border-bottom: solid 1px var(--wa-color-surface-border);
border-radius: 0;
padding: 1rem 1.5rem 1rem 0.5rem; /* extra right padding to hide the fade effect */
margin-top: 1rem;
overflow-x: auto;
}
.content__toc ul::after {
content: '';
position: absolute;
top: 0;
bottom: 1rem; /* don't cover the scrollbar */
right: 0;
width: 2rem;
background: linear-gradient(90deg, rgba(0 0 0 / 0) 0%, var(--wa-color-surface-default) 100%);
}
.content__toc li {
white-space: nowrap;
}
.content__toc li:not(:last-of-type) {
margin-bottom: 0;
}
.content__toc [data-level]:not([data-level='2']) {
display: none;
}
.content__body {
order: 2;
}
}
/* Menu toggle */
#menu-toggle {
display: none;
position: fixed;
z-index: 30;
top: 0.25rem;
left: 0.25rem;
height: auto;
width: auto;
color: var(--wa-color-neutral-spot);
border: none;
border-radius: 50%;
background: var(--wa-color-surface-default);
padding: 0.5rem;
margin: 0;
cursor: pointer;
transition: 250ms rotate ease;
}
@media screen and (max-width: 900px) {
#menu-toggle {
display: flex;
}
}
#menu-toggle svg {
width: 1.25rem;
height: 1.25rem;
}
html.sidebar-open #menu-toggle {
rotate: 180deg;
}
/* Skip to main content */
#skip-to-main {
position: fixed;
top: 0.25rem;
left: calc(50% - var(--docs-skip-to-main-width) / 2);
z-index: 100;
width: var(--docs-skip-to-main-width);
text-align: center;
text-decoration: none;
border-radius: 9999px;
background: var(--wa-color-surface-default);
color: var(--wa-color-text-normal);
padding: 0.5rem;
}
/* Print styles */
@media print {
a:not(.anchor-heading)[href]::after {
content: ' (' attr(href) ')';
}
details,
pre {
border: solid var(--wa-border-width-s) var(--wa-color-surface-border);
}
details summary {
list-style: none;
}
details summary span {
padding-left: 0;
}
details summary::marker,
details summary::-webkit-details-marker {
display: none;
}
.component-page__navigation,
.copy-code-button,
.code-preview__buttons,
.code-preview__resizer {
display: none !important;
}
.flavor-html .code-preview__source--html,
.flavor-react .code-preview__source--react {
display: block !important;
}
.flavor-html .code-preview__source--html > pre,
.flavor-react .code-preview__source--react > pre {
border: none;
}
.code-preview__source-group {
border-bottom: solid 1px var(--wa-border-width-s);
border-bottom-left-radius: var(--wa-corners-s);
border-bottom-right-radius: var(--wa-corners-s);
}
#sidebar {
display: none;
}
#content {
margin-left: 0;
}
#menu-toggle,
#icon-toolbar,
.external-link__icon {
display: none;
}
}
/* Splash */
.splash {
padding-top: 2rem;
}
.splash-start {
min-width: 440px;
}
.splash li img {
width: 1em;
height: 1em;
vertical-align: -2px;
}
.splash svg {
margin-block-end: var(--wa-space-m);
}
.splash-end {
display: flex;
align-items: flex-end;
gap: 16px;
}
main .sl-container {
margin: 0 auto !important;
}
.splash-image {
width: calc(100% - 300px);
height: auto;
}
.splash-start h1:first-of-type {
font-size: var(--wa-font-size-l);
font-weight: var(--wa-font-weight-normal);
margin: 0 0 0.5rem 0;
}
@media screen and (max-width: 1280px) {
.splash {
display: block;
}
.splash-start {
min-width: 0;
padding-bottom: 1rem;
}
.splash-end {
display: block;
padding: 0;
}
.splash-image {
display: block;
max-width: 400px;
}
/* Shields */
.splash + p {
margin-top: 2rem;
}
}
/* Component headers */
.component-header h1 {
margin-bottom: 0;
}
.component-header__tag {
margin-top: -0.5rem;
margin-bottom: 0.5rem;
}
.component-header__tag code {
background: none;
color: var(--wa-color-text-quiet);
font-size: var(--wa-font-size-l);
padding: 0;
margin: 0;
}
.component-header__info {
margin-bottom: var(--wa-space-2xl);
}
.component-summary {
font-size: var(--wa-font-size-l);
line-height: 1.6;
margin: 2rem 0;
}
/* Repo buttons */
.sidebar-buttons {
display: flex;
gap: 0.125rem;
justify-content: space-between;
}
.sidebar-buttons .repo-button {
flex: 1 1 auto;
}
.repo-button wa-icon {
color: var(--wa-color-neutral-text-on-spot);
}
@media screen and (max-width: 400px) {
:not(.sidebar-buttons) > .repo-button {
width: 100%;
margin-bottom: 1rem;
}
}
body[data-page^='/tokens/'] .table-wrapper td:first-child,
body[data-page^='/tokens/'] .table-wrapper td:first-child code {
white-space: nowrap;
}
/* Border demos */
.border-demo {
height: 60px;

View File

@@ -1,3 +1,7 @@
:root {
--sl-content-width: 50rem;
}
/* Code highlighter */
pre,
.sl-markdown-content pre:not(:where(.not-content *)) {

1387
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,7 +47,7 @@
"build": "node scripts/build.js",
"docs:build": "cd docs && npm run build -- --silent",
"verify": "npm run prettier:check && npm run lint && npm run build && npm run test",
"prepare": "./scripts/clear-vercel-patches.sh && npm i --ignore-scripts && npx patch-package && npx playwright install",
"postinstall": "npx playwright install && ./scripts/clear-vercel-patches.sh && npm i --ignore-scripts && npx patch-package",
"prepublishOnly": "npm run verify",
"prettier": "prettier --write --log-level warn .",
"prettier:check": "prettier --check --log-level warn .",
@@ -86,9 +86,9 @@
"@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.7.5",
"@web/dev-server-esbuild": "^0.3.6",
"@web/test-runner": "^0.18.1",
"@web/test-runner-commands": "^0.9.0",
"@web/test-runner-playwright": "^0.11.0",
"@web/test-runner": "^0.15.3",
"@web/test-runner-commands": "^0.6.6",
"@web/test-runner-playwright": "^0.9.0",
"astro": "^4.0.1",
"bootstrap-icons": "^1.11.1",
"browser-sync": "^2.29.3",
@@ -133,7 +133,6 @@
"npm-check-updates": "^16.14.6",
"pascal-case": "^3.1.2",
"patch-package": "^8.0.0",
"playwright": "^1.42.0",
"plop": "^4.0.0",
"prettier": "^3.0.3",
"prismjs": "^1.29.0",

View File

@@ -24,7 +24,7 @@ import type { CSSResultGroup } from 'lit';
* @cssproperty --example - An example CSS custom property.
*/
export default class {{ properCase tag }} extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
private readonly localize = new LocalizeController(this);

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
display: block;
}

View File

@@ -1,12 +1,12 @@
import { animateTo, stopAnimations } from '../../internal/animate.js';
import { classMap } from 'lit/directives/class-map.js';
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';
import { HasSlotController } from '../../internal/slot.js';
import { html } from 'lit';
import { LocalizeController } from '../../utilities/localize.js';
import { property, query } from 'lit/decorators.js';
import { waitForEvent } from '../../internal/event.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './alert.styles.js';
import WaIconButton from '../icon-button/icon-button.component.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
@@ -50,10 +50,11 @@ const toastStack = Object.assign(document.createElement('div'), { className: 'wa
* @animation alert.hide - The animation to use when hiding the alert.
*/
export default class WaAlert extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
static dependencies = { 'wa-icon-button': WaIconButton };
private autoHideTimeout: number;
private readonly hasSlotController = new HasSlotController(this, 'icon', 'suffix');
private readonly localize = new LocalizeController(this);
@query('[part~="base"]') base: HTMLElement;
@@ -196,6 +197,7 @@ export default class WaAlert extends WebAwesomeElement {
alert: true,
'alert--open': this.open,
'alert--closable': this.closable,
'alert--has-icon': this.hasSlotController.test('icon'),
'alert--brand': this.variant === 'brand',
'alert--success': this.variant === 'success',
'alert--neutral': this.variant === 'neutral',

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
--border-radius: var(--wa-panel-corners);
--border-style: var(--wa-panel-border-style);
@@ -56,10 +59,10 @@ export default css`
border-width: var(--border-width);
color: var(--content-color);
font: inherit;
padding: var(--padding);
margin: inherit;
}
.alert:not(.alert--has-icon) .alert__icon,
.alert:not(.alert--closable) .alert__close-button {
display: none;
}
@@ -70,15 +73,13 @@ export default css`
align-items: center;
color: var(--icon-color);
font-size: var(--icon-size);
}
.alert__icon ::slotted(*) {
margin-inline-end: var(--padding) !important;
padding-inline-start: var(--padding);
}
.alert__message {
flex: 1 1 auto;
display: block;
padding: var(--padding);
overflow: hidden;
}
@@ -88,7 +89,7 @@ export default css`
align-items: center;
color: currentColor;
font-size: var(--wa-font-size-m);
padding-inline-start: var(--padding);
padding-inline-end: var(--padding);
}
.alert__close-button:hover::part(base) {

View File

@@ -63,8 +63,7 @@ describe('<wa-alert>', () => {
afterEach(async () => {
clock?.restore();
// eslint-disable-next-line
await resetMouse().catch(() => {});
await resetMouse();
});
it('renders', async () => {
@@ -97,30 +96,28 @@ describe('<wa-alert>', () => {
expectAlertToBeInvisible(alert);
await expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, async () => await alert.show());
await expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, () => alert.show());
});
it('should emit wa-hide and wa-after-hide when calling hide()', async () => {
const alert = await fixture<WaAlert>(html` <wa-alert open>I am an alert</wa-alert>`);
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, async () => await alert.hide());
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => alert.hide());
});
it('should emit wa-show and wa-after-show when setting open = true', async () => {
const alert = await fixture<WaAlert>(html` <wa-alert>I am an alert</wa-alert> `);
await expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, async () => {
await expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, () => {
alert.open = true;
await alert.updateComplete;
});
});
it('should emit wa-hide and wa-after-hide when setting open = false', async () => {
const alert = await fixture<WaAlert>(html` <wa-alert open>I am an alert</wa-alert> `);
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, async () => {
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {
alert.open = false;
await alert.updateComplete;
});
});
});
@@ -137,8 +134,8 @@ describe('<wa-alert>', () => {
const alert = await fixture<WaAlert>(html` <wa-alert open closable>I am an alert</wa-alert> `);
const closeButton = getCloseButton(alert);
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, async () => {
await clickOnElement(closeButton!);
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {
clickOnElement(closeButton!);
});
});
});
@@ -162,7 +159,7 @@ describe('<wa-alert>', () => {
it('can be rendered as a toast', async () => {
const alert = await fixture<WaAlert>(html`<wa-alert>I am an alert</wa-alert>`);
await expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, async () => await alert.toast());
expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, () => alert.toast());
const toastStack = getToastStack();
expect(toastStack).to.be.visible;
expect(toastStack?.firstChild).to.be.equal(alert);
@@ -295,15 +292,17 @@ describe('<wa-alert>', () => {
});
});
});
});
it('Should properly render alert variants', async () => {
const variants = ['brand', 'success', 'neutral', 'warning', 'danger'];
for (const variant of variants) {
const alert = await fixture<WaAlert>(html`<wa-alert variant="${variant}" open>I am an alert</wa-alert>`);
const alertContainer = getAlertContainer(alert);
expect(alertContainer).to.have.class(`alert--${variant}`);
}
describe('alert variants', () => {
const variants = ['brand', 'success', 'neutral', 'warning', 'danger'];
variants.forEach(variant => {
it(`adapts to the variant: ${variant}`, async () => {
const alert = await fixture<WaAlert>(html`<wa-alert variant="${variant}" open>I am an alert</wa-alert>`);
const alertContainer = getAlertContainer(alert);
expect(alertContainer).to.have.class(`alert--${variant}`);
});
});
});
});

View File

@@ -1,7 +1,6 @@
import { html } from 'lit';
import { property, query, state } from 'lit/decorators.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './animated-image.styles.js';
import WaIcon from '../icon/icon.component.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
@@ -27,7 +26,7 @@ import type { CSSResultGroup } from 'lit';
* @cssproperty --icon-size - The size of the play/pause icons.
*/
export default class WaAnimatedImage extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
static dependencies = { 'wa-icon': WaIcon };
@query('.animated-image__animated') animatedImage: HTMLImageElement;

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
--control-box-size: 3rem;
--icon-size: calc(var(--control-box-size) * 0.625);

View File

@@ -2,7 +2,6 @@ import { animations } from './animations.js';
import { html } from 'lit';
import { property, queryAsync } from 'lit/decorators.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './animation.styles.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import type { CSSResultGroup } from 'lit';
@@ -21,7 +20,7 @@ import type { CSSResultGroup } from 'lit';
* animate multiple elements, either wrap them in a single container or use multiple `<wa-animation>` elements.
*/
export default class WaAnimation extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
private animation?: Animation;
private hasStarted = false;

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
display: contents;
}

View File

@@ -2,7 +2,6 @@ import { classMap } from 'lit/directives/class-map.js';
import { html } from 'lit';
import { property, state } from 'lit/decorators.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './avatar.styles.js';
import WaIcon from '../icon/icon.component.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
@@ -26,7 +25,7 @@ import type { CSSResultGroup } from 'lit';
* @cssproperty --size - The size of the avatar.
*/
export default class WaAvatar extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
static dependencies = {
'wa-icon': WaIcon
};

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
display: inline-block;

View File

@@ -1,7 +1,6 @@
import { classMap } from 'lit/directives/class-map.js';
import { html } from 'lit';
import { property } from 'lit/decorators.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './badge.styles.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import type { CSSResultGroup } from 'lit';
@@ -24,7 +23,7 @@ import type { CSSResultGroup } from 'lit';
* @cssproperty --content-color - The color of the badge's content.
*/
export default class WaBadge extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
/** The badge's theme variant. */
@property({ reflect: true }) variant: 'brand' | 'success' | 'neutral' | 'warning' | 'danger' = 'brand';

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
--border-color: var(--wa-color-surface-default);
--border-radius: var(--wa-corners-xs);

View File

@@ -1,7 +1,8 @@
import { classMap } from 'lit/directives/class-map.js';
import { HasSlotController } from '../../internal/slot.js';
import { html } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import { property } from 'lit/decorators.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './breadcrumb-item.styles.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import type { CSSResultGroup } from 'lit';
@@ -25,7 +26,9 @@ import type { CSSResultGroup } from 'lit';
* @csspart separator - The container that wraps the separator.
*/
export default class WaBreadcrumbItem extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
private readonly hasSlotController = new HasSlotController(this, 'prefix', 'suffix');
/**
* Optional URL to direct the user to when the breadcrumb item is activated. When set, a link will be rendered
@@ -43,7 +46,14 @@ export default class WaBreadcrumbItem extends WebAwesomeElement {
const isLink = this.href ? true : false;
return html`
<div part="base" class="breadcrumb-item">
<div
part="base"
class=${classMap({
'breadcrumb-item': true,
'breadcrumb-item--has-prefix': this.hasSlotController.test('prefix'),
'breadcrumb-item--has-suffix': this.hasSlotController.test('suffix')
})}
>
<span part="prefix" class="breadcrumb-item__prefix">
<slot name="prefix"></slot>
</span>

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
display: inline-flex;
}
@@ -58,13 +61,14 @@ export default css`
align-items: center;
}
.breadcrumb-item__prefix,
.breadcrumb-item__suffix {
.breadcrumb-item--has-prefix .breadcrumb-item__prefix {
display: inline-flex;
margin-inline-end: var(--wa-space-s);
}
::slotted(*) {
margin-inline-end: var(--wa-space-s) !important;
}
.breadcrumb-item--has-suffix .breadcrumb-item__suffix {
display: inline-flex;
margin-inline-start: var(--wa-space-s);
}
:host(:last-of-type) .breadcrumb-item__separator {

View File

@@ -1,7 +1,6 @@
import { html } from 'lit';
import { LocalizeController } from '../../utilities/localize.js';
import { property, query } from 'lit/decorators.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './breadcrumb.styles.js';
import WaIcon from '../icon/icon.component.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
@@ -22,7 +21,7 @@ import type WaBreadcrumbItem from '../breadcrumb-item/breadcrumb-item.js';
* @csspart base - The component's base wrapper.
*/
export default class WaBreadcrumb extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
static dependencies = { 'wa-icon': WaIcon };
private readonly localize = new LocalizeController(this);

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
.breadcrumb {
display: flex;
align-items: center;

View File

@@ -1,6 +1,5 @@
import { html } from 'lit';
import { property, query, state } from 'lit/decorators.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './button-group.styles.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import type { CSSResultGroup } from 'lit';
@@ -16,7 +15,7 @@ import type { CSSResultGroup } from 'lit';
* @csspart base - The component's base wrapper.
*/
export default class WaButtonGroup extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
@query('slot') defaultSlot: HTMLSlotElement;

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
display: inline-block;
}

View File

@@ -1,11 +1,11 @@
import { classMap } from 'lit/directives/class-map.js';
import { FormControlController, validValidityState } from '../../internal/form.js';
import { HasSlotController } from '../../internal/slot.js';
import { html, literal } from 'lit/static-html.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { LocalizeController } from '../../utilities/localize.js';
import { property, query, state } from 'lit/decorators.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './button.styles.js';
import WaIcon from '../icon/icon.component.js';
import WaSpinner from '../spinner/spinner.component.js';
@@ -53,7 +53,7 @@ import type { WebAwesomeFormControl } from '../../internal/webawesome-element.js
* @cssproperty --label-color-hover - The color of the button's label on hover.
*/
export default class WaButton extends WebAwesomeElement implements WebAwesomeFormControl {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
static dependencies = {
'wa-icon': WaIcon,
'wa-spinner': WaSpinner
@@ -62,6 +62,7 @@ export default class WaButton extends WebAwesomeElement implements WebAwesomeFor
private readonly formControlController = new FormControlController(this, {
assumeInteractionOn: ['click']
});
private readonly hasSlotController = new HasSlotController(this, '[default]', 'prefix', 'suffix');
private readonly localize = new LocalizeController(this);
@query('.button') button: HTMLButtonElement | HTMLLinkElement;
@@ -286,7 +287,10 @@ export default class WaButton extends WebAwesomeElement implements WebAwesomeFor
'button--standard': !this.outline,
'button--outline': this.outline,
'button--pill': this.pill,
'button--rtl': this.localize.dir() === 'rtl'
'button--rtl': this.localize.dir() === 'rtl',
'button--has-label': this.hasSlotController.test('[default]'),
'button--has-prefix': this.hasSlotController.test('prefix'),
'button--has-suffix': this.hasSlotController.test('suffix')
})}
?disabled=${ifDefined(isLink ? undefined : this.disabled)}
type=${ifDefined(isLink ? undefined : this.type)}

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
--border-radius: var(--wa-form-controls-corners);
--border-style: var(--wa-border-style);
@@ -213,13 +216,13 @@ export default css`
pointer-events: none;
}
.button:hover:not(.button--disabled, .button--loading) {
.button:hover:not(.button--disabled) {
background: var(--background-hover, var(--background, none));
border-color: var(--border-color-hover, var(--border-color, transparent));
color: var(--label-color-hover, var(--label-color));
}
.button:active:not(.button--disabled, .button--loading) {
.button:active:not(.button--disabled) {
background: var(--background-active, var(--background, none));
border-color: var(--border-color-active, var(--border-color, transparent));
color: var(--label-color-active, var(--label-color));
@@ -300,21 +303,6 @@ export default css`
align-items: center;
}
.button--small .button__caret {
margin-inline-start: calc(-0.5 * var(--wa-space-xs));
margin-inline-end: var(--wa-space-xs);
}
.button--medium .button__caret {
margin-inline-start: calc(-0.5 * var(--wa-space-s));
margin-inline-end: var(--wa-space-s);
}
.button--large .button__caret {
margin-inline-start: calc(-0.5 * var(--wa-space-m));
margin-inline-end: var(--wa-space-m);
}
.button--caret .button__caret::part(svg) {
width: 0.875em;
height: 0.875em;
@@ -369,46 +357,70 @@ export default css`
* Button spacing
*/
.button--small {
::slotted([slot='prefix']) {
margin-inline-start: var(--wa-space-xs) !important;
}
::slotted([slot='suffix']) {
margin-inline-end: var(--wa-space-xs) !important;
}
.button__label {
padding: 0 var(--wa-space-s);
}
.button--has-label.button--small .button__label {
padding: 0 var(--wa-space-s);
}
.button--medium {
::slotted([slot='prefix']) {
margin-inline-start: var(--wa-space-s) !important;
}
::slotted([slot='suffix']) {
margin-inline-end: var(--wa-space-s) !important;
}
.button__label {
padding: 0 var(--wa-space-m);
}
.button--has-label.button--medium .button__label {
padding: 0 var(--wa-space-m);
}
.button--large {
::slotted([slot='prefix']) {
margin-inline-start: var(--wa-space-m) !important;
}
.button--has-label.button--large .button__label {
padding: 0 var(--wa-space-l);
}
::slotted([slot='suffix']) {
margin-inline-end: var(--wa-space-m) !important;
}
.button--has-prefix.button--small {
padding-inline-start: var(--wa-space-xs);
}
.button__label {
padding: 0 var(--wa-space-l);
}
.button--has-prefix.button--small .button__label {
padding-inline-start: var(--wa-space-xs);
}
.button--has-prefix.button--medium {
padding-inline-start: var(--wa-space-s);
}
.button--has-prefix.button--medium .button__label {
padding-inline-start: var(--wa-space-s);
}
.button--has-prefix.button--large {
padding-inline-start: var(--wa-space-m);
}
.button--has-prefix.button--large .button__label {
padding-inline-start: var(--wa-space-m);
}
.button--has-suffix.button--small,
.button--caret.button--small {
padding-inline-end: var(--wa-space-xs);
}
.button--has-suffix.button--small .button__label,
.button--caret.button--small .button__label {
padding-inline-end: var(--wa-space-xs);
}
.button--has-suffix.button--medium,
.button--caret.button--medium {
padding-inline-end: var(--wa-space-s);
}
.button--has-suffix.button--medium .button__label,
.button--caret.button--medium .button__label {
padding-inline-end: var(--wa-space-s);
}
.button--has-suffix.button--large,
.button--caret.button--large {
padding-inline-end: var(--wa-space-m);
}
.button--has-suffix.button--large .button__label,
.button--caret.button--large .button__label {
padding-inline-end: var(--wa-space-m);
}
/*

View File

@@ -1,7 +1,6 @@
import { classMap } from 'lit/directives/class-map.js';
import { HasSlotController } from '../../internal/slot.js';
import { html } from 'lit';
import { property } from 'lit/decorators.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './card.styles.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import type { CSSResultGroup } from 'lit';
@@ -32,16 +31,9 @@ import type { CSSResultGroup } from 'lit';
* @cssproperty --padding - The padding for each section in the card. Expects a single value.
*/
export default class WaCard extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
/** Renders the card with a header */
@property({ attribute: 'with-header', type: Boolean }) withHeader = false;
/** Renders the card with an image */
@property({ attribute: 'with-image', type: Boolean }) withImage = false;
/** Renders the card with a footer */
@property({ attribute: 'with-footer', type: Boolean }) withFooter = false;
private readonly hasSlotController = new HasSlotController(this, 'footer', 'header', 'image');
render() {
return html`
@@ -49,9 +41,9 @@ export default class WaCard extends WebAwesomeElement {
part="base"
class=${classMap({
card: true,
'card--has-footer': this.withFooter,
'card--has-image': this.withImage,
'card--has-header': this.withHeader
'card--has-footer': this.hasSlotController.test('footer'),
'card--has-image': this.hasSlotController.test('image'),
'card--has-header': this.hasSlotController.test('header')
})}
>
<slot name="image" part="image" class="card__image"></slot>

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
--background: var(--wa-color-surface-default);
--border-color: var(--wa-color-surface-border);
@@ -37,8 +40,6 @@ export default css`
.card__image::slotted(img) {
display: block;
width: 100%;
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.card:not(.card--has-image) .card__image {

View File

@@ -29,7 +29,7 @@ describe('<wa-card>', () => {
describe('when provided an element in the slot "header" to render a header', () => {
before(async () => {
el = await fixture<WaCard>(
html`<wa-card with-header>
html`<wa-card>
<div slot="header">Header Title</div>
This card has a header. You can put all sorts of things in it!
</wa-card>`
@@ -65,7 +65,7 @@ describe('<wa-card>', () => {
describe('when provided an element in the slot "footer" to render a footer', () => {
before(async () => {
el = await fixture<WaCard>(
html`<wa-card with-footer>
html`<wa-card>
This card has a footer. You can put all sorts of things in it!
<div slot="footer">Footer Content</div>
@@ -102,7 +102,7 @@ describe('<wa-card>', () => {
describe('when provided an element in the slot "image" to render a image', () => {
before(async () => {
el = await fixture<WaCard>(
html`<wa-card with-image>
html`<wa-card>
<img
slot="image"
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"

View File

@@ -1,5 +1,4 @@
import { html } from 'lit';
import componentStyles from '../../styles/component.styles.js';
import styles from './carousel-item.styles.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import type { CSSResultGroup } from 'lit';
@@ -16,7 +15,7 @@ import type { CSSResultGroup } from 'lit';
*
*/
export default class WaCarouselItem extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
connectedCallback() {
super.connectedCallback();

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
--aspect-ratio: inherit;

View File

@@ -11,7 +11,6 @@ import { prefersReducedMotion } from '../../internal/animate.js';
import { range } from 'lit/directives/range.js';
import { waitForEvent } from '../../internal/event.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './carousel.styles.js';
import WaIcon from '../icon/icon.component.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
@@ -48,7 +47,7 @@ import type WaCarouselItem from '../carousel-item/carousel-item.component.js';
* partially visible as a scroll hint.
*/
export default class SlCarousel extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
static dependencies = { 'wa-icon': WaIcon };
/** When set, allows the user to navigate the carousel in the same direction indefinitely. */

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
--aspect-ratio: 16 / 9;
--navigation-color: var(--wa-color-text-quiet);

View File

@@ -1,4 +1,4 @@
import '../../../dist/webawesome.js';
import '../../../dist/shoelace.js';
import { clickOnElement, dragElement, moveMouseOnElement } from '../../internal/test.js';
import { expect, fixture, html, nextFrame, oneEvent } from '@open-wc/testing';
import { map } from 'lit/directives/map.js';
@@ -11,8 +11,7 @@ describe('<wa-carousel>', () => {
const sandbox = sinon.createSandbox();
afterEach(async () => {
// eslint-disable-next-line
await resetMouse().catch(() => {});
await resetMouse();
});
afterEach(() => {
@@ -642,7 +641,7 @@ describe('<wa-carousel>', () => {
</wa-carousel>
`);
sandbox.spy(el, 'goToSlide');
const expectedCarouselItem: HTMLElement = el.querySelector('wa-carousel-item:nth-child(2)')!;
const expectedCarouselItem: HTMLElement = el.querySelector('sl-carousel-item:nth-child(2)')!;
// Act
el.next();
@@ -669,7 +668,7 @@ describe('<wa-carousel>', () => {
<wa-carousel-item>Node 3</wa-carousel-item>
</wa-carousel>
`);
const expectedCarouselItem: HTMLElement = el.querySelector('wa-carousel-item:nth-child(1)')!;
const expectedCarouselItem: HTMLElement = el.querySelector('sl-carousel-item:nth-child(1)')!;
el.goToSlide(1);

View File

@@ -7,7 +7,6 @@ import { ifDefined } from 'lit/directives/if-defined.js';
import { live } from 'lit/directives/live.js';
import { property, query, state } from 'lit/decorators.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './checkbox.styles.js';
import WaIcon from '../icon/icon.component.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
@@ -52,7 +51,7 @@ import type { WebAwesomeFormControl } from '../../internal/webawesome-element.js
*/
export default class WaCheckbox extends WebAwesomeElement implements WebAwesomeFormControl {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
static dependencies = { 'wa-icon': WaIcon };
private readonly formControlController = new FormControlController(this, {

View File

@@ -1,6 +1,11 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
import formControlStyles from '../../styles/form-control.styles.js';
export default css`
${componentStyles}
${formControlStyles}
:host {
--background: var(--wa-form-controls-background);
--background-checked: var(--wa-form-controls-activated-color);

View File

@@ -63,7 +63,6 @@ describe('<wa-checkbox>', () => {
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.click();
await aTimeout(0);
await el.updateComplete;
expect(changeHandler).to.have.been.calledOnce;
@@ -94,10 +93,8 @@ describe('<wa-checkbox>', () => {
el.addEventListener('wa-input', () => expect.fail('wa-input should not be emitted'));
el.checked = true;
await el.updateComplete;
await aTimeout(0);
el.checked = false;
await el.updateComplete;
await aTimeout(0);
});
it('should hide the native input with the correct positioning to scroll correctly when contained in an overflow', async () => {

View File

@@ -10,7 +10,6 @@ import { property, query, state } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import { TinyColor } from '@ctrl/tinycolor';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './color-picker.styles.js';
import WaButton from '../button/button.component.js';
import WaButtonGroup from '../button-group/button-group.component.js';
@@ -91,7 +90,7 @@ declare const EyeDropper: EyeDropperConstructor;
* @cssproperty --swatch-size - The size of each predefined color swatch.
*/
export default class WaColorPicker extends WebAwesomeElement implements WebAwesomeFormControl {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
static dependencies = {
'wa-button-group': WaButtonGroup,

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
--grid-width: 280px;
--grid-height: 200px;

View File

@@ -3,7 +3,6 @@ import { getAnimation, setDefaultAnimation } from '../../utilities/animation-reg
import { html } from 'lit';
import { LocalizeController } from '../../utilities/localize.js';
import { property, query, state } from 'lit/decorators.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './copy-button.styles.js';
import WaIcon from '../icon/icon.component.js';
import WaTooltip from '../tooltip/tooltip.component.js';
@@ -42,7 +41,7 @@ import type { CSSResultGroup } from 'lit';
* @animation copy.out - The animation to use when feedback icons animate out.
*/
export default class WaCopyButton extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
static dependencies = {
'wa-icon': WaIcon,
'wa-tooltip': WaTooltip

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
--error-color: var(--wa-color-danger-spot);
--success-color: var(--wa-color-success-spot);

View File

@@ -6,7 +6,6 @@ import { LocalizeController } from '../../utilities/localize.js';
import { property, query } from 'lit/decorators.js';
import { waitForEvent } from '../../internal/event.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './details.styles.js';
import WaIcon from '../icon/icon.component.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
@@ -48,7 +47,7 @@ import type { CSSResultGroup } from 'lit';
* @animation details.hide - The animation to use when hiding details. You can use `height: auto` with this animation.
*/
export default class WaDetails extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
static dependencies = {
'wa-icon': WaIcon

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
--background: var(--wa-color-surface-default);
--border-color: var(--wa-color-surface-border);

View File

@@ -1,6 +1,7 @@
import { animateTo, stopAnimations } from '../../internal/animate.js';
import { classMap } from 'lit/directives/class-map.js';
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';
import { HasSlotController } from '../../internal/slot.js';
import { html } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import { LocalizeController } from '../../utilities/localize.js';
@@ -8,7 +9,6 @@ import { lockBodyScrolling, unlockBodyScrolling } from '../../internal/scroll.js
import { property, query } from 'lit/decorators.js';
import { waitForEvent } from '../../internal/event.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import Modal from '../../internal/modal.js';
import styles from './dialog.styles.js';
import WaIconButton from '../icon-button/icon-button.component.js';
@@ -65,11 +65,12 @@ import type { CSSResultGroup } from 'lit';
* the third-party modal opens. Upon closing, call `modal.deactivateExternal()` to restore Shoelace's focus trapping.
*/
export default class WaDialog extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
static dependencies = {
'wa-icon-button': WaIconButton
};
private readonly hasSlotController = new HasSlotController(this, 'footer');
private readonly localize = new LocalizeController(this);
private originalTrigger: HTMLElement | null;
public modal = new Modal(this);
@@ -86,16 +87,16 @@ export default class WaDialog extends WebAwesomeElement {
@property({ type: Boolean, reflect: true }) open = false;
/**
* The dialog's label as displayed in the header. You should always include a relevant label, as it is required for
* proper accessibility. If you need to display HTML, use the `label` slot instead.
* The dialog's label as displayed in the header. You should always include a relevant label even when using
* `no-header`, as it is required for proper accessibility. If you need to display HTML, use the `label` slot instead.
*/
@property({ reflect: true }) label = '';
/** Renders the dialog with a header. */
@property({ attribute: 'with-header', type: Boolean, reflect: true }) withHeader = false;
/** Renders the dialog with a footer. */
@property({ attribute: 'with-footer', type: Boolean, reflect: true }) withFooter = false;
/**
* Disables the header. This will also remove the default close button, so please ensure you provide an easy,
* accessible way for users to dismiss the dialog.
*/
@property({ attribute: 'no-header', type: Boolean, reflect: true }) noHeader = false;
firstUpdated() {
this.dialog.hidden = !this.open;
@@ -270,8 +271,7 @@ export default class WaDialog extends WebAwesomeElement {
class=${classMap({
dialog: true,
'dialog--open': this.open,
'dialog--with-header': this.withHeader,
'dialog--with-footer': this.withFooter
'dialog--has-footer': this.hasSlotController.test('footer')
})}
>
<div part="overlay" class="dialog__overlay" @click=${() => this.requestClose('overlay')} tabindex="-1"></div>
@@ -282,11 +282,11 @@ export default class WaDialog extends WebAwesomeElement {
role="dialog"
aria-modal="true"
aria-hidden=${this.open ? 'false' : 'true'}
aria-label=${ifDefined(this.withHeader ? undefined : this.label)}
aria-labelledby=${ifDefined(this.withHeader ? 'title' : undefined)}
aria-label=${ifDefined(this.noHeader ? this.label : undefined)}
aria-labelledby=${ifDefined(!this.noHeader ? 'title' : undefined)}
tabindex="-1"
>
${this.withHeader
${!this.noHeader
? html`
<header part="header" class="dialog__header">
<h2 part="title" class="dialog__title" id="title">
@@ -313,13 +313,9 @@ export default class WaDialog extends WebAwesomeElement {
}
<div part="body" class="dialog__body" tabindex="-1"><slot></slot></div>
${this.withFooter
? html`
<footer part="footer" class="dialog__footer">
<slot name="footer"></slot>
</footer>
`
: ''}
<footer part="footer" class="dialog__footer">
<slot name="footer"></slot>
</footer>
</div>
</div>
`;

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
--width: 31rem;
--header-spacing: var(--wa-space-l);
@@ -99,6 +102,10 @@ export default css`
margin-inline-start: var(--wa-spacing-xs);
}
.dialog:not(.dialog--has-footer) .dialog__footer {
display: none;
}
.dialog__overlay {
position: fixed;
top: 0;

View File

@@ -6,10 +6,10 @@ import { sendKeys } from '@web/test-runner-commands';
import sinon from 'sinon';
import type WaDialog from './dialog.js';
describe('<wa-dialog with-header>', () => {
describe('<wa-dialog>', () => {
it('should be visible with the open attribute', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog with-header open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
`);
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
@@ -18,7 +18,7 @@ describe('<wa-dialog with-header>', () => {
it('should not be visible without the open attribute', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog with-header>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
`);
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
@@ -27,7 +27,7 @@ describe('<wa-dialog with-header>', () => {
it('should emit wa-show and wa-after-show when calling show()', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog with-header>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
`);
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
const showHandler = sinon.spy();
@@ -47,7 +47,7 @@ describe('<wa-dialog with-header>', () => {
it('should emit wa-hide and wa-after-hide when calling hide()', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog with-header open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
`);
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
const hideHandler = sinon.spy();
@@ -67,7 +67,7 @@ describe('<wa-dialog with-header>', () => {
it('should emit wa-show and wa-after-show when setting open = true', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog with-header>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
`);
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
const showHandler = sinon.spy();
@@ -87,7 +87,7 @@ describe('<wa-dialog with-header>', () => {
it('should emit wa-hide and wa-after-hide when setting open = false', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog with-header open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
`);
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
const hideHandler = sinon.spy();
@@ -107,7 +107,7 @@ describe('<wa-dialog with-header>', () => {
it('should not close when wa-request-close is prevented', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog with-header open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
`);
const overlay = el.shadowRoot!.querySelector<HTMLElement>('[part~="overlay"]')!;
@@ -120,7 +120,7 @@ describe('<wa-dialog with-header>', () => {
});
it('should allow initial focus to be set', async () => {
const el = await fixture<WaDialog>(html` <wa-dialog with-header><input /></wa-dialog> `);
const el = await fixture<WaDialog>(html` <wa-dialog><input /></wa-dialog> `);
const input = el.querySelector('input')!;
const initialFocusHandler = sinon.spy((event: Event) => {
event.preventDefault();
@@ -137,7 +137,7 @@ describe('<wa-dialog with-header>', () => {
});
it('should close when pressing Escape', async () => {
const el = await fixture<WaDialog>(html` <wa-dialog with-header open></wa-dialog> `);
const el = await fixture<WaDialog>(html` <wa-dialog open></wa-dialog> `);
const hideHandler = sinon.spy();
el.addEventListener('wa-hide', hideHandler);
@@ -162,7 +162,7 @@ describe('<wa-dialog with-header>', () => {
render() {
return html`
<h1>Dialog Example</h1>
<wa-dialog with-header label="Dialog" class="dialog-overview">
<wa-dialog label="Dialog" class="dialog-overview">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<br />
<label><input type="checkbox" />A</label>
@@ -200,7 +200,7 @@ describe('<wa-dialog with-header>', () => {
const dialog = container.shadowRoot?.querySelector('wa-dialog');
if (!dialog) {
throw Error('Could not find <wa-dialog with-header> element.');
throw Error('Could not find <wa-dialog> element.');
}
const closeButton = dialog.shadowRoot?.querySelector('wa-icon-button');

View File

@@ -1,6 +1,5 @@
import { property } from 'lit/decorators.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './divider.styles.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import type { CSSResultGroup } from 'lit';
@@ -16,7 +15,7 @@ import type { CSSResultGroup } from 'lit';
* @cssproperty --spacing - The spacing of the divider.
*/
export default class WaDivider extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
/** Draws the divider in a vertical orientation. */
@property({ type: Boolean, reflect: true }) vertical = false;

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
--color: var(--wa-color-surface-border);
--width: var(--wa-border-width-s);

View File

@@ -1,6 +1,7 @@
import { animateTo, stopAnimations } from '../../internal/animate.js';
import { classMap } from 'lit/directives/class-map.js';
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';
import { HasSlotController } from '../../internal/slot.js';
import { html } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import { LocalizeController } from '../../utilities/localize.js';
@@ -9,7 +10,6 @@ import { property, query } from 'lit/decorators.js';
import { uppercaseFirstLetter } from '../../internal/string.js';
import { waitForEvent } from '../../internal/event.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import Modal from '../../internal/modal.js';
import styles from './drawer.styles.js';
import WaIconButton from '../icon-button/icon-button.component.js';
@@ -74,9 +74,10 @@ import type { CSSResultGroup } from 'lit';
* the third-party modal opens. Upon closing, call `modal.deactivateExternal()` to restore Shoelace's focus trapping.
*/
export default class WaDrawer extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
static dependencies = { 'wa-icon-button': WaIconButton };
private readonly hasSlotController = new HasSlotController(this, 'footer');
private readonly localize = new LocalizeController(this);
private originalTrigger: HTMLElement | null;
public modal = new Modal(this);
@@ -93,27 +94,36 @@ export default class WaDrawer extends WebAwesomeElement {
@property({ type: Boolean, reflect: true }) open = false;
/**
* The drawer's label as displayed in the header. You should always include a relevant label, as it is required for
* proper accessibility. If you need to display HTML, use the `label` slot instead.
* The drawer's label as displayed in the header. You should always include a relevant label even when using
* `no-header`, as it is required for proper accessibility. If you need to display HTML, use the `label` slot instead.
*/
@property({ reflect: true }) label = '';
/** The direction from which the drawer will open. */
@property({ reflect: true }) placement: 'top' | 'end' | 'bottom' | 'start' = 'end';
/** Renders the drawer with a header. */
@property({ attribute: 'with-header', type: Boolean, reflect: true }) withHeader = false;
/**
* By default, the drawer slides out of its containing block (usually the viewport). To make the drawer slide out of
* its parent element, set this attribute and add `position: relative` to the parent.
*/
@property({ type: Boolean, reflect: true }) contained = false;
/** Renders the drawer with a footer. */
@property({ attribute: 'with-footer', type: Boolean, reflect: true }) withFooter = false;
/**
* Removes the header. This will also remove the default close button, so please ensure you provide an easy,
* accessible way for users to dismiss the drawer.
*/
@property({ attribute: 'no-header', type: Boolean, reflect: true }) noHeader = false;
firstUpdated() {
this.drawer.hidden = !this.open;
if (this.open) {
this.addOpenListeners();
this.modal.activate();
lockBodyScrolling(this);
if (!this.contained) {
this.modal.activate();
lockBodyScrolling(this);
}
}
}
@@ -141,8 +151,10 @@ export default class WaDrawer extends WebAwesomeElement {
private addOpenListeners() {
if ('CloseWatcher' in window) {
this.closeWatcher?.destroy();
this.closeWatcher = new CloseWatcher();
this.closeWatcher.onclose = () => this.requestClose('keyboard');
if (!this.contained) {
this.closeWatcher = new CloseWatcher();
this.closeWatcher.onclose = () => this.requestClose('keyboard');
}
} else {
document.addEventListener('keydown', this.handleDocumentKeyDown);
this.closeWatcher?.destroy();
@@ -154,6 +166,11 @@ export default class WaDrawer extends WebAwesomeElement {
}
private handleDocumentKeyDown = (event: KeyboardEvent) => {
// Contained drawers aren't modal and don't response to the escape key
if (this.contained) {
return;
}
if (event.key === 'Escape' && this.modal.isActive() && this.open) {
event.stopImmediatePropagation();
this.requestClose('keyboard');
@@ -167,8 +184,12 @@ export default class WaDrawer extends WebAwesomeElement {
this.emit('wa-show');
this.addOpenListeners();
this.originalTrigger = document.activeElement as HTMLElement;
this.modal.activate();
lockBodyScrolling(this);
// Lock body scrolling only if the drawer isn't contained
if (!this.contained) {
this.modal.activate();
lockBodyScrolling(this);
}
// When the drawer is shown, Safari will attempt to set focus on whatever element has autofocus. This causes the
// drawer's animation to jitter, so we'll temporarily remove the attribute, call `focus({ preventScroll: true })`
@@ -217,8 +238,11 @@ export default class WaDrawer extends WebAwesomeElement {
// Hide
this.emit('wa-hide');
this.removeOpenListeners();
this.modal.deactivate();
unlockBodyScrolling(this);
if (!this.contained) {
this.modal.deactivate();
unlockBodyScrolling(this);
}
await Promise.all([stopAnimations(this.drawer), stopAnimations(this.overlay)]);
const panelAnimation = getAnimation(this, `drawer.hide${uppercaseFirstLetter(this.placement)}`, {
@@ -254,6 +278,19 @@ export default class WaDrawer extends WebAwesomeElement {
}
}
@watch('contained', { waitUntilFirstUpdate: true })
handleNoModalChange() {
if (this.open && !this.contained) {
this.modal.activate();
lockBodyScrolling(this);
}
if (this.open && this.contained) {
this.modal.deactivate();
unlockBodyScrolling(this);
}
}
/** Shows the drawer. */
async show() {
if (this.open) {
@@ -285,9 +322,10 @@ export default class WaDrawer extends WebAwesomeElement {
'drawer--end': this.placement === 'end',
'drawer--bottom': this.placement === 'bottom',
'drawer--start': this.placement === 'start',
'drawer--contained': this.contained,
'drawer--fixed': !this.contained,
'drawer--rtl': this.localize.dir() === 'rtl',
'drawer--with-header': this.withHeader,
'drawer--with-footer': this.withFooter
'drawer--has-footer': this.hasSlotController.test('footer')
})}
>
<div part="overlay" class="drawer__overlay" @click=${() => this.requestClose('overlay')} tabindex="-1"></div>
@@ -298,11 +336,11 @@ export default class WaDrawer extends WebAwesomeElement {
role="dialog"
aria-modal="true"
aria-hidden=${this.open ? 'false' : 'true'}
aria-label=${ifDefined(this.withHeader ? undefined : this.label)}
aria-labelledby=${ifDefined(this.withHeader ? 'title' : undefined)}
aria-label=${ifDefined(this.noHeader ? this.label : undefined)}
aria-labelledby=${ifDefined(!this.noHeader ? 'title' : undefined)}
tabindex="0"
>
${this.withHeader
${!this.noHeader
? html`
<header part="header" class="drawer__header">
<h2 part="title" class="drawer__title" id="title">
@@ -328,13 +366,9 @@ export default class WaDrawer extends WebAwesomeElement {
<slot part="body" class="drawer__body"></slot>
${this.withFooter
? html`
<footer part="footer" class="drawer__footer">
<slot name="footer"></slot>
</footer>
`
: ''}
<footer part="footer" class="drawer__footer">
<slot name="footer"></slot>
</footer>
</div>
</div>
`;

View File

@@ -1,18 +1,20 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
--size: 25rem;
--header-spacing: var(--wa-space-l);
--body-spacing: var(--wa-space-l);
--footer-spacing: var(--wa-space-l);
--panel-background: var(--wa-color-surface-raised);
display: contents;
}
.drawer {
position: fixed;
z-index: var(--wa-z-index-drawer);
top: 0;
inset-inline-start: 0;
width: 100%;
@@ -21,6 +23,16 @@ export default css`
overflow: hidden;
}
.drawer--contained {
position: absolute;
z-index: initial;
}
.drawer--fixed {
position: fixed;
z-index: var(--wa-z-index-drawer);
}
.drawer__panel {
position: absolute;
display: flex;
@@ -28,7 +40,7 @@ export default css`
z-index: 2;
max-width: 100%;
max-height: 100%;
background-color: var(--wa-color-surface-raised);
background-color: var(--panel-background);
box-shadow: var(--wa-shadow-level-3);
overflow: auto;
pointer-events: all;
@@ -121,6 +133,10 @@ export default css`
margin-inline-end: var(--wa-spacing-xs);
}
.drawer:not(.drawer--has-footer) .drawer__footer {
display: none;
}
.drawer__overlay {
display: block;
position: fixed;
@@ -132,6 +148,10 @@ export default css`
pointer-events: all;
}
.drawer--contained .drawer__overlay {
display: none;
}
@media (forced-colors: active) {
.drawer__panel {
border: solid 1px white;

View File

@@ -7,7 +7,6 @@ import { LocalizeController } from '../../utilities/localize.js';
import { property, query } from 'lit/decorators.js';
import { waitForEvent } from '../../internal/event.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './dropdown.styles.js';
import WaPopup from '../popup/popup.component.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
@@ -41,7 +40,7 @@ import type WaMenu from '../menu/menu.js';
* @animation dropdown.hide - The animation to use when hiding the dropdown.
*/
export default class WaDropdown extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
static dependencies = { 'wa-popup': WaPopup };
@query('.dropdown') popup: WaPopup;

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
display: inline-block;
}

View File

@@ -2,7 +2,6 @@ import { classMap } from 'lit/directives/class-map.js';
import { html, literal } from 'lit/static-html.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { property, query, state } from 'lit/decorators.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './icon-button.styles.js';
import WaIcon from '../icon/icon.component.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
@@ -22,7 +21,7 @@ import type { CSSResultGroup } from 'lit';
* @csspart base - The component's base wrapper.
*/
export default class WaIconButton extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
static dependencies = { 'wa-icon': WaIcon };
@query('.icon-button') button: HTMLButtonElement | HTMLLinkElement;

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
display: inline-block;
color: var(--wa-color-text-quiet);

View File

@@ -3,7 +3,6 @@ import { html } from 'lit';
import { isTemplateResult } from 'lit/directive-helpers.js';
import { property, state } from 'lit/decorators.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './icon.styles.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
@@ -34,7 +33,7 @@ interface IconSource {
* @csspart use - The <use> element generated when using `spriteSheet: true`
*/
export default class WaIcon extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
private initialRender = false;

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
display: inline-block;
width: auto;

View File

@@ -6,7 +6,6 @@ import { LocalizeController } from '../../utilities/localize.js';
import { property, query } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './image-comparer.styles.js';
import WaIcon from '../icon/icon.component.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
@@ -36,7 +35,7 @@ import type { CSSResultGroup } from 'lit';
* @cssproperty --handle-size - The size of the compare handle.
*/
export default class WaImageComparer extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
static scopedElement = { 'wa-icon': WaIcon };
private readonly localize = new LocalizeController(this);

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
--divider-width: 2px;
--handle-size: 2.5rem;

View File

@@ -2,7 +2,6 @@ import { html } from 'lit';
import { property } from 'lit/decorators.js';
import { requestInclude } from './request.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './include.styles.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import type { CSSResultGroup } from 'lit';
@@ -17,7 +16,7 @@ import type { CSSResultGroup } from 'lit';
* @event {{ status: number }} wa-error - Emitted when the included file fails to load due to an error.
*/
export default class WaInclude extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
/**
* The location of the HTML file to include. Be sure you trust the content you are including as it will be executed as

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
display: block;
}

View File

@@ -8,8 +8,6 @@ import { live } from 'lit/directives/live.js';
import { LocalizeController } from '../../utilities/localize.js';
import { property, query, state } from 'lit/decorators.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import formControlStyles from '../../styles/form-control.styles.js';
import styles from './input.styles.js';
import WaIcon from '../icon/icon.component.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
@@ -58,7 +56,7 @@ import type { WebAwesomeFormControl } from '../../internal/webawesome-element.js
* @cssproperty --box-shadow - The shadow effects around the edges of the input.
*/
export default class WaInput extends WebAwesomeElement implements WebAwesomeFormControl {
static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];
static styles: CSSResultGroup = styles;
static dependencies = { 'wa-icon': WaIcon };
private readonly formControlController = new FormControlController(this, {

View File

@@ -1,6 +1,11 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
import formControlStyles from '../../styles/form-control.styles.js';
export default css`
${componentStyles}
${formControlStyles}
:host {
--background: var(--wa-form-controls-background);
--border-color: var(--wa-form-controls-resting-color);

View File

@@ -1,11 +1,10 @@
import { classMap } from 'lit/directives/class-map.js';
import { getTextContent } from '../../internal/slot.js';
import { getTextContent, HasSlotController } from '../../internal/slot.js';
import { html } from 'lit';
import { LocalizeController } from '../../utilities/localize.js';
import { property, query } from 'lit/decorators.js';
import { SubmenuController } from './submenu-controller.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './menu-item.styles.js';
import WaIcon from '../icon/icon.component.js';
import WaPopup from '../popup/popup.component.js';
@@ -39,7 +38,7 @@ import type { CSSResultGroup } from 'lit';
* @cssproperty [--submenu-offset=-2px] - The distance submenus shift to overlap the parent menu.
*/
export default class WaMenuItem extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
static dependencies = {
'wa-icon': WaIcon,
'wa-popup': WaPopup,
@@ -67,7 +66,8 @@ export default class WaMenuItem extends WebAwesomeElement {
@property({ type: Boolean, reflect: true }) disabled = false;
private readonly localize = new LocalizeController(this);
private submenuController: SubmenuController = new SubmenuController(this, this.localize);
private readonly hasSlotController = new HasSlotController(this, 'submenu');
private submenuController: SubmenuController = new SubmenuController(this, this.hasSlotController, this.localize);
connectedCallback() {
super.connectedCallback();
@@ -149,7 +149,7 @@ export default class WaMenuItem extends WebAwesomeElement {
}
isSubmenu() {
return this.querySelector(`:scope > [slot="submenu"]`) !== null;
return this.hasSlotController.test('submenu');
}
render() {
@@ -191,7 +191,7 @@ export default class WaMenuItem extends WebAwesomeElement {
></wa-icon>
</span>
${this.submenuController.renderSubmenu()} ${this.loading ? html`<wa-spinner part="spinner"></wa-spinner>` : ''}
${this.submenuController.renderSubmenu()} ${this.loading ? html`<sl-spinner part="spinner"></sl-spinner>` : ''}
</div>
`;
}

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
--submenu-offset: -2px;

View File

@@ -1,4 +1,5 @@
import { createRef, ref, type Ref } from 'lit/directives/ref.js';
import { type HasSlotController } from '../../internal/slot.js';
import { html } from 'lit';
import { type LocalizeController } from '../../utilities/localize.js';
import type { ReactiveController, ReactiveControllerHost } from 'lit';
@@ -13,20 +14,22 @@ export class SubmenuController implements ReactiveController {
private isConnected = false;
private isPopupConnected = false;
private skidding = 0;
private readonly hasSlotController: HasSlotController;
private readonly localize: LocalizeController;
private readonly submenuOpenDelay = 100;
constructor(host: ReactiveControllerHost & WaMenuItem, localize: LocalizeController) {
constructor(
host: ReactiveControllerHost & WaMenuItem,
hasSlotController: HasSlotController,
localize: LocalizeController
) {
(this.host = host).addController(this);
this.hasSlotController = hasSlotController;
this.localize = localize;
}
private hasSubmenu() {
return this.host.querySelector(`:scope > [slot="submenu"]`) !== null;
}
hostConnected() {
if (this.hasSubmenu() && !this.host.disabled) {
if (this.hasSlotController.test('submenu') && !this.host.disabled) {
this.addListeners();
}
}
@@ -36,7 +39,7 @@ export class SubmenuController implements ReactiveController {
}
hostUpdated() {
if (this.hasSubmenu() && !this.host.disabled) {
if (this.hasSlotController.test('submenu') && !this.host.disabled) {
this.addListeners();
this.updateSkidding();
} else {
@@ -90,7 +93,7 @@ export class SubmenuController implements ReactiveController {
};
private handleMouseOver = () => {
if (this.hasSubmenu()) {
if (this.hasSlotController.test('submenu')) {
this.enableSubmenu();
}
};

View File

@@ -1,5 +1,4 @@
import { html } from 'lit';
import componentStyles from '../../styles/component.styles.js';
import styles from './menu-label.styles.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import type { CSSResultGroup } from 'lit';
@@ -15,7 +14,7 @@ import type { CSSResultGroup } from 'lit';
* @csspart base - The component's base wrapper.
*/
export default class WaMenuLabel extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
render() {
return html` <slot part="base" class="menu-label"></slot> `;

View File

@@ -1,6 +1,9 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
display: block;
font-size: var(--wa-font-size-s);

View File

@@ -1,11 +1,9 @@
import { html } from 'lit';
import { query } from 'lit/decorators.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './menu.styles.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import type { CSSResultGroup } from 'lit';
import type WaMenuItem from '../menu-item/menu-item.component.js';
export interface MenuSelectEventDetail {
item: WaMenuItem;
}
@@ -21,7 +19,7 @@ export interface MenuSelectEventDetail {
* @event {{ item: WaMenuItem }} wa-select - Emitted when a menu item is selected.
*/
export default class WaMenu extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
static styles: CSSResultGroup = styles;
@query('slot') defaultSlot: HTMLSlotElement;

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