Compare commits

..

21 Commits

Author SHA1 Message Date
Cory LaViska
4e9223953e remove unused controller 2024-03-21 15:10:24 -04:00
Cory LaViska
cf9044d826 remove unused controller 2024-03-21 15:10:16 -04:00
Cory LaViska
252d710175 remove slot detection from card, dialog, drawer 2024-03-21 14:47:29 -04:00
Cory LaViska
58d14ed1d2 remove slot detection from buttons 2024-03-21 13:55:40 -04:00
Cory LaViska
879e912a5c remove slot detection from breadcrumb items 2024-03-21 13:55:31 -04:00
Cory LaViska
c69e98cd7f remove slot detection from alert 2024-03-21 13:55:22 -04:00
Konnor Rogers
8e5e039af8 Rename Layout to Page (#65)
* working on layouts

* fix buildS

* first layout converted to sportawesome

* working on playgrounds

* continued work on layouts

* working on astro layout

* light-pen 3

* rename layout to page

* prettier

* add vercel.json

* add vercel.json

* add vercel.json

* add vercel.json

* add vercel.json

* add sandbox-settings

* add sandbox-settings

* add sandbox-settings

* maybe now?

* remove sandbox-settings

* remove vercel.json

* rename to pagE

* fix grid for navigation

* prettier

* fix stuff

* fix search

* prettier
2024-03-14 12:47:28 -04:00
Cory LaViska
2e725a2d93 prettier 2024-03-12 16:02:31 -04:00
Cory LaViska
c139865635 fix padding 2024-03-12 15:59:22 -04:00
Cory LaViska
f59c544fbe update content 2024-03-12 15:06:28 -04:00
Cory LaViska
28bdcae2c6 fix layout to be more font awesomey 2024-03-12 15:00:58 -04:00
konnorrogers
b1530d0773 Fix dev server infinite reload 2024-03-12 14:16:11 -04:00
Konnor Rogers
5feee64425 Font Awesome theme 👀 (#64)
* font awesome site theme

* separate font awesome.css

* prettier

* remove image borders

* fix search and prism

* select first item

* 30ms'

* fix double render

* fix for turbo loading

* fix preview

* prettier

* fix pagefind

* prettier
2024-03-12 13:45:33 -04:00
Konnor Rogers
9647259b5f fix the test suite (#63)
* fix the test suite

* prettier

* fix the test suite

* prettier
2024-03-11 12:23:25 -04:00
Cory LaViska
84e276ae10 Merge pull request #32 from shoelace-style/prepare
Backport 1868
2024-03-06 07:56:34 -05:00
Cory LaViska
4718c3d815 move to prepare 2024-03-06 07:56:24 -05:00
Cory LaViska
aa1bfb0885 Merge branch 'next' into prepare 2024-03-06 07:54:52 -05:00
Cory LaViska
acf2055768 Merge pull request #31 from shoelace-style/efficient-style-imports
Backport 1861
2024-03-06 07:54:16 -05:00
Cory LaViska
ef4d2fac40 Merge branch 'next' into efficient-style-imports 2024-03-06 07:53:52 -05:00
Cory LaViska
a1b1d594aa backport 1868 2024-02-12 12:50:55 -05:00
Cory LaViska
531a2f1634 backport 1861 2024-02-09 11:03:15 -05:00
158 changed files with 1890 additions and 2533 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 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.
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.
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
View File

@@ -1 +0,0 @@
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 [cory@fontawesome.com](mailto:cory@abeautifulsite.net) and include "WEB AWESOME SECURITY" in the subject line.
To report a security issue, email [support@fontawesome.com](mailto:support@fontawesome.com) 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,4 +1,5 @@
*.hbs
*.mdx
.cache
.github
cspell.json

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
<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>

After

Width:  |  Height:  |  Size: 722 B

View File

@@ -33,22 +33,23 @@ export default defineConfig({
server: {
open: true,
port: 4000,
host: true,
fs: {
strict: false
}
host: true
},
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

@@ -1,389 +0,0 @@
---
---
<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">
<wa-dialog id="icon-chooser" label="Browse Icons" with-header>
<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,6 +8,7 @@ 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,143 +52,220 @@ const pagefindTranslations = {
<script>
class SiteSearch extends HTMLElement {
translations = {}
stripTrailingSlash = (path: string) => path.replace(/(.)\/(#.*)?$/, '$1$2');
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')!;
connectedCallback () {
this.translations = {};
/** 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 = {};
try {
this.translations = JSON.parse(this.dataset.translations || '{}');
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;
this.openBtn.addEventListener('click', this.openModal);
this.openBtn.disabled = false;
this.closeBtn.addEventListener('click', this.closeModal);
this.dialog.addEventListener('close', this.handleClose)
document.querySelector("#starlight__search").innerHTML = ""
// Listen for `/` and `cmd + k` keyboard shortcuts.
window.addEventListener('keydown', this.handleKeyDown);
window.addEventListener('turbo:load', () => {
document.querySelector("#starlight__search").innerHTML = ""
window.addEventListener('DOMContentLoaded', this.loadPageFind);
window.addEventListener('turbo:load', this.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')
}
}
// @ts-expect-error — Missing types for @pagefind/default-ui package.
const { PagefindUI } = await import('@pagefind/default-ui');
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;
});
},
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;
});
},
});
});
});
}
get formatURL () {
return this.shouldStrip ? this.stripTrailingSlash : (path: string) => path;
}
let links: (HTMLAnchorElement | HTMLButtonElement)[] = []
get shouldStrip () {
return this.dataset.stripTrailingSlash != null;
}
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 {}
</script>
<style>
@@ -358,6 +435,12 @@ 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);
}
@@ -380,15 +463,17 @@ 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 *)):focus-within,
.pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)):is(.is-active, :focus-within),
#starlight__search .pagefind-ui__result-nested:hover,
#starlight__search .pagefind-ui__result-nested:focus-within {
#starlight__search .pagefind-ui__result-nested:is(.is-active, :focus-within),
#starlight__search .pagefind-ui__button.is-active {
outline: 1px solid var(--sl-color-accent-high);
}
#starlight__search
.pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)):focus-within,
#starlight__search .pagefind-ui__result-nested:focus-within {
.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 {
background-color: var(--sl-color-accent-low);
}
@@ -479,4 +564,7 @@ const pagefindTranslations = {
background-color: transparent;
font-weight: 600;
}
</style>

View File

@@ -9,6 +9,15 @@ 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 class="card-overview">
<wa-card with-image with-footer 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 className="card-overview">
<WaCard with-image with-footer 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 class="card-header">
<wa-card with-header 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 className="card-header">
<WaCard with-header 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 class="card-footer">
<wa-card with-footer 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 className="card-footer">
<WaCard with-footer 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 class="card-image">
<wa-card with-image 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 className="card-image">
<WaCard with-image 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" class="dialog-overview">
<wa-dialog label="Dialog" with-header with-footer 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" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" with-header with-footer 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" class="dialog-width" style="--width: 50vw;">
<wa-dialog label="Dialog" with-header with-footer 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" open={open} style={{ '--width': '50vw' }} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" with-header with-footer 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" class="dialog-scrolling">
<wa-dialog label="Dialog" with-header with-footer 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" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" with-header with-footer 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" class="dialog-header-actions">
<wa-dialog label="Dialog" with-header with-footer 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" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" with-header with-footer 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" class="dialog-deny-close">
<wa-dialog label="Dialog" with-header with-footer 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" open={open} onWaRequestClose={handleRequestClose} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" with-header with-footer 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" class="dialog-focus">
<wa-dialog label="Dialog" with-header with-footer 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" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" with-header with-footer 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" class="drawer-overview">
<wa-drawer label="Drawer" with-header with-footer 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" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" with-header with-footer 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" class="drawer-placement-start">
<wa-drawer label="Drawer" placement="start" with-header with-footer 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" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" placement="start" with-header with-footer 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" class="drawer-placement-top">
<wa-drawer label="Drawer" placement="top" with-header with-footer 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" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" placement="top" with-header with-footer 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" class="drawer-placement-bottom">
<wa-drawer label="Drawer" placement="bottom" with-header with-footer 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" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" placement="bottom" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
This drawer slides in from the bottom.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
@@ -184,84 +184,12 @@ 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" class="drawer-custom-size" style="--size: 50vw;">
<wa-drawer label="Drawer" with-header with-footer 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>
@@ -288,7 +216,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)} style={{ '--size': '50vw' }}>
<WaDrawer label="Drawer" with-header with-footer 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
@@ -306,7 +234,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" class="drawer-scrolling">
<wa-drawer label="Drawer" with-header with-footer 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>
@@ -335,7 +263,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<div
style={{
height: '150vh',
@@ -361,7 +289,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" class="drawer-header-actions">
<wa-drawer label="Drawer" with-header with-footer 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>
@@ -392,7 +320,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" with-header with-footer 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)}>
@@ -415,7 +343,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" class="drawer-deny-close">
<wa-drawer label="Drawer" with-header with-footer 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>
@@ -456,7 +384,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" open={open} onWaRequestClose={handleRequestClose} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" with-header with-footer 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
@@ -474,7 +402,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" class="drawer-focus">
<wa-drawer label="Drawer" with-header with-footer 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>
@@ -503,7 +431,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" with-header with-footer 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 class="card-overview">
<wa-card with-image 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,43 +1,14 @@
---
title: 'Web Awesome: A forward-thinking library of web components.'
title: 'Web Awesome'
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)
@@ -48,14 +19,8 @@ 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,5 +17,4 @@ 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.
Cory LaViska<br>
_Creator of Web Awesome_
The Web Awesome team

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. You're also welcome to follow [@claviska](https://twitter.com/claviska), the creator, for tweets about web components, web development, and life.
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.
**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 `noHeader` and a corresponding kebab-case attribute such as `no-header`.
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`.
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

@@ -1,37 +0,0 @@
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,7 +94,10 @@ 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>
<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>
<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>
</wa-tab-panel>
<wa-tab-panel name="import">
@@ -102,23 +105,32 @@ 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>
<pre><code class="language-js" set:html={
highlight("js", `import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${version}/${cdndir}/${component.path}';`)
}></code></pre>
<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>
</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>
<pre><code class="language-js" set:html={highlight("js", `import '@shoelace-style/shoelace/${npmdir}/${component.path}';`)}></code></pre>
<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>
</wa-tab-panel>
<wa-tab-panel name="react">
<p>
To import this component as a <a href="/frameworks/react">React component</a>:
</p>
<pre><code class="language-js" set:html={highlight("js", `import ${component.name} from '@shoelace-style/shoelace/${npmdir}/react/${component.tagNameWithoutPrefix}';`)}></code></pre>
<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>
</wa-tab-panel>
</wa-tab-group>

View File

@@ -1,4 +1,4 @@
wa-page > * {
.grid {
font-size: 1.35rem;
text-align: center;
display: grid;
@@ -6,84 +6,58 @@ wa-page > * {
padding: 1rem;
}
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;
header {
background-color: var(--wa-color-blue-90);
}
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'] {
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,22 +1,16 @@
<wa-page main-id="main-content" class="wa-theme-light">
<header slot="banner" class="grid banner">banner</header>
<header slot="banner">banner</header>
<header slot="header" class="grid header">header</header>
<header slot="header">
<wa-icon-button name="bars" data-toggle-nav></wa-icon-button>
header
</header>
<aside class="grid" slot="menu">menu</aside>
<aside slot="navigation">menu</aside>
<header class="grid" slot="main-header">main-header</header>
<header slot="main-header">main-header</header>
<main class="grid" id="main-content">main</main>
<main id="main-content">main</main>
<footer class="grid" slot="main-footer">main-footer</footer>
<footer slot="main-footer">main-footer</footer>
<aside slot="aside">aside</aside>
<footer slot="footer">footer</footer>
</wa-page>
<aside class="grid" slot="aside">aside</aside>
<footer class="grid" slot="footer">footer</footer>
</wa-page>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,19 +1,58 @@
// import { APIContext } from "astro";
import * as pagefind from 'pagefind';
import * as path from 'node:path';
import { generateSearch } from '../js/generate-search';
// 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> = {};
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(null, {
return new Response(JSON.stringify(json), {
status: 200,
headers: {
'Content-Type': 'application/json'

View File

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

285
docs/src/styles/font-awesome.css vendored Normal file
View File

@@ -0,0 +1,285 @@
: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,6 +22,10 @@
--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 {
@@ -44,10 +48,26 @@ html:not(.wa-theme-dark) .only-dark {
padding: 0 !important;
}
@media screen and (max-width: 900px) {
:root {
--docs-content-padding: 1rem;
}
.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;
}
html {
@@ -168,11 +188,6 @@ th.table-description {
}
/* Code blocks */
pre:not(:last-child) {
margin-bottom: 1.5rem;
}
pre {
position: relative;
overflow: auto;
@@ -191,71 +206,6 @@ 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;
@@ -269,594 +219,30 @@ pre .token.italic {
}
.copy-code-button::part(button) {
background-color: var(--wa-color-neutral-fill-subtle);
background-color: var(--wa-color-primary-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-neutral-fill-subtle), var(--wa-color-mix-hover));
background-color: color-mix(in oklab, var(--wa-color-primary-fill-subtle), var(--wa-color-mix-hover));
}
.copy-code-button::part(button):active {
background-color: color-mix(in oklab, var(--wa-color-neutral-fill-subtle), var(--wa-color-mix-active));
background-color: color-mix(in oklab, var(--wa-color-primary-fill-subtle), var(--wa-color-mix-active));
}
:is(.code-preview__source, pre) .copy-code-button {
:is(.code-preview__source, pre, .code-preview) > .copy-code-button {
opacity: 0;
scale: 0.75;
}
:is(.code-preview__source, pre):hover .copy-code-button,
:is(.code-preview__source, pre, .code-preview):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,7 +1,3 @@
:root {
--sl-content-width: 50rem;
}
/* Code highlighter */
pre,
.sl-markdown-content pre:not(:where(.not-content *)) {

1407
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",
"postinstall": "npx playwright install && ./scripts/clear-vercel-patches.sh && npm i --ignore-scripts && npx patch-package",
"prepare": "./scripts/clear-vercel-patches.sh && npm i --ignore-scripts && npx patch-package && npx playwright install",
"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.15.3",
"@web/test-runner-commands": "^0.6.6",
"@web/test-runner-playwright": "^0.9.0",
"@web/test-runner": "^0.18.1",
"@web/test-runner-commands": "^0.9.0",
"@web/test-runner-playwright": "^0.11.0",
"astro": "^4.0.1",
"bootstrap-icons": "^1.11.1",
"browser-sync": "^2.29.3",
@@ -133,6 +133,7 @@
"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 = styles;
static styles: CSSResultGroup = [componentStyles, styles];
private readonly localize = new LocalizeController(this);

View File

@@ -1,9 +1,6 @@
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,11 +50,10 @@ 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 = styles;
static styles: CSSResultGroup = [componentStyles, 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;
@@ -197,7 +196,6 @@ 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,9 +1,6 @@
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);
@@ -59,10 +56,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;
}
@@ -73,13 +70,15 @@ export default css`
align-items: center;
color: var(--icon-color);
font-size: var(--icon-size);
padding-inline-start: var(--padding);
}
.alert__icon ::slotted(*) {
margin-inline-end: var(--padding) !important;
}
.alert__message {
flex: 1 1 auto;
display: block;
padding: var(--padding);
overflow: hidden;
}
@@ -89,7 +88,7 @@ export default css`
align-items: center;
color: currentColor;
font-size: var(--wa-font-size-m);
padding-inline-end: var(--padding);
padding-inline-start: var(--padding);
}
.alert__close-button:hover::part(base) {

View File

@@ -63,7 +63,8 @@ describe('<wa-alert>', () => {
afterEach(async () => {
clock?.restore();
await resetMouse();
// eslint-disable-next-line
await resetMouse().catch(() => {});
});
it('renders', async () => {
@@ -96,28 +97,30 @@ describe('<wa-alert>', () => {
expectAlertToBeInvisible(alert);
await expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, () => alert.show());
await expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, async () => await 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, () => alert.hide());
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, async () => await 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, () => {
await expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, async () => {
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, () => {
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, async () => {
alert.open = false;
await alert.updateComplete;
});
});
});
@@ -134,8 +137,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, () => {
clickOnElement(closeButton!);
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, async () => {
await clickOnElement(closeButton!);
});
});
});
@@ -159,7 +162,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>`);
expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, () => alert.toast());
await expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, async () => await alert.toast());
const toastStack = getToastStack();
expect(toastStack).to.be.visible;
expect(toastStack?.firstChild).to.be.equal(alert);
@@ -292,17 +295,15 @@ describe('<wa-alert>', () => {
});
});
});
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}`);
});
});
});
});
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}`);
}
});

View File

@@ -1,6 +1,7 @@
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';
@@ -26,7 +27,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 = styles;
static styles: CSSResultGroup = [componentStyles, styles];
static dependencies = { 'wa-icon': WaIcon };
@query('.animated-image__animated') animatedImage: HTMLImageElement;

View File

@@ -1,9 +1,6 @@
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,6 +2,7 @@ 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';
@@ -20,7 +21,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 = styles;
static styles: CSSResultGroup = [componentStyles, styles];
private animation?: Animation;
private hasStarted = false;

View File

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

View File

@@ -2,6 +2,7 @@ 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';
@@ -25,7 +26,7 @@ import type { CSSResultGroup } from 'lit';
* @cssproperty --size - The size of the avatar.
*/
export default class WaAvatar extends WebAwesomeElement {
static styles: CSSResultGroup = styles;
static styles: CSSResultGroup = [componentStyles, styles];
static dependencies = {
'wa-icon': WaIcon
};

View File

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

View File

@@ -1,6 +1,7 @@
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';
@@ -23,7 +24,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 = styles;
static styles: CSSResultGroup = [componentStyles, styles];
/** The badge's theme variant. */
@property({ reflect: true }) variant: 'brand' | 'success' | 'neutral' | 'warning' | 'danger' = 'brand';

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
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';
@@ -21,7 +22,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 = styles;
static styles: CSSResultGroup = [componentStyles, styles];
static dependencies = { 'wa-icon': WaIcon };
private readonly localize = new LocalizeController(this);

View File

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

View File

@@ -1,5 +1,6 @@
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';
@@ -15,7 +16,7 @@ import type { CSSResultGroup } from 'lit';
* @csspart base - The component's base wrapper.
*/
export default class WaButtonGroup extends WebAwesomeElement {
static styles: CSSResultGroup = styles;
static styles: CSSResultGroup = [componentStyles, styles];
@query('slot') defaultSlot: HTMLSlotElement;

View File

@@ -1,9 +1,6 @@
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 = styles;
static styles: CSSResultGroup = [componentStyles, styles];
static dependencies = {
'wa-icon': WaIcon,
'wa-spinner': WaSpinner
@@ -62,7 +62,6 @@ 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;
@@ -287,10 +286,7 @@ 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--has-label': this.hasSlotController.test('[default]'),
'button--has-prefix': this.hasSlotController.test('prefix'),
'button--has-suffix': this.hasSlotController.test('suffix')
'button--rtl': this.localize.dir() === 'rtl'
})}
?disabled=${ifDefined(isLink ? undefined : this.disabled)}
type=${ifDefined(isLink ? undefined : this.type)}

View File

@@ -1,9 +1,6 @@
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);
@@ -216,13 +213,13 @@ export default css`
pointer-events: none;
}
.button:hover:not(.button--disabled) {
.button:hover:not(.button--disabled, .button--loading) {
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:active:not(.button--disabled, .button--loading) {
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));
@@ -303,6 +300,21 @@ 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;
@@ -357,70 +369,46 @@ export default css`
* Button spacing
*/
.button--has-label.button--small .button__label {
padding: 0 var(--wa-space-s);
.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--medium .button__label {
padding: 0 var(--wa-space-m);
.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--large .button__label {
padding: 0 var(--wa-space-l);
}
.button--large {
::slotted([slot='prefix']) {
margin-inline-start: var(--wa-space-m) !important;
}
.button--has-prefix.button--small {
padding-inline-start: var(--wa-space-xs);
}
::slotted([slot='suffix']) {
margin-inline-end: var(--wa-space-m) !important;
}
.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);
.button__label {
padding: 0 var(--wa-space-l);
}
}
/*

View File

@@ -1,6 +1,7 @@
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';
@@ -31,9 +32,16 @@ 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 = styles;
static styles: CSSResultGroup = [componentStyles, styles];
private readonly hasSlotController = new HasSlotController(this, 'footer', 'header', 'image');
/** 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;
render() {
return html`
@@ -41,9 +49,9 @@ export default class WaCard extends WebAwesomeElement {
part="base"
class=${classMap({
card: true,
'card--has-footer': this.hasSlotController.test('footer'),
'card--has-image': this.hasSlotController.test('image'),
'card--has-header': this.hasSlotController.test('header')
'card--has-footer': this.withFooter,
'card--has-image': this.withImage,
'card--has-header': this.withHeader
})}
>
<slot name="image" part="image" class="card__image"></slot>

View File

@@ -1,9 +1,6 @@
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);
@@ -40,6 +37,8 @@ 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>
html`<wa-card with-header>
<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>
html`<wa-card with-footer>
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>
html`<wa-card with-image>
<img
slot="image"
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"

View File

@@ -1,4 +1,5 @@
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';
@@ -15,7 +16,7 @@ import type { CSSResultGroup } from 'lit';
*
*/
export default class WaCarouselItem extends WebAwesomeElement {
static styles: CSSResultGroup = styles;
static styles: CSSResultGroup = [componentStyles, styles];
connectedCallback() {
super.connectedCallback();

View File

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

View File

@@ -11,6 +11,7 @@ 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';
@@ -47,7 +48,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 = styles;
static styles: CSSResultGroup = [componentStyles, styles];
static dependencies = { 'wa-icon': WaIcon };
/** When set, allows the user to navigate the carousel in the same direction indefinitely. */

View File

@@ -1,9 +1,6 @@
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/shoelace.js';
import '../../../dist/webawesome.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,7 +11,8 @@ describe('<wa-carousel>', () => {
const sandbox = sinon.createSandbox();
afterEach(async () => {
await resetMouse();
// eslint-disable-next-line
await resetMouse().catch(() => {});
});
afterEach(() => {
@@ -641,7 +642,7 @@ describe('<wa-carousel>', () => {
</wa-carousel>
`);
sandbox.spy(el, 'goToSlide');
const expectedCarouselItem: HTMLElement = el.querySelector('sl-carousel-item:nth-child(2)')!;
const expectedCarouselItem: HTMLElement = el.querySelector('wa-carousel-item:nth-child(2)')!;
// Act
el.next();
@@ -668,7 +669,7 @@ describe('<wa-carousel>', () => {
<wa-carousel-item>Node 3</wa-carousel-item>
</wa-carousel>
`);
const expectedCarouselItem: HTMLElement = el.querySelector('sl-carousel-item:nth-child(1)')!;
const expectedCarouselItem: HTMLElement = el.querySelector('wa-carousel-item:nth-child(1)')!;
el.goToSlide(1);

View File

@@ -7,6 +7,7 @@ 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';
@@ -51,7 +52,7 @@ import type { WebAwesomeFormControl } from '../../internal/webawesome-element.js
*/
export default class WaCheckbox extends WebAwesomeElement implements WebAwesomeFormControl {
static styles: CSSResultGroup = styles;
static styles: CSSResultGroup = [componentStyles, styles];
static dependencies = { 'wa-icon': WaIcon };
private readonly formControlController = new FormControlController(this, {

View File

@@ -1,11 +1,6 @@
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,6 +63,7 @@ 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;
@@ -93,8 +94,10 @@ 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,6 +10,7 @@ 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';
@@ -90,7 +91,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 = styles;
static styles: CSSResultGroup = [componentStyles, styles];
static dependencies = {
'wa-button-group': WaButtonGroup,

View File

@@ -1,9 +1,6 @@
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,6 +3,7 @@ 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';
@@ -41,7 +42,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 = styles;
static styles: CSSResultGroup = [componentStyles, styles];
static dependencies = {
'wa-icon': WaIcon,
'wa-tooltip': WaTooltip

View File

@@ -1,9 +1,6 @@
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,6 +6,7 @@ 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';
@@ -47,7 +48,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 = styles;
static styles: CSSResultGroup = [componentStyles, styles];
static dependencies = {
'wa-icon': WaIcon

View File

@@ -1,9 +1,6 @@
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,7 +1,6 @@
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,6 +8,7 @@ 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,12 +65,11 @@ 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 = styles;
static styles: CSSResultGroup = [componentStyles, 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);
@@ -87,16 +86,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 even when using
* `no-header`, 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, as it is required for
* proper accessibility. If you need to display HTML, use the `label` slot instead.
*/
@property({ reflect: true }) label = '';
/**
* 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;
/** 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;
firstUpdated() {
this.dialog.hidden = !this.open;
@@ -271,7 +270,8 @@ export default class WaDialog extends WebAwesomeElement {
class=${classMap({
dialog: true,
'dialog--open': this.open,
'dialog--has-footer': this.hasSlotController.test('footer')
'dialog--with-header': this.withHeader,
'dialog--with-footer': this.withFooter
})}
>
<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.noHeader ? this.label : undefined)}
aria-labelledby=${ifDefined(!this.noHeader ? 'title' : undefined)}
aria-label=${ifDefined(this.withHeader ? undefined : this.label)}
aria-labelledby=${ifDefined(this.withHeader ? 'title' : undefined)}
tabindex="-1"
>
${!this.noHeader
${this.withHeader
? html`
<header part="header" class="dialog__header">
<h2 part="title" class="dialog__title" id="title">
@@ -313,9 +313,13 @@ export default class WaDialog extends WebAwesomeElement {
}
<div part="body" class="dialog__body" tabindex="-1"><slot></slot></div>
<footer part="footer" class="dialog__footer">
<slot name="footer"></slot>
</footer>
${this.withFooter
? html`
<footer part="footer" class="dialog__footer">
<slot name="footer"></slot>
</footer>
`
: ''}
</div>
</div>
`;

View File

@@ -1,9 +1,6 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
--width: 31rem;
--header-spacing: var(--wa-space-l);
@@ -102,10 +99,6 @@ 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>', () => {
describe('<wa-dialog with-header>', () => {
it('should be visible with the open attribute', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog with-header 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>', () => {
it('should not be visible without the open attribute', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog with-header>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>', () => {
it('should emit wa-show and wa-after-show when calling show()', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog with-header>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>', () => {
it('should emit wa-hide and wa-after-hide when calling hide()', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog with-header 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>', () => {
it('should emit wa-show and wa-after-show when setting open = true', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog with-header>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>', () => {
it('should emit wa-hide and wa-after-hide when setting open = false', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog with-header 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>', () => {
it('should not close when wa-request-close is prevented', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog with-header 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>', () => {
});
it('should allow initial focus to be set', async () => {
const el = await fixture<WaDialog>(html` <wa-dialog><input /></wa-dialog> `);
const el = await fixture<WaDialog>(html` <wa-dialog with-header><input /></wa-dialog> `);
const input = el.querySelector('input')!;
const initialFocusHandler = sinon.spy((event: Event) => {
event.preventDefault();
@@ -137,7 +137,7 @@ describe('<wa-dialog>', () => {
});
it('should close when pressing Escape', async () => {
const el = await fixture<WaDialog>(html` <wa-dialog open></wa-dialog> `);
const el = await fixture<WaDialog>(html` <wa-dialog with-header open></wa-dialog> `);
const hideHandler = sinon.spy();
el.addEventListener('wa-hide', hideHandler);
@@ -162,7 +162,7 @@ describe('<wa-dialog>', () => {
render() {
return html`
<h1>Dialog Example</h1>
<wa-dialog label="Dialog" class="dialog-overview">
<wa-dialog with-header 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>', () => {
const dialog = container.shadowRoot?.querySelector('wa-dialog');
if (!dialog) {
throw Error('Could not find <wa-dialog> element.');
throw Error('Could not find <wa-dialog with-header> element.');
}
const closeButton = dialog.shadowRoot?.querySelector('wa-icon-button');

View File

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

View File

@@ -1,9 +1,6 @@
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,7 +1,6 @@
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';
@@ -10,6 +9,7 @@ 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,10 +74,9 @@ 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 = styles;
static styles: CSSResultGroup = [componentStyles, 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);
@@ -94,36 +93,27 @@ 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 even when using
* `no-header`, 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, 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';
/**
* 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 header. */
@property({ attribute: 'with-header', type: Boolean, reflect: true }) withHeader = 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;
/** Renders the drawer with a footer. */
@property({ attribute: 'with-footer', type: Boolean, reflect: true }) withFooter = false;
firstUpdated() {
this.drawer.hidden = !this.open;
if (this.open) {
this.addOpenListeners();
if (!this.contained) {
this.modal.activate();
lockBodyScrolling(this);
}
this.modal.activate();
lockBodyScrolling(this);
}
}
@@ -151,10 +141,8 @@ export default class WaDrawer extends WebAwesomeElement {
private addOpenListeners() {
if ('CloseWatcher' in window) {
this.closeWatcher?.destroy();
if (!this.contained) {
this.closeWatcher = new CloseWatcher();
this.closeWatcher.onclose = () => this.requestClose('keyboard');
}
this.closeWatcher = new CloseWatcher();
this.closeWatcher.onclose = () => this.requestClose('keyboard');
} else {
document.addEventListener('keydown', this.handleDocumentKeyDown);
this.closeWatcher?.destroy();
@@ -166,11 +154,6 @@ 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');
@@ -184,12 +167,8 @@ export default class WaDrawer extends WebAwesomeElement {
this.emit('wa-show');
this.addOpenListeners();
this.originalTrigger = document.activeElement as HTMLElement;
// Lock body scrolling only if the drawer isn't contained
if (!this.contained) {
this.modal.activate();
lockBodyScrolling(this);
}
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 })`
@@ -238,11 +217,8 @@ export default class WaDrawer extends WebAwesomeElement {
// Hide
this.emit('wa-hide');
this.removeOpenListeners();
if (!this.contained) {
this.modal.deactivate();
unlockBodyScrolling(this);
}
this.modal.deactivate();
unlockBodyScrolling(this);
await Promise.all([stopAnimations(this.drawer), stopAnimations(this.overlay)]);
const panelAnimation = getAnimation(this, `drawer.hide${uppercaseFirstLetter(this.placement)}`, {
@@ -278,19 +254,6 @@ 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) {
@@ -322,10 +285,9 @@ 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--has-footer': this.hasSlotController.test('footer')
'drawer--with-header': this.withHeader,
'drawer--with-footer': this.withFooter
})}
>
<div part="overlay" class="drawer__overlay" @click=${() => this.requestClose('overlay')} tabindex="-1"></div>
@@ -336,11 +298,11 @@ export default class WaDrawer extends WebAwesomeElement {
role="dialog"
aria-modal="true"
aria-hidden=${this.open ? 'false' : 'true'}
aria-label=${ifDefined(this.noHeader ? this.label : undefined)}
aria-labelledby=${ifDefined(!this.noHeader ? 'title' : undefined)}
aria-label=${ifDefined(this.withHeader ? undefined : this.label)}
aria-labelledby=${ifDefined(this.withHeader ? 'title' : undefined)}
tabindex="0"
>
${!this.noHeader
${this.withHeader
? html`
<header part="header" class="drawer__header">
<h2 part="title" class="drawer__title" id="title">
@@ -366,9 +328,13 @@ export default class WaDrawer extends WebAwesomeElement {
<slot part="body" class="drawer__body"></slot>
<footer part="footer" class="drawer__footer">
<slot name="footer"></slot>
</footer>
${this.withFooter
? html`
<footer part="footer" class="drawer__footer">
<slot name="footer"></slot>
</footer>
`
: ''}
</div>
</div>
`;

View File

@@ -1,20 +1,18 @@
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%;
@@ -23,16 +21,6 @@ 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;
@@ -40,7 +28,7 @@ export default css`
z-index: 2;
max-width: 100%;
max-height: 100%;
background-color: var(--panel-background);
background-color: var(--wa-color-surface-raised);
box-shadow: var(--wa-shadow-level-3);
overflow: auto;
pointer-events: all;
@@ -133,10 +121,6 @@ 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;
@@ -148,10 +132,6 @@ 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,6 +7,7 @@ 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';
@@ -40,7 +41,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 = styles;
static styles: CSSResultGroup = [componentStyles, styles];
static dependencies = { 'wa-popup': WaPopup };
@query('.dropdown') popup: WaPopup;

View File

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

View File

@@ -2,6 +2,7 @@ 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';
@@ -21,7 +22,7 @@ import type { CSSResultGroup } from 'lit';
* @csspart base - The component's base wrapper.
*/
export default class WaIconButton extends WebAwesomeElement {
static styles: CSSResultGroup = styles;
static styles: CSSResultGroup = [componentStyles, styles];
static dependencies = { 'wa-icon': WaIcon };
@query('.icon-button') button: HTMLButtonElement | HTMLLinkElement;

View File

@@ -1,9 +1,6 @@
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,6 +3,7 @@ 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';
@@ -33,7 +34,7 @@ interface IconSource {
* @csspart use - The <use> element generated when using `spriteSheet: true`
*/
export default class WaIcon extends WebAwesomeElement {
static styles: CSSResultGroup = styles;
static styles: CSSResultGroup = [componentStyles, styles];
private initialRender = false;

View File

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

View File

@@ -6,6 +6,7 @@ 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';
@@ -35,7 +36,7 @@ import type { CSSResultGroup } from 'lit';
* @cssproperty --handle-size - The size of the compare handle.
*/
export default class WaImageComparer extends WebAwesomeElement {
static styles: CSSResultGroup = styles;
static styles: CSSResultGroup = [componentStyles, styles];
static scopedElement = { 'wa-icon': WaIcon };
private readonly localize = new LocalizeController(this);

View File

@@ -1,9 +1,6 @@
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,6 +2,7 @@ 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';
@@ -16,7 +17,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 = styles;
static styles: CSSResultGroup = [componentStyles, 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,9 +1,6 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
export default css`
${componentStyles}
:host {
display: block;
}

View File

@@ -8,6 +8,8 @@ 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';
@@ -56,7 +58,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 = styles;
static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];
static dependencies = { 'wa-icon': WaIcon };
private readonly formControlController = new FormControlController(this, {

View File

@@ -1,11 +1,6 @@
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,10 +1,11 @@
import { classMap } from 'lit/directives/class-map.js';
import { getTextContent, HasSlotController } from '../../internal/slot.js';
import { getTextContent } 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';
@@ -38,7 +39,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 = styles;
static styles: CSSResultGroup = [componentStyles, styles];
static dependencies = {
'wa-icon': WaIcon,
'wa-popup': WaPopup,
@@ -66,8 +67,7 @@ export default class WaMenuItem extends WebAwesomeElement {
@property({ type: Boolean, reflect: true }) disabled = false;
private readonly localize = new LocalizeController(this);
private readonly hasSlotController = new HasSlotController(this, 'submenu');
private submenuController: SubmenuController = new SubmenuController(this, this.hasSlotController, this.localize);
private submenuController: SubmenuController = new SubmenuController(this, this.localize);
connectedCallback() {
super.connectedCallback();
@@ -149,7 +149,7 @@ export default class WaMenuItem extends WebAwesomeElement {
}
isSubmenu() {
return this.hasSlotController.test('submenu');
return this.querySelector(`:scope > [slot="submenu"]`) !== null;
}
render() {
@@ -191,7 +191,7 @@ export default class WaMenuItem extends WebAwesomeElement {
></wa-icon>
</span>
${this.submenuController.renderSubmenu()} ${this.loading ? html`<sl-spinner part="spinner"></sl-spinner>` : ''}
${this.submenuController.renderSubmenu()} ${this.loading ? html`<wa-spinner part="spinner"></wa-spinner>` : ''}
</div>
`;
}

View File

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

View File

@@ -1,5 +1,4 @@
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';
@@ -14,22 +13,20 @@ 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,
hasSlotController: HasSlotController,
localize: LocalizeController
) {
constructor(host: ReactiveControllerHost & WaMenuItem, 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.hasSlotController.test('submenu') && !this.host.disabled) {
if (this.hasSubmenu() && !this.host.disabled) {
this.addListeners();
}
}
@@ -39,7 +36,7 @@ export class SubmenuController implements ReactiveController {
}
hostUpdated() {
if (this.hasSlotController.test('submenu') && !this.host.disabled) {
if (this.hasSubmenu() && !this.host.disabled) {
this.addListeners();
this.updateSkidding();
} else {
@@ -93,7 +90,7 @@ export class SubmenuController implements ReactiveController {
};
private handleMouseOver = () => {
if (this.hasSlotController.test('submenu')) {
if (this.hasSubmenu()) {
this.enableSubmenu();
}
};

View File

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

View File

@@ -1,9 +1,6 @@
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,9 +1,11 @@
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;
}
@@ -19,7 +21,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 = styles;
static styles: CSSResultGroup = [componentStyles, styles];
@query('slot') defaultSlot: HTMLSlotElement;

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