Compare commits

..

1 Commits

Author SHA1 Message Date
Cory LaViska
23b1fb8984 unset last focused item; #1436 2023-07-12 11:19:55 -04:00
236 changed files with 12777 additions and 14739 deletions

View File

@@ -3,7 +3,8 @@ name: Bug Report
about: Create a bug report to help us fix a demonstrable problem with code in the library.
title: ''
labels: bug
assignees:
assignees: claviska
---
### Describe the bug

View File

@@ -3,6 +3,8 @@ name: Feature Request
about: Suggest an idea for this project.
title: ''
labels: feature
assignees: claviska
---
### What issue are you having?

View File

@@ -51,7 +51,6 @@
"erroneou",
"errormessage",
"esbuild",
"exportmaps",
"exportparts",
"fieldsets",
"formaction",
@@ -99,7 +98,6 @@
"minlength",
"monospace",
"mousedown",
"mousemove",
"mouseup",
"multiselectable",
"nextjs",
@@ -153,7 +151,6 @@
"tinycolor",
"transitionend",
"treeitem",
"treeshaking",
"Triaging",
"turbolinks",
"typeof",

View File

@@ -1,5 +1,4 @@
import * as path from 'path';
import { customElementVsCodePlugin } from 'custom-element-vs-code-integration';
import { generateCustomData } from 'cem-plugin-vs-code-custom-data-generator';
import { parse } from 'comment-parser';
import { pascalCase } from 'pascal-case';
import commandLineArgs from 'command-line-args';
@@ -27,7 +26,7 @@ function replace(string, terms) {
}
export default {
globs: ['src/components/**/*.component.ts'],
globs: ['src/components/**/*.ts'],
exclude: ['**/*.styles.ts', '**/*.test.ts'],
plugins: [
// Append package data
@@ -37,34 +36,7 @@ export default {
customElementsManifest.package = { name, description, version, author, homepage, license };
}
},
// Infer tag names because we no longer use @customElement decorators.
{
name: 'shoelace-infer-tag-names',
analyzePhase({ ts, node, moduleDoc }) {
switch (node.kind) {
case ts.SyntaxKind.ClassDeclaration: {
const className = node.name.getText();
const classDoc = moduleDoc?.declarations?.find(declaration => declaration.name === className);
const importPath = moduleDoc.path;
// This is kind of a best guess at components. "thing.component.ts"
if (!importPath.endsWith('.component.ts')) {
return;
}
const tagNameWithoutPrefix = path.basename(importPath, '.component.ts');
const tagName = 'sl-' + tagNameWithoutPrefix;
classDoc.tagNameWithoutPrefix = tagNameWithoutPrefix;
classDoc.tagName = tagName;
// This used to be set to true by @customElement
classDoc.customElement = true;
}
}
}
},
// Parse custom jsDoc tags
{
name: 'shoelace-custom-tags',
@@ -86,9 +58,6 @@ export default {
});
});
// This is what allows us to map JSDOC comments to ReactWrappers.
classDoc['jsDoc'] = node.jsDoc?.map(jsDoc => jsDoc.getFullText()).join('\n');
const parsed = parse(`${customComments}\n */`);
parsed[0].tags?.forEach(t => {
switch (t.tag) {
@@ -147,7 +116,6 @@ export default {
if (classDoc?.events) {
classDoc.events.forEach(event => {
event.reactName = `on${pascalCase(event.name)}`;
event.eventName = `${pascalCase(event.name)}Event`;
});
}
}
@@ -169,7 +137,7 @@ export default {
//
const terms = [
{ from: /^src\//, to: '' }, // Strip the src/ prefix
{ from: /\.component.(t|j)sx?$/, to: '.js' } // Convert .ts to .js
{ from: /\.(t|j)sx?$/, to: '.js' } // Convert .ts to .js
];
mod.path = replace(mod.path, terms);
@@ -191,15 +159,9 @@ export default {
}
},
// Generate custom VS Code data
customElementVsCodePlugin({
generateCustomData({
outdir,
cssFileName: null,
referencesTemplate: (_, tag) => [
{
name: 'Documentation',
url: `https://shoelace.style/components/${tag.replace('sl-', '')}`
}
]
cssFileName: null
})
]
};

View File

@@ -84,7 +84,7 @@
<p>
To import this component as a <a href="/frameworks/react">React component</a>:
</p>
<pre><code class="language-js">import {{ component.name }} from '@shoelace-style/shoelace/{{ meta.npmdir }}/react/{{ component.tagNameWithoutPrefix }}';</code></pre>
<pre><code class="language-js">import { {{ component.name }} } from '@shoelace-style/shoelace/{{ meta.npmdir }}/react';</code></pre>
</sl-tab-panel>
</sl-tab-group>

View File

@@ -1,9 +1,9 @@
(() => {
function convertModuleLinks(html) {
html = html
.replace(/@shoelace-style\/shoelace/g, `https://esm.sh/@shoelace-style/shoelace@${shoelaceVersion}`)
.replace(/from 'react'/g, `from 'https://esm.sh/react@${reactVersion}'`)
.replace(/from "react"/g, `from "https://esm.sh/react@${reactVersion}"`);
.replace(/@shoelace-style\/shoelace/g, `https://cdn.skypack.dev/@shoelace-style/shoelace@${shoelaceVersion}`)
.replace(/from 'react'/g, `from 'https://cdn.skypack.dev/react@${reactVersion}'`)
.replace(/from "react"/g, `from "https://cdn.skypack.dev/react@${reactVersion}"`);
return html;
}
@@ -191,12 +191,12 @@
if (isReact) {
htmlTemplate = '<div id="root"></div>';
jsTemplate =
`import React from 'https://esm.sh/react@${reactVersion}';\n` +
`import ReactDOM from 'https://esm.sh/react-dom@${reactVersion}';\n` +
`import { setBasePath } from 'https://esm.sh/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/utilities/base-path';\n` +
`import React from 'https://cdn.skypack.dev/react@${reactVersion}';\n` +
`import ReactDOM from 'https://cdn.skypack.dev/react-dom@${reactVersion}';\n` +
`import { setBasePath } from 'https://cdn.skypack.dev/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/utilities/base-path';\n` +
`\n` +
`// Set the base path for Shoelace assets\n` +
`setBasePath('https://esm.sh/@shoelace-style/shoelace@${shoelaceVersion}/${npmdir}/')\n` +
`setBasePath('https://cdn.skypack.dev/@shoelace-style/shoelace@${shoelaceVersion}/${npmdir}/')\n` +
`\n${convertModuleLinks(reactExample)}\n` +
`\n` +
`ReactDOM.render(<App />, document.getElementById('root'));`;

View File

@@ -58,15 +58,15 @@
const clearButton = siteSearch.querySelector('.search__clear-button');
const results = siteSearch.querySelector('.search__results');
const version = document.documentElement.getAttribute('data-shoelace-version');
const key = `search_${version}`;
const searchDebounce = 50;
const animationDuration = 150;
const searchDebounce = 50;
let isShowing = false;
let searchTimeout;
let searchIndex;
let map;
const loadSearchIndex = new Promise(resolve => {
const key = `search_${version}`;
const cache = localStorage.getItem(key);
const wait = 'requestIdleCallback' in window ? requestIdleCallback : requestAnimationFrame;
@@ -284,7 +284,7 @@
const a = document.createElement('a');
const displayTitle = page.title ?? '';
const displayDescription = page.description ?? '';
const displayUrl = page.url.replace(/^\//, '').replace(/\/$/, '');
const displayUrl = page.url.replace(/^\//, '');
let icon = 'file-text';
a.setAttribute('role', 'option');
@@ -357,13 +357,6 @@
}
});
// 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);

View File

@@ -180,10 +180,7 @@ p {
img {
max-width: 100%;
}
.badges img {
border-radius: var(--sl-border-radius-medium);
border-radius: var(--docs-border-radius);
}
.callout img,
@@ -237,7 +234,6 @@ kbd {
border: solid 1px var(--sl-color-neutral-200);
box-shadow: inset 0 1px 0 0 var(--sl-color-neutral-0), inset 0 -1px 0 0 var(--sl-color-neutral-200);
font-family: var(--sl-font-mono);
font-size: 0.9125em;
border-radius: var(--docs-border-radius);
color: var(--sl-color-neutral-800);
padding: 0.125em 0.4em;
@@ -394,10 +390,6 @@ table td p:last-child {
overflow-x: auto;
}
.table-scroll code {
white-space: nowrap;
}
th.table-name,
th.table-event-detail {
min-width: 15ch;
@@ -1066,29 +1058,23 @@ html.sidebar-open #menu-toggle {
/* Hide when not defined to prevent extra wide icon toolbar while loading */
display: none;
}
#theme-selector sl-menu {
/* Set an initial size to prevent width being too small when first opening on small screen width */
/* Set an initial size to prevent width being initally too small when first opening on small screen width */
width: 140px;
}
#theme-selector sl-button {
transition: 250ms scale ease;
}
#theme-selector sl-button::part(base) {
color: var(--sl-color-neutral-0);
}
#theme-selector sl-button::part(label) {
display: flex;
padding: 0.5rem;
}
#theme-selector sl-icon {
font-size: 1.25rem;
}
.sl-theme-dark #theme-selector sl-button::part(base) {
color: var(--sl-color-neutral-1000);
}

View File

@@ -171,10 +171,7 @@ module.exports = function (eleventyConfig) {
this.field('c'); // content
results.forEach((result, index) => {
const url = path
.join('/', path.relative(eleventyConfig.dir.output, result.outputPath))
.replace(/\\/g, '/') // convert backslashes to forward slashes
.replace(/\/index.html$/, '/'); // convert trailing /index.html to /
const url = path.join('/', path.relative(eleventyConfig.dir.output, result.outputPath)).replace(/\\/g, '/');
const doc = new JSDOM(result.content, {
// We must set a default URL so links are parsed with a hostname. Let's use a bogus TLD so we can easily
// identify which ones are internal and which ones are external.

View File

@@ -13,8 +13,7 @@ layout: component
```
```jsx:react
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlAlert, SlIcon } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlAlert open>
@@ -75,8 +74,7 @@ Set the `variant` attribute to change the alert's variant.
```
```jsx:react
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlAlert, SlIcon } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -146,8 +144,7 @@ Add the `closable` attribute to show a close button that will hide the alert.
```jsx:react
import { useState } from 'react';
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlAlert, SlIcon } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(true);
@@ -175,7 +172,7 @@ Icons are optional. Simply omit the `icon` slot if you don't want them.
```
```jsx:react
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
import { SlAlert } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlAlert variant="primary" open>
@@ -215,9 +212,7 @@ Set the `duration` attribute to automatically hide an alert after a period of ti
```jsx:react
import { useState } from 'react';
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlAlert, SlButton, SlIcon } from '@shoelace-style/shoelace/dist/react';
const css = `
.alert-duration sl-alert {
@@ -306,9 +301,7 @@ You should always use the `closable` attribute so users can dismiss the notifica
```jsx:react
import { useRef } from 'react';
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlAlert, SlButton, SlIcon } from '@shoelace-style/shoelace/dist/react';
function showToast(alert) {
alert.toast();

View File

@@ -13,7 +13,7 @@ layout: component
```
```jsx:react
import SlAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';
import { SlAnimatedImage } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlAnimatedImage
@@ -41,7 +41,7 @@ Both GIF and WEBP images are supported.
```
```jsx:react
import SlAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';
import { SlAnimatedImage } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlAnimatedImage src="https://shoelace.style/assets/images/tie.webp" alt="Animation of a shoe being tied" />
@@ -64,7 +64,7 @@ To set a custom size, apply a width and/or height to the host element.
{% raw %}
```jsx:react
import SlAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';
import { SlAnimatedImage } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlAnimatedImage
@@ -102,7 +102,7 @@ You can change the appearance and location of the control box by targeting the `
```
```jsx:react
import SlAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';
import { SlAnimatedImage } from '@shoelace-style/shoelace/dist/react';
const css = `
.animated-image-custom-control-box::part(control-box) {

View File

@@ -27,7 +27,7 @@ To animate an element, wrap it in `<sl-animation>` and set an animation `name`.
```
```jsx:react
import SlAnimation from '@shoelace-style/shoelace/dist/react/animation';
import { SlAnimation } from '@shoelace-style/shoelace/dist/react';
const css = `
.animation-overview .box {
@@ -173,7 +173,7 @@ Use an [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/
```jsx:react
import { useEffect, useRef, useState } from 'react';
import SlAnimation from '@shoelace-style/shoelace/dist/react/animation';
import { SlAnimation } from '@shoelace-style/shoelace/dist/react';
const css = `
.animation-scroll {
@@ -262,7 +262,7 @@ Supply your own [keyframe formats](https://developer.mozilla.org/en-US/docs/Web/
```
```jsx:react
import SlAnimation from '@shoelace-style/shoelace/dist/react/animation';
import { SlAnimation } from '@shoelace-style/shoelace/dist/react';
const css = `
.animation-keyframes .box {
@@ -329,8 +329,7 @@ Animations won't play until you apply the `play` attribute. You can omit it init
```jsx:react
import { useState } from 'react';
import SlAnimation from '@shoelace-style/shoelace/dist/react/animation';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import { SlAnimation, SlButton } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [play, setPlay] = useState(false);

View File

@@ -12,7 +12,7 @@ By default, a generic icon will be shown. You can personalize avatars by adding
```
```jsx:react
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
import { SlAvatar } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlAvatar label="User avatar" />;
```
@@ -37,7 +37,7 @@ Avatar images can be lazily loaded by setting the `loading` attribute to `lazy`.
```
```jsx:react
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
import { SlAvatar } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlAvatar
@@ -61,7 +61,7 @@ When you don't have an image to use, you can set the `initials` attribute to sho
```
```jsx:react
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
import { SlAvatar } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlAvatar initials="SL" label="Avatar with initials: SL" />;
```
@@ -85,8 +85,7 @@ When no image or initials are set, an icon will be shown. The default avatar sho
```
```jsx:react
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlAvatar, SlIcon } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -116,8 +115,7 @@ Avatars can be shaped using the `shape` attribute.
```
```jsx:react
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlAvatar, SlIcon } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -167,8 +165,7 @@ You can group avatars with a few lines of CSS.
```
```jsx:react
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlAvatar, SlIcon } from '@shoelace-style/shoelace/dist/react';
const css = `
.avatar-group sl-avatar:not(:first-of-type) {

View File

@@ -10,7 +10,7 @@ layout: component
```
```jsx:react
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
import { SlBadge } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlBadge>Badge</SlBadge>;
```
@@ -30,7 +30,7 @@ Set the `variant` attribute to change the badge's variant.
```
```jsx:react
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
import { SlBadge } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -56,7 +56,7 @@ Use the `pill` attribute to give badges rounded edges.
```
```jsx:react
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
import { SlBadge } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -100,7 +100,7 @@ Use the `pulse` attribute to draw attention to the badge with a subtle animation
```
```jsx:react
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
import { SlBadge } from '@shoelace-style/shoelace/dist/react';
const css = `
.badge-pulse sl-badge:not(:last-of-type) {
@@ -157,8 +157,7 @@ One of the most common use cases for badges is attaching them to buttons. To mak
{% raw %}
```jsx:react
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import { SlBadge, SlButton } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -201,11 +200,7 @@ When including badges in menu items, use the `suffix` slot to make sure they're
{% raw %}
```jsx:react
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import SlMenuLabel from '@shoelace-style/shoelace/dist/react/menu-label';
import { SlBadge, SlButton, SlMenu, SlMenuItem, SlMenuLabel } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlMenu

View File

@@ -17,9 +17,7 @@ layout: component
```
```jsx:react
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlBreadcrumb, SlBreadcrumbItem, SlIcon } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlBreadcrumb>

View File

@@ -17,8 +17,7 @@ Breadcrumbs are usually placed before a page's main content with the current pag
```
```jsx:react
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
import { SlBreadcrumb, SlBreadcrumbItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlBreadcrumb>
@@ -51,8 +50,7 @@ For websites, you'll probably want to use links instead. You can make any breadc
```
```jsx:react
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
import { SlBreadcrumb, SlBreadcrumbItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlBreadcrumb>
@@ -100,8 +98,7 @@ Use the `separator` slot to change the separator that goes between breadcrumb it
```jsx:react
import '@shoelace-style/shoelace/dist/components/icon/icon.js';
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
import { SlBreadcrumb, SlBreadcrumbItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -149,9 +146,7 @@ Use the `prefix` slot to add content before any breadcrumb item.
```
```jsx:react
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlBreadcrumb, SlBreadcrumbItem, SlIcon } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlBreadcrumb>
@@ -181,9 +176,7 @@ Use the `suffix` slot to add content after any breadcrumb item.
```
```jsx:react
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlBreadcrumb, SlBreadcrumbItem, SlIcon } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlBreadcrumb>

View File

@@ -14,8 +14,7 @@ layout: component
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
import { SlButton, SlButtonGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlButtonGroup label="Alignment">
@@ -57,8 +56,7 @@ All button sizes are supported, but avoid mixing sizes within the same button gr
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
import { SlButton, SlButtonGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -134,8 +132,7 @@ Theme buttons are supported through the button's `variant` attribute.
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
import { SlButton, SlButtonGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -213,8 +210,7 @@ Pill buttons are supported through the button's `pill` attribute.
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
import { SlButton, SlButtonGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -283,11 +279,7 @@ Dropdowns can be placed inside button groups as long as the trigger is an `<sl-b
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlButton, SlButtonGroup, SlDropdown, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlButtonGroup label="Example Button Group">
@@ -328,11 +320,7 @@ Create a split button using a button and a dropdown. Use a [visually hidden](/co
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlButton, SlButtonGroup, SlDropdown, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlButtonGroup label="Example Button Group">
@@ -370,9 +358,7 @@ Buttons can be wrapped in tooltips to provide more detail when the user interact
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
import SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';
import { SlButton, SlButtonGroup, SlTooltip } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -441,10 +427,7 @@ Create interactive toolbars with button groups.
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';
import { SlButton, SlButtonGroup, SlIcon, SlTooltip } from '@shoelace-style/shoelace/dist/react';
const css = `
.button-group-toolbar sl-button-group:not(:last-of-type) {

View File

@@ -10,7 +10,7 @@ layout: component
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import { SlButton } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlButton>Button</SlButton>;
```
@@ -31,7 +31,7 @@ Use the `variant` attribute to set the button's variant.
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import { SlButton } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -56,7 +56,7 @@ Use the `size` attribute to change a button's size.
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import { SlButton } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -81,7 +81,7 @@ Use the `outline` attribute to draw outlined buttons with transparent background
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import { SlButton } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -118,7 +118,7 @@ Use the `pill` attribute to give buttons rounded edges.
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import { SlButton } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -154,8 +154,7 @@ Use the `circle` attribute to create circular icon buttons. When this attribute
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlButton, SlIcon } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -183,7 +182,7 @@ Use the `text` variant to create text buttons that share the same size as regula
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import { SlButton } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -212,7 +211,7 @@ It's often helpful to have a button that works like a link. This is possible by
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import { SlButton } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -247,7 +246,7 @@ As expected, buttons can be given a custom width by setting the `width` attribut
{% raw %}
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import { SlButton } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -325,8 +324,7 @@ Use the `prefix` and `suffix` slots to add icons.
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlButton, SlIcon } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -398,7 +396,7 @@ Use the `caret` attribute to add a dropdown indicator when a button will trigger
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import { SlButton } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -429,7 +427,7 @@ Use the `loading` attribute to make a button busy. The width will remain the sam
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import { SlButton } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -457,7 +455,7 @@ const App = () => (
### Disabled
Use the `disabled` attribute to disable a button.
Use the `disabled` attribute to disable a button. Clicks will be suppressed until the disabled state is removed.
```html:preview
<sl-button variant="default" disabled>Default</sl-button>
@@ -469,7 +467,7 @@ Use the `disabled` attribute to disable a button.
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import { SlButton } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>

View File

@@ -41,9 +41,7 @@ layout: component
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlCard from '@shoelace-style/shoelace/dist/react/card';
import SlRating from '@shoelace-style/shoelace/dist/react/rating';
import { SlButton, SlCard, SlRating } from '@shoelace-style/shoelace/dist/react';
const css = `
.card-overview {
@@ -106,7 +104,7 @@ Basic cards aren't very exciting, but they can display any content you want them
```
```jsx:react
import SlCard from '@shoelace-style/shoelace/dist/react/card';
import { SlCard } from '@shoelace-style/shoelace/dist/react';
const css = `
.card-basic {
@@ -161,8 +159,7 @@ Headers can be used to display titles and more.
```
```jsx:react
import SlCard from '@shoelace-style/shoelace/dist/react/card';
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
import { SlCard, SlIconButton } from '@shoelace-style/shoelace/dist/react';
const css = `
.card-header {
@@ -227,9 +224,7 @@ Footers can be used to display actions, summaries, or other relevant content.
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlCard from '@shoelace-style/shoelace/dist/react/card';
import SlRating from '@shoelace-style/shoelace/dist/react/rating';
import { SlButton, SlCard, SlRating } from '@shoelace-style/shoelace/dist/react';
const css = `
.card-footer {
@@ -282,7 +277,7 @@ Cards accept an `image` slot. The image is displayed atop the card and stretches
```
```jsx:react
import SlCard from '@shoelace-style/shoelace/dist/react/card';
import { SlCard } from '@shoelace-style/shoelace/dist/react';
const css = `
.card-image {

View File

@@ -41,8 +41,7 @@ layout: component
```
```jsx:react
import SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';
import SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlCarousel pagination>

View File

@@ -41,8 +41,7 @@ layout: component
```
```jsx:react
import SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';
import SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -124,8 +123,7 @@ Use the `pagination` attribute to show the total number of slides and the curren
```
```jsx:react
import SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';
import SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlCarousel pagination>
@@ -203,8 +201,7 @@ Use the `navigation` attribute to show previous and next buttons.
```
```jsx:react
import SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';
import SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlCarousel navigation>
@@ -282,8 +279,7 @@ By default, the carousel will not advanced beyond the first and last slides. You
```
```jsx:react
import SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';
import SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlCarousel loop navigation pagination>
@@ -361,8 +357,7 @@ The carousel will automatically advance when the `autoplay` attribute is used. T
```
```jsx:react
import SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';
import SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlCarousel autoplay loop pagination>
@@ -459,10 +454,7 @@ This example is best demonstrated using a mouse. Try clicking and dragging the s
```jsx:react
import { useState } from 'react';
import SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';
import SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import SlSwitch from '@shoelace-style/shoelace/dist/react/switch';
import { SlCarousel, SlCarouselItem, SlDivider, SlSwitch } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [isEnabled, setIsEnabled] = useState(false);
@@ -530,8 +522,7 @@ The `slides-per-page` attribute makes it possible to display multiple slides at
{% raw %}
```jsx:react
import SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';
import SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlCarousel navigation pagination slidesPerPage={2} slidesPerMove={2}>
@@ -623,8 +614,7 @@ The content of the carousel can be changed by adding or removing carousel items.
```jsx:react
import { useState } from 'react';
import SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';
import SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
const css = `
.dynamic-carousel {
@@ -740,8 +730,7 @@ Setting the `orientation` attribute to `vertical` will render the carousel in a
```
```jsx:react
import SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';
import SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
const css = `
.vertical {
@@ -863,11 +852,7 @@ Use the `--aspect-ratio` custom property to customize the size of the carousel's
```jsx:react
import { useState } from 'react';
import SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';
import SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import SlOption from '@shoelace-style/shoelace/dist/react/option';
import { SlCarousel, SlCarouselItem, SlDivider, SlSelect, SlOption } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [aspectRatio, setAspectRatio] = useState('3/2');
@@ -971,10 +956,7 @@ Use the `--scroll-hint` custom property to add inline padding in horizontal caro
```jsx:react
import { useState } from 'react';
import SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';
import SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import SlRange from '@shoelace-style/shoelace/dist/react/range';
import { SlCarousel, SlCarouselItem, SlDivider, SlRange } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -1137,10 +1119,7 @@ The carousel has a robust API that makes it possible to extend and customize. Th
```jsx:react
import { useRef } from 'react';
import SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';
import SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import SlRange from '@shoelace-style/shoelace/dist/react/range';
import { SlCarousel, SlCarouselItem, SlDivider, SlRange } from '@shoelace-style/shoelace/dist/react';
const css = `
.carousel-thumbnails {

View File

@@ -10,7 +10,7 @@ layout: component
```
```jsx:react
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
import { SlCheckbox } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlCheckbox>Checkbox</SlCheckbox>;
```
@@ -30,7 +30,7 @@ Use the `checked` attribute to activate the checkbox.
```
```jsx:react
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
import { SlCheckbox } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlCheckbox checked>Checked</SlCheckbox>;
```
@@ -44,7 +44,7 @@ Use the `indeterminate` attribute to make the checkbox indeterminate.
```
```jsx:react
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
import { SlCheckbox } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlCheckbox indeterminate>Indeterminate</SlCheckbox>;
```
@@ -58,7 +58,7 @@ Use the `disabled` attribute to disable the checkbox.
```
```jsx:react
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
import { SlCheckbox } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlCheckbox disabled>Disabled</SlCheckbox>;
```
@@ -76,7 +76,7 @@ Use the `size` attribute to change a checkbox's size.
```
```jsx:react
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
import { SlCheckbox } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -127,8 +127,7 @@ Use the `setCustomValidity()` method to set a custom validation message. This wi
```jsx:react
import { useEffect, useRef } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
import { SlButton, SlCheckbox } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const checkbox = useRef(null);

View File

@@ -10,7 +10,7 @@ layout: component
```
```jsx:react
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlColorPicker label="Select a color" />;
```
@@ -30,7 +30,7 @@ Use the `value` attribute to set an initial value for the color picker.
```
```jsx:react
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlColorPicker value="#4a90e2" label="Select a color" />;
```
@@ -44,7 +44,7 @@ Use the `opacity` attribute to enable the opacity slider. When this is enabled,
```
```jsx:react
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlColorPicker opacity label="Select a color" />;
```
@@ -63,7 +63,7 @@ To prevent users from toggling the format themselves, add the `no-format-toggle`
```
```jsx:react
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -90,7 +90,7 @@ Use the `swatches` attribute to add convenient presets to the color picker. Any
```
```jsx:react
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlColorPicker
@@ -114,7 +114,7 @@ Use the `size` attribute to change the color picker's trigger size.
```
```jsx:react
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -134,7 +134,7 @@ The color picker can be rendered inline instead of in a dropdown using the `inli
```
```jsx:react
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlColorPicker inline label="Select a color" />;
```

View File

@@ -1,258 +0,0 @@
---
meta:
title: Copy Button
description: Copies data to the clipboard when the user clicks the button.
layout: component
---
```html:preview
<sl-copy-button value="Shoelace rocks!"></sl-copy-button>
```
```jsx:react
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
const App = () => (
<SlCopyButton value="Shoelace rocks!" />
);
```
## Examples
### Custom Labels
Copy Buttons display feedback in a tooltip. You can customize the labels using the `copy-label`, `success-label`, and `error-label` attributes.
```html:preview
<sl-copy-button
value="Custom labels are easy"
copy-label="Click to copy"
success-label="You did it!"
error-label="Whoops, your browser doesn't support this!"
></sl-copy-button>
```
```jsx:react
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
const App = () => (
<SlCopyButton
value="Custom labels are easy"
copy-label="Click to copy"
success-label="You did it!"
error-label="Whoops, your browser doesn't support this!"
/>
);
```
### Custom Icons
Use the `copy-icon`, `success-icon`, and `error-icon` slots to customize the icons that get displayed for each state. You can use [`<sl-icon>`](/components/icon) or your own images.
```html:preview
<sl-copy-button value="Copied from a custom button">
<sl-icon slot="copy-icon" name="clipboard"></sl-icon>
<sl-icon slot="success-icon" name="clipboard-check"></sl-icon>
<sl-icon slot="error-icon" name="clipboard-x"></sl-icon>
</sl-copy-button>
```
```jsx:react
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
import { SlIcon } from '@shoelace-style/shoelace/dist/react/icon';
const App = () => (
<>
<SlCopyButton value="Copied from a custom button">
<SlIcon slot="copy-icon" name="clipboard" />
<SlIcon slot="success-icon" name="clipboard-check" />
<SlIcon slot="error-icon" name="clipboard-x" />
</SlCopyButton>
</>
);
```
### Copying Values From Other Elements
Normally, the data that gets copied will come from the component's `value` attribute, but you can copy data from any element within the same document by providing its `id` to the `from` attribute.
When using the `from` attribute, the element's [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) will be copied by default. Passing an attribute or property modifier will let you copy data from one of the element's attributes or properties instead.
To copy data from an attribute, use `from="id[attr]"` where `id` is the id of the target element and `attr` is the name of the attribute you'd like to copy. To copy data from a property, use `from="id.prop"` where `id` is the id of the target element and `prop` is the name of the property you'd like to copy.
```html:preview
<!-- Copies the span's textContent -->
<span id="my-phone">+1 (234) 456-7890</span>
<sl-copy-button from="my-phone"></sl-copy-button>
<br><br>
<!-- Copies the input's "value" property -->
<sl-input id="my-input" type="text" value="User input" style="display: inline-block; max-width: 300px;"></sl-input>
<sl-copy-button from="my-input.value"></sl-copy-button>
<br><br>
<!-- Copies the link's "href" attribute -->
<a id="my-link" href="https://shoelace.style/">Shoelace Website</a>
<sl-copy-button from="my-link[href]"></sl-copy-button>
```
```jsx:react
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
import { SlInput } from '@shoelace-style/shoelace/dist/react/input';
const App = () => (
<>
{/* Copies the span's textContent */}
<span id="my-phone">+1 (234) 456-7890</span>
<SlCopyButton from="my-phone" />
<br /><br />
{/* Copies the input's "value" property */}
<SlInput id="my-input" type="text" />
<SlCopyButton from="my-input.value" />
<br /><br />
{/* Copies the link's "href" attribute */}
<a id="my-link" href="https://shoelace.style/">Shoelace Website</a>
<SlCopyButton from="my-link[href]" />
</>
);
```
### Handling Errors
A copy error will occur if the value is an empty string, if the `from` attribute points to an id that doesn't exist, or if the browser rejects the operation for any reason. When this happens, the `sl-error` event will be emitted.
This example demonstrates what happens when a copy error occurs. You can customize the error label and icon using the `error-label` attribute and the `error-icon` slot, respectively.
```html:preview
<sl-copy-button from="i-do-not-exist"></sl-copy-button>
```
```jsx:react
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
const App = () => (
<SlCopyButton from="i-do-not-exist" />
);
```
### Disabled
Copy buttons can be disabled by adding the `disabled` attribute.
```html:preview
<sl-copy-button value="You can't copy me" disabled></sl-copy-button>
```
```jsx:react
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
const App = () => (
<SlCopyButton value="You can't copy me" disabled />
);
```
### Changing Feedback Duration
A success indicator is briefly shown after copying. You can customize the length of time the indicator is shown using the `feedback-duration` attribute.
```html:preview
<sl-copy-button value="Shoelace rocks!" feedback-duration="250"></sl-copy-button>
```
```jsx:react
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
const App = () => (
<SlCopyButton value="Shoelace rocks!" feedback-duration={250} />
);
```
### Custom Styles
You can customize the button to your liking with CSS.
```html:preview
<sl-copy-button value="I'm so stylish" class="custom-styles">
<sl-icon slot="copy-icon" name="asterisk"></sl-icon>
<sl-icon slot="success-icon" name="check-lg"></sl-icon>
<sl-icon slot="error-icon" name="x-lg"></sl-icon>
</sl-copy-button>
<style>
.custom-styles {
--success-color: white;
--error-color: white;
color: white;
}
.custom-styles::part(button) {
background-color: #ff1493;
border: solid 4px #ff7ac1;
border-right-color: #ad005c;
border-bottom-color: #ad005c;
border-radius: 0;
transition: 100ms scale ease-in-out, 100ms translate ease-in-out;
}
.custom-styles::part(button):hover {
scale: 1.1;
}
.custom-styles::part(button):active {
translate: 0 2px;
}
.custom-styles::part(button):focus-visible {
outline: dashed 2px deeppink;
outline-offset: 4px;
}
</style>
```
```jsx:react
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
const css = `
.custom-styles {
--success-color: white;
--error-color: white;
color: white;
}
.custom-styles::part(button) {
background-color: #ff1493;
border: solid 4px #ff7ac1;
border-right-color: #ad005c;
border-bottom-color: #ad005c;
border-radius: 0;
transition: 100ms scale ease-in-out, 100ms translate ease-in-out;
}
.custom-styles::part(button):hover {
scale: 1.1;
}
.custom-styles::part(button):active {
translate: 0 2px;
}
.custom-styles::part(button):focus-visible {
outline: dashed 2px deeppink;
outline-offset: 4px;
}
`;
const App = () => (
<>
<SlCopyButton value="I'm so stylish" className="custom-styles" />
<style>{css}</style>
</>
);
```

View File

@@ -15,7 +15,7 @@ layout: component
```
```jsx:react
import SlDetails from '@shoelace-style/shoelace/dist/react/details';
import { SlDetails } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlDetails summary="Toggle Me">
@@ -39,7 +39,7 @@ Use the `disable` attribute to prevent the details from expanding.
```
```jsx:react
import SlDetails from '@shoelace-style/shoelace/dist/react/details';
import { SlDetails } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlDetails summary="Disabled" disabled>
@@ -71,8 +71,7 @@ Use the `expand-icon` and `collapse-icon` slots to change the expand and collaps
```
```jsx:react
import SlDetails from '@shoelace-style/shoelace/dist/react/details';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlDetails, SlIcon } from '@shoelace-style/shoelace/dist/react';
const css = `
sl-details.custom-icon::part(summary-icon) {

View File

@@ -27,8 +27,7 @@ layout: component
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
import { SlButton, SlDialog } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(false);
@@ -76,8 +75,7 @@ Use the `--width` custom property to set the dialog's width.
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
import { SlButton, SlDialog } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(false);
@@ -127,8 +125,7 @@ By design, a dialog's height will never exceed that of the viewport. As such, di
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
import { SlButton, SlDialog } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(false);
@@ -186,9 +183,7 @@ The header shows a functional close button by default. You can use the `header-a
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
import { SlButton, SlDialog, SlIconButton } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(false);
@@ -249,8 +244,7 @@ You can use `event.detail.source` to determine what triggered the request to clo
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
import { SlButton, SlDialog } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(false);
@@ -302,9 +296,7 @@ By default, the dialog's panel will gain focus when opened. This allows a subseq
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlButton, SlDialog, SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(false);

View File

@@ -10,7 +10,7 @@ layout: component
```
```jsx:react
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import { SlDivider } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlDivider />;
```
@@ -28,7 +28,7 @@ Use the `--width` custom property to change the width of the divider.
{% raw %}
```jsx:react
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import { SlDivider } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlDivider style={{ '--width': '4px' }} />;
```
@@ -46,7 +46,7 @@ Use the `--color` custom property to change the color of the divider.
{% raw %}
```jsx:react
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import { SlDivider } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlDivider style={{ '--color': 'tomato' }} />;
```
@@ -68,7 +68,7 @@ Use the `--spacing` custom property to change the amount of space between the di
{% raw %}
```jsx:react
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import { SlDivider } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -98,7 +98,7 @@ Add the `vertical` attribute to draw the divider in a vertical orientation. The
{% raw %}
```jsx:react
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import { SlDivider } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<div
@@ -138,9 +138,7 @@ Use dividers in [menus](/components/menu) to visually group menu items.
{% raw %}
```jsx:react
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlDivider, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlMenu style={{ maxWidth: '200px' }}>

View File

@@ -27,8 +27,7 @@ layout: component
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
import { SlButton, SlDrawer } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(false);
@@ -74,8 +73,7 @@ By default, drawers slide in from the end. To make the drawer slide in from the
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
import { SlButton, SlDrawer } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(false);
@@ -119,8 +117,7 @@ To make the drawer slide in from the top, set the `placement` attribute to `top`
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
import { SlButton, SlDrawer } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(false);
@@ -164,8 +161,7 @@ To make the drawer slide in from the bottom, set the `placement` attribute to `b
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
import { SlButton, SlDrawer } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(false);
@@ -219,8 +215,7 @@ Unlike normal drawers, contained drawers are not modal. This means they do not s
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
import { SlButton, SlDrawer } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(false);
@@ -287,8 +282,7 @@ Use the `--size` custom property to set the drawer's size. This will be applied
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
import { SlButton, SlDrawer } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(false);
@@ -338,8 +332,7 @@ By design, a drawer's height will never exceed 100% of its container. As such, d
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
import { SlButton, SlDrawer } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(false);
@@ -396,9 +389,7 @@ The header shows a functional close button by default. You can use the `header-a
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
import { SlButton, SlDrawer, SlIconButton } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(false);
@@ -454,8 +445,7 @@ You can use `event.detail.source` to determine what triggered the request to clo
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
import { SlButton, SlDrawer } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(false);
@@ -507,9 +497,7 @@ By default, the drawer's panel will gain focus when opened. This allows a subseq
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlButton, SlDrawer, SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(false);

View File

@@ -7,7 +7,7 @@ layout: component
Dropdowns consist of a trigger and a panel. By default, activating the trigger will expose the panel and interacting outside of the panel will close it.
Dropdowns are designed to work well with [menus](/components/menu) to provide a list of options the user can select from. However, dropdowns can also be used in lower-level applications (e.g. [color picker](/components/color-picker)). The API gives you complete control over showing, hiding, and positioning the panel.
Dropdowns are designed to work well with [menus](/components/menu) to provide a list of options the user can select from. However, dropdowns can also be used in lower-level applications (e.g. [color picker](/components/color-picker) and [select](/components/select)). The API gives you complete control over showing, hiding, and positioning the panel.
```html:preview
<sl-dropdown>
@@ -33,12 +33,7 @@ Dropdowns are designed to work well with [menus](/components/menu) to provide a
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlButton, SlDivider, SlDropdown, SlIcon, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlDropdown>
@@ -98,10 +93,7 @@ When dropdowns are used with [menus](/components/menu), you can listen for the [
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlButton, SlDropdown, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const App = () => {
function handleSelect(event) {
@@ -151,10 +143,7 @@ Alternatively, you can listen for the `click` event on individual menu items. No
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlButton, SlDropdown, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const App = () => {
function handleCut() {
@@ -203,11 +192,7 @@ The preferred placement of the dropdown can be set with the `placement` attribut
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlButton, SlDivider, SlDropdown, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlDropdown placement="top-start">
@@ -245,11 +230,7 @@ The distance from the panel to the trigger can be customized using the `distance
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlButton, SlDivider, SlDropdown, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlDropdown distance={30}>
@@ -287,11 +268,7 @@ The offset of the panel along the trigger can be customized using the `skidding`
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlButton, SlDivider, SlDropdown, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlDropdown skidding={30}>
@@ -346,12 +323,7 @@ Dropdown panels will be clipped if they're inside a container that has `overflow
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlButton, SlDivider, SlDropdown, SlIcon, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const css = `
.dropdown-hoist {

View File

@@ -24,9 +24,7 @@ layout: component
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlFormatBytes from '@shoelace-style/shoelace/dist/react/format-bytes';
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlButton, SlFormatBytes, SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [value, setValue] = useState(1000);
@@ -64,7 +62,7 @@ Set the `value` attribute to a number to get the value in bytes.
```
```jsx:react
import SlFormatBytes from '@shoelace-style/shoelace/dist/react/format-bytes';
import { SlFormatBytes } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -91,7 +89,7 @@ To get the value in bits, set the `unit` attribute to `bit`.
```
```jsx:react
import SlFormatBytes from '@shoelace-style/shoelace/dist/react/format-bytes';
import { SlFormatBytes } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -118,7 +116,7 @@ Use the `lang` attribute to set the number formatting locale.
```
```jsx:react
import SlFormatBytes from '@shoelace-style/shoelace/dist/react/format-bytes';
import { SlFormatBytes } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>

View File

@@ -13,7 +13,7 @@ Localization is handled by the browser's [`Intl.DateTimeFormat` API](https://dev
```
```jsx:react
import SlFormatDate from '@shoelace-style/shoelace/dist/react/format-date';
import { SlFormatDate } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlFormatDate date="2020-07-15T09:17:00-04:00" />;
```
@@ -51,7 +51,7 @@ Formatting options are based on those found in the [`Intl.DateTimeFormat` API](h
```
```jsx:react
import SlFormatDate from '@shoelace-style/shoelace/dist/react/format-date';
import { SlFormatDate } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -91,7 +91,7 @@ By default, the browser will determine whether to use 12-hour or 24-hour time. T
```
```jsx:react
import SlFormatDate from '@shoelace-style/shoelace/dist/react/format-date';
import { SlFormatDate } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -113,7 +113,7 @@ Russian: <sl-format-date lang="ru"></sl-format-date>
```
```jsx:react
import SlFormatDate from '@shoelace-style/shoelace/dist/react/format-date';
import { SlFormatDate } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>

View File

@@ -27,8 +27,7 @@ Localization is handled by the browser's [`Intl.NumberFormat` API](https://devel
```jsx:react
import { useState } from 'react';
import SlFormatNumber from '@shoelace-style/shoelace/dist/react/format-number';
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlFormatNumber, SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [value, setValue] = useState(1000);
@@ -67,7 +66,7 @@ To get the value as a percent, set the `type` attribute to `percent`.
```
```jsx:react
import SlFormatNumber from '@shoelace-style/shoelace/dist/react/format-number';
import { SlFormatNumber } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -95,7 +94,7 @@ Russian: <sl-format-number value="2000" lang="ru" minimum-fraction-digits="2"></
```
```jsx:react
import SlFormatNumber from '@shoelace-style/shoelace/dist/react/format-number';
import { SlFormatNumber } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -121,7 +120,7 @@ To format a number as a monetary value, set the `type` attribute to `currency` a
```
```jsx:react
import SlFormatNumber from '@shoelace-style/shoelace/dist/react/format-number';
import { SlFormatNumber } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>

View File

@@ -12,7 +12,7 @@ For a full list of icons that come bundled with Shoelace, refer to the [icon com
```
```jsx:react
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
import { SlIconButton } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlIconButton name="gear" label="Settings" />;
```
@@ -32,7 +32,7 @@ Icon buttons inherit their parent element's `font-size`.
{% raw %}
```jsx:react
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
import { SlIconButton } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -73,7 +73,7 @@ Icon buttons are designed to have a uniform appearance, so their color is not in
```
```jsx:react
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
import { SlIconButton } from '@shoelace-style/shoelace/dist/react';
const css = `
.icon-button-color sl-icon-button::part(base) {
@@ -112,7 +112,7 @@ Use the `href` attribute to convert the button to a link.
```
```jsx:react
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
import { SlIconButton } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlIconButton name="gear" label="Settings" href="https://example.com" target="_blank" />;
```
@@ -128,8 +128,7 @@ Wrap a tooltip around an icon button to provide contextual information to the us
```
```jsx:react
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
import SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';
import { SlIconButton, SlTooltip } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTooltip content="Settings">
@@ -147,7 +146,7 @@ Use the `disabled` attribute to disable the icon button.
```
```jsx:react
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
import { SlIconButton } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlIconButton name="gear" label="Settings" disabled />;
```

View File

@@ -8,7 +8,7 @@ layout: component
Shoelace comes bundled with over 1,500 icons courtesy of the [Bootstrap Icons](https://icons.getbootstrap.com/) project. These icons are part of the `default` icon library. If you prefer, you can register [custom icon libraries](#icon-libraries) as well.
:::tip
Depending on how you're loading Shoelace, you may need to copy icon assets and/or [set the base path](/getting-started/installation/#setting-the-base-path) so Shoelace knows where to load them from. Otherwise, icons may not appear and you'll see 404 Not Found errors in the dev console.
Depending on how you're loading Shoelace, you may need to copy icon assets and/or [set the base path](getting-started/installation#setting-the-base-path) so Shoelace knows where to load them from. Otherwise, icons may not appear and you'll see 404 Not Found errors in the dev console.
:::
## Default Icons
@@ -70,7 +70,7 @@ Icons inherit their color from the current text color. Thus, you can set the `co
{% raw %}
```jsx:react
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlIcon } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -132,7 +132,7 @@ Icons are sized relative to the current font size. To change their size, set the
{% raw %}
```jsx:react
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlIcon } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<div style={{ fontSize: '32px' }}>
@@ -167,7 +167,7 @@ For non-decorative icons, use the `label` attribute to announce it to assistive
```
```jsx:react
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlIcon } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlIcon name="star-fill" label="Add to favorites" />;
```
@@ -183,7 +183,7 @@ Custom icons can be loaded individually with the `src` attribute. Only SVGs on a
{% raw %}
```jsx:react
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlIcon } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlIcon src="https://shoelace.style/assets/images/shoe.svg" style={{ fontSize: '8rem' }}></SlIcon>;
```
@@ -645,7 +645,9 @@ When using sprite sheets, the `sl-load` and `sl-error` events will not fire.
:::
:::danger
For security reasons, browsers may apply the same-origin policy on `<use>` elements located in the `<sl-icon>` shadow DOM and may refuse to load a cross-origin URL. There is currently no defined way to set a cross-origin policy for `<use>` elements. For this reason, sprite sheets should only be used if you're self-hosting them.
For security reasons, browsers may apply the same-origin policy on `<use>` elements located in the `<sl-icon>` shadow dom and
may refuse to load a cross-origin URL. There is currently no defined way to set a cross-origin policy for `<use>` elements.
For this reason, sprite sheets should only be used if you're self-hosting them.
:::
```html:preview

View File

@@ -23,7 +23,7 @@ For best results, use images that share the same dimensions. The slider can be c
```
```jsx:react
import SlImageComparer from '@shoelace-style/shoelace/dist/react/image-comparer';
import { SlImageComparer } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlImageComparer>
@@ -63,7 +63,7 @@ Use the `position` attribute to set the initial position of the slider. This is
```
```jsx:react
import SlImageComparer from '@shoelace-style/shoelace/dist/react/image-comparer';
import { SlImageComparer } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlImageComparer position={25}>

View File

@@ -14,7 +14,7 @@ The included content will be inserted into the `<sl-include>` element's default
```
```jsx:react
import SlInclude from '@shoelace-style/shoelace/dist/react/include';
import { SlInclude } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlInclude src="https://shoelace.style/assets/examples/include.html" />;
```

View File

@@ -10,7 +10,7 @@ layout: component
```
```jsx:react
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlInput />;
```
@@ -30,8 +30,7 @@ Use the `label` attribute to give the input an accessible label. For labels that
```
```jsx:react
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlIcon, SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlInput label="What is your name?" />;
```
@@ -45,8 +44,7 @@ Add descriptive help text to an input with the `help-text` attribute. For help t
```
```jsx:react
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlIcon, SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlInput label="Nickname" help-text="What would you like people to call you?" />;
```
@@ -60,7 +58,7 @@ Use the `placeholder` attribute to add a placeholder.
```
```jsx:react
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlInput placeholder="Type something" />;
```
@@ -74,7 +72,7 @@ Add the `clearable` attribute to add a clear button when the input has content.
```
```jsx:react
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlInput placeholder="Clearable" clearable />;
```
@@ -88,7 +86,7 @@ Add the `password-toggle` attribute to add a toggle button that will show the pa
```
```jsx:react
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlInput type="password" placeholder="Password Toggle" size="medium" password-toggle />;
```
@@ -102,7 +100,7 @@ Add the `filled` attribute to draw a filled input.
```
```jsx:react
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlInput placeholder="Type something" filled />;
```
@@ -116,7 +114,7 @@ Use the `disabled` attribute to disable an input.
```
```jsx:react
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlInput placeholder="Disabled" disabled />;
```
@@ -134,7 +132,7 @@ Use the `size` attribute to change an input's size.
```
```jsx:react
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -160,7 +158,7 @@ Use the `pill` attribute to give inputs rounded edges.
```
```jsx:react
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -186,7 +184,7 @@ The `type` attribute controls the type of input the browser renders.
```
```jsx:react
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -221,8 +219,7 @@ Use the `prefix` and `suffix` slots to add icons.
```
```jsx:react
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlIcon, SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>

View File

@@ -28,10 +28,7 @@ layout: component
{% raw %}
```jsx:react
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlDivider, SlIcon, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlMenu style={{ maxWidth: '200px' }}>
@@ -75,8 +72,7 @@ Add the `disabled` attribute to disable the menu item so it cannot be selected.
{% raw %}
```jsx:react
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlMenu style={{ maxWidth: '200px' }}>
@@ -118,11 +114,7 @@ Add content to the start and end of menu items using the `prefix` and `suffix` s
{% raw %}
```jsx:react
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlBadge, SlDivider, SlIcon, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlMenu style={{ maxWidth: '200px' }}>
@@ -168,8 +160,7 @@ Checkbox menu items are visually indistinguishable from regular menu items. Thei
{% raw %}
```jsx:react
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlMenu style={{ maxWidth: '200px' }}>
@@ -218,8 +209,7 @@ The `value` attribute can be used to assign a hidden value, such as a unique ide
{% raw %}
```jsx:react
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const App = () => {
function handleSelect(event) {

View File

@@ -22,10 +22,7 @@ layout: component
{% raw %}
```jsx:react
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuLabel from '@shoelace-style/shoelace/dist/react/menu-label';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlDivider, SlMenu, SlMenuLabel, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlMenu style={{ maxWidth: '200px' }}>

View File

@@ -22,9 +22,7 @@ You can use [menu items](/components/menu-item), [menu labels](/components/menu-
{% raw %}
```jsx:react
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlDivider, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlMenu style={{ maxWidth: '200px' }}>

View File

@@ -45,8 +45,7 @@ The mutation observer will report changes to the content it wraps through the `s
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlMutationObserver from '@shoelace-style/shoelace/dist/react/mutation-observer';
import { SlButton, SlMutationObserver } from '@shoelace-style/shoelace/dist/react';
const css = `
.resize-observer-overview div {
@@ -147,8 +146,7 @@ Use the `child-list` attribute to watch for new child elements that are added or
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlMutationObserver from '@shoelace-style/shoelace/dist/react/mutation-observer';
import { SlButton, SlMutationObserver } from '@shoelace-style/shoelace/dist/react';
const css = `
.mutation-child-list .buttons {

View File

@@ -14,8 +14,7 @@ layout: component
```
```jsx:react
import SlOption from '@shoelace-style/shoelace/dist/react/option';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSelect>
@@ -41,8 +40,7 @@ Use the `disabled` attribute to disable an option and prevent it from being sele
```
```jsx:react
import SlOption from '@shoelace-style/shoelace/dist/react/option';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSelect>

View File

@@ -104,11 +104,7 @@ Popup is a low-level utility built specifically for positioning elements. Do not
```jsx:react
import { useState } from 'react';
import SlPopup from '@shoelace-style/shoelace/dist/react/popup';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import SlSwitch from '@shoelace-style/shoelace/dist/react/switch';
import { SlPopup, SlSelect, SlMenuItem, SlInput, SlSwitch } from '@shoelace-style/shoelace/dist/react';
const css = `
.popup-overview sl-popup {
@@ -273,8 +269,7 @@ Popups are inactive and hidden until the `active` attribute is applied. Removing
```jsx:react
import { useState } from 'react';
import SlPopup from '@shoelace-style/shoelace/dist/react/popup';
import SlSwitch from '@shoelace-style/shoelace/dist/react/switch';
import { SlPopup, SlSwitch } from '@shoelace-style/shoelace/dist/react';
const css = `
.popup-active span[slot='anchor'] {
@@ -346,7 +341,7 @@ By default, anchors are slotted into the popup using the `anchor` slot. If your
```
```jsx:react
import SlPopup from '@shoelace-style/shoelace/dist/react/popup';
import { SlPopup } from '@shoelace-style/shoelace/dist/react';
const css = `
#external-anchor {
@@ -441,9 +436,7 @@ Since placement is preferred when using `flip`, you can observe the popup's curr
```jsx:react
import { useState } from 'react';
import SlPopup from '@shoelace-style/shoelace/dist/react/popup';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlPopup, SlSelect, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const css = `
.popup-placement span[slot='anchor'] {
@@ -545,8 +538,7 @@ Use the `distance` attribute to change the distance between the popup and its an
```jsx:react
import { useState } from 'react';
import SlPopup from '@shoelace-style/shoelace/dist/react/popup';
import SlRange from '@shoelace-style/shoelace/dist/react/range';
import { SlPopup, SlRange } from '@shoelace-style/shoelace/dist/react';
const css = `
.popup-distance span[slot='anchor'] {
@@ -642,8 +634,7 @@ The `skidding` attribute is similar to `distance`, but instead allows you to off
```jsx:react
import { useState } from 'react';
import SlPopup from '@shoelace-style/shoelace/dist/react/popup';
import SlRange from '@shoelace-style/shoelace/dist/react/range';
import { SlPopup, SlRange } from '@shoelace-style/shoelace/dist/react';
const css = `
.popup-skidding span[slot='anchor'] {
@@ -786,10 +777,7 @@ By default, the arrow will be aligned as close to the center of the _anchor_ as
```jsx:react
import { useState } from 'react';
import SlPopup from '@shoelace-style/shoelace/dist/react/popup';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import SlSwitch from '@shoelace-style/shoelace/dist/react/switch';
import { SlPopup, SlSelect, SlMenuItem, SlSwitch } from '@shoelace-style/shoelace/dist/react';
const css = `
.popup-arrow sl-popup {
@@ -942,9 +930,7 @@ Use the `sync` attribute to make the popup the same width or height as the ancho
```jsx:react
import { useState } from 'react';
import SlPopup from '@shoelace-style/shoelace/dist/react/popup';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlPopup, SlSelect, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const css = `
.popup-sync span[slot='anchor'] {
@@ -1055,8 +1041,7 @@ Toggle the switch and scroll the container to see the difference.
```jsx:react
import { useState } from 'react';
import SlPopup from '@shoelace-style/shoelace/dist/react/popup';
import SlSwitch from '@shoelace-style/shoelace/dist/react/switch';
import { SlPopup, SlSwitch } from '@shoelace-style/shoelace/dist/react';
const css = `
.popup-strategy .overflow {
@@ -1164,8 +1149,7 @@ Scroll the container to see how the popup flips to prevent clipping.
```jsx:react
import { useState } from 'react';
import SlPopup from '@shoelace-style/shoelace/dist/react/popup';
import SlSwitch from '@shoelace-style/shoelace/dist/react/switch';
import { SlPopup, SlSwitch } from '@shoelace-style/shoelace/dist/react';
const css = `
.popup-flip .overflow {
@@ -1262,7 +1246,7 @@ Scroll the container to see how the popup changes it's fallback placement to pre
```
```jsx:react
import SlPopup from '@shoelace-style/shoelace/dist/react/popup';
import { SlPopup } from '@shoelace-style/shoelace/dist/react';
const css = `
.popup-flip-fallbacks .overflow {
@@ -1358,8 +1342,7 @@ Toggle the switch to see the difference.
```jsx:react
import { useState } from 'react';
import SlPopup from '@shoelace-style/shoelace/dist/react/popup';
import SlSwitch from '@shoelace-style/shoelace/dist/react/switch';
import { SlPopup, SlSwitch } from '@shoelace-style/shoelace/dist/react';
const css = `
.popup-shift .overflow {
@@ -1471,8 +1454,7 @@ Scroll the container to see the popup resize as its available space changes.
```jsx:react
import { useState } from 'react';
import SlPopup from '@shoelace-style/shoelace/dist/react/popup';
import SlSwitch from '@shoelace-style/shoelace/dist/react/switch';
import { SlPopup, SlSwitch } from '@shoelace-style/shoelace/dist/react';
const css = `
.popup-auto-size .overflow {
@@ -1529,179 +1511,3 @@ const App = () => {
);
};
```
### Virtual Elements
In most cases, popups are anchored to an actual element. Sometimes, it can be useful to anchor them to a non-element. To do this, you can pass a `VirtualElement` to the anchor property. A virtual element must contain a function called `getBoundingClientRect()` that returns a [`DOMRect`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRect) object as shown below.
```ts
const virtualElement = {
getBoundingClientRect() {
// ...
return { width, height, x, y, top, left, right, bottom };
}
};
```
This example anchors a popup to the mouse cursor using a virtual element. As such, a mouse is required to properly view it.
```html:preview
<div class="popup-virtual-element">
<sl-popup placement="right-start">
<div class="circle"></div>
</sl-popup>
<sl-switch>Highlight mouse cursor</sl-switch>
</div>
<script>
const container = document.querySelector('.popup-virtual-element');
const popup = container.querySelector('sl-popup');
const circle = container.querySelector('.circle');
const enabled = container.querySelector('sl-switch');
let clientX = 0;
let clientY = 0;
// Set the virtual element as a property
popup.anchor = {
getBoundingClientRect() {
return {
width: 0,
height: 0,
x: clientX,
y: clientY,
top: clientY,
left: clientX,
right: clientX,
bottom: clientY
};
}
};
// Only activate the popup when the switch is checked
enabled.addEventListener('sl-change', () => {
popup.active = enabled.checked;
});
// Listen for the mouse to move
document.addEventListener('mousemove', handleMouseMove);
// Update the virtual element as the mouse moves
function handleMouseMove(event) {
clientX = event.clientX;
clientY = event.clientY;
// Reposition the popup when the virtual anchor moves
if (popup.active) {
popup.reposition();
}
}
</script>
<style>
/* If you need to set a z-index, set it on the popup part like this */
.popup-virtual-element sl-popup::part(popup) {
z-index: 1000;
pointer-events: none;
}
.popup-virtual-element .circle {
width: 100px;
height: 100px;
border: solid 4px var(--sl-color-primary-600);
border-radius: 50%;
translate: -50px -50px;
animation: 1s virtual-cursor infinite;
}
@keyframes virtual-cursor {
0% { scale: 1; }
50% { scale: 1.1; }
}
</style>
```
```jsx:react
import { useRef, useState } from 'react';
import SlPopup from '@shoelace-style/shoelace/dist/react/popup';
import SlSwitch from '@shoelace-style/shoelace/dist/react/switch';
const css = `
/* If you need to set a z-index, set it on the popup part like this */
.popup-virtual-element sl-popup::part(popup) {
z-index: 1000;
pointer-events: none;
}
.popup-virtual-element .circle {
width: 100px;
height: 100px;
border: solid 4px var(--sl-color-primary-600);
border-radius: 50%;
translate: -50px -50px;
animation: 1s virtual-cursor infinite;
}
@keyframes virtual-cursor {
0% { scale: 1; }
50% { scale: 1.1; }
}
`;
const App = () => {
const [enabled, setEnabled] = useState(false);
const [clientX, setClientX] = useState(0);
const [clientY, setClientY] = useState(0);
const popup = useRef(null);
const circle = useRef(null);
const virtualElement = {
getBoundingClientRect() {
return {
width: 0,
height: 0,
x: clientX,
y: clientY,
top: clientY,
left: clientX,
right: clientX,
bottom: clientY
};
}
};
// Listen for the mouse to move
document.addEventListener('mousemove', handleMouseMove);
// Update the virtual element as the mouse moves
function handleMouseMove(event) {
setClientX(event.clientX);
setClientY(event.clientY);
// Reposition the popup when the virtual anchor moves
if (popup.active) {
popup.current.reposition();
}
}
return (
<>
<div className="popup-virtual-element">
<SlPopup
ref={popup}
placement="right-start"
active={enabled}
anchor={virtualElement}
>
<div ref={circle} className="circle" />
</SlPopup>
<SlSwitch checked={enabled} onSlChange={event => setEnabled(event.target.checked)}>
Highlight mouse cursor
</SlSwitch>
</div>
<style>{css}</style>
</>
);
};
```

View File

@@ -10,7 +10,7 @@ layout: component
```
```jsx:react
import SlProgressBar from '@shoelace-style/shoelace/dist/react/progress-bar';
import { SlProgressBar } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlProgressBar value={50} />;
```
@@ -26,7 +26,7 @@ Use the `label` attribute to label the progress bar and tell assistive devices h
```
```jsx:react
import SlProgressBar from '@shoelace-style/shoelace/dist/react/progress-bar';
import { SlProgressBar } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlProgressBar value="50" label="Upload progress" />;
```
@@ -42,7 +42,7 @@ Use the `--height` custom property to set the progress bar's height.
{% raw %}
```jsx:react
import SlProgressBar from '@shoelace-style/shoelace/dist/react/progress-bar';
import { SlProgressBar } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlProgressBar value={50} style={{ '--height': '6px' }} />;
```
@@ -82,9 +82,7 @@ Use the default slot to show a value.
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import SlProgressBar from '@shoelace-style/shoelace/dist/react/progress-bar';
import { SlButton, SlIcon, SlProgressBar } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [value, setValue] = useState(50);
@@ -123,7 +121,7 @@ The `indeterminate` attribute can be used to inform the user that the operation
```
```jsx:react
import SlProgressBar from '@shoelace-style/shoelace/dist/react/progress-bar';
import { SlProgressBar } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlProgressBar indeterminate />;
```

View File

@@ -10,7 +10,7 @@ layout: component
```
```jsx:react
import SlProgressRing from '@shoelace-style/shoelace/dist/react/progress-ring';
import { SlProgressRing } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlProgressRing value="25" />;
```
@@ -28,7 +28,7 @@ Use the `--size` custom property to set the diameter of the progress ring.
{% raw %}
```jsx:react
import SlProgressRing from '@shoelace-style/shoelace/dist/react/progress-ring';
import { SlProgressRing } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlProgressRing value="50" style={{ '--size': '200px' }} />;
```
@@ -46,7 +46,7 @@ Use the `--track-width` and `--indicator-width` custom properties to set the wid
{% raw %}
```jsx:react
import SlProgressRing from '@shoelace-style/shoelace/dist/react/progress-ring';
import { SlProgressRing } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlProgressRing value="50" style={{ '--track-width': '6px', '--indicator-width': '12px' }} />;
```
@@ -70,7 +70,7 @@ To change the color, use the `--track-color` and `--indicator-color` custom prop
{% raw %}
```jsx:react
import SlProgressRing from '@shoelace-style/shoelace/dist/react/progress-ring';
import { SlProgressRing } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlProgressRing
@@ -94,7 +94,7 @@ Use the `label` attribute to label the progress ring and tell assistive devices
```
```jsx:react
import SlProgressRing from '@shoelace-style/shoelace/dist/react/progress-ring';
import { SlProgressRing } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlProgressRing value="50" label="Upload progress" />;
```
@@ -134,9 +134,7 @@ Use the default slot to show a label inside the progress ring.
```jsx:react
import { useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import SlProgressRing from '@shoelace-style/shoelace/dist/react/progress-ring';
import { SlButton, SlIcon, SlProgressRing } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [value, setValue] = useState(50);

View File

@@ -39,8 +39,7 @@ QR codes are useful for providing small pieces of information to users who can q
```jsx:react
import { useState } from 'react';
import SlQrCode from '@shoelace-style/shoelace/dist/react/qr-code';
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlQrCode, SlInput } from '@shoelace-style/shoelace/dist/react';
const css = `
.qr-overview {
@@ -81,7 +80,7 @@ Use the `fill` and `background` attributes to modify the QR code's colors. You s
```
```jsx:react
import SlQrCode from '@shoelace-style/shoelace/dist/react/qr-code';
import { SlQrCode } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlQrCode value="https://shoelace.style/" fill="deeppink" background="white" />;
```
@@ -95,7 +94,7 @@ Use the `size` attribute to change the size of the QR code.
```
```jsx:react
import SlQrCode from '@shoelace-style/shoelace/dist/react/qr-code';
import { SlQrCode } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlQrCode value="https://shoelace.style/" size="64" />;
```
@@ -109,7 +108,7 @@ Create a rounded effect with the `radius` attribute.
```
```jsx:react
import SlQrCode from '@shoelace-style/shoelace/dist/react/qr-code';
import { SlQrCode } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlQrCode value="https://shoelace.style/" radius="0.5" />;
```
@@ -136,7 +135,7 @@ QR codes can be rendered with various levels of [error correction](https://www.q
```
```jsx:react
import SlQrCode from '@shoelace-style/shoelace/dist/react/qr-code';
import { SlQrCode } from '@shoelace-style/shoelace/dist/react';
const css = `
.qr-error-correction {

View File

@@ -16,8 +16,7 @@ Radio buttons are designed to be used with [radio groups](/components/radio-grou
```
```jsx:react
import SlRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';
import SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRadioGroup label="Select an option" name="a" value="1">
@@ -43,8 +42,7 @@ To set the initial value and checked state, use the `value` attribute on the con
```
```jsx:react
import SlRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';
import SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRadioGroup label="Select an option" name="a" value="1">
@@ -68,8 +66,7 @@ Use the `disabled` attribute to disable a radio button.
```
```jsx:react
import SlRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';
import SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRadioGroup label="Select an option" name="a" value="1">
@@ -111,8 +108,7 @@ Use the `size` attribute to change a radio button's size.
```
```jsx:react
import SlRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';
import SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRadioGroup label="Select an option" name="a" value="1">
@@ -168,8 +164,7 @@ Use the `pill` attribute to give radio buttons rounded edges.
```
```jsx:react
import SlRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';
import SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRadioGroup label="Select an option" name="a" value="1">
@@ -221,9 +216,7 @@ Use the `prefix` and `suffix` slots to add icons.
```
```jsx:react
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import SlRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';
import SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
import { SlIcon, SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRadioGroup label="Select an option" name="a" value="1">
@@ -275,9 +268,7 @@ You can omit button labels and use icons instead. Make sure to set a `label` att
```
```jsx:react
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import SlRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';
import SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
import { SlIcon, SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRadioGroup label="Select an option" name="a" value="neutral">

View File

@@ -14,8 +14,7 @@ layout: component
```
```jsx:react
import SlRadio from '@shoelace-style/shoelace/dist/react/radio';
import SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRadioGroup label="Select an option" name="a" value="1">
@@ -41,8 +40,7 @@ Add descriptive help text to a radio group with the `help-text` attribute. For h
```
```jsx:react
import SlRadio from '@shoelace-style/shoelace/dist/react/radio';
import SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRadioGroup label="Select an option" help-text="Choose the most appropriate option." name="a" value="1">
@@ -66,8 +64,7 @@ const App = () => (
```
```jsx:react
import SlRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';
import SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRadioGroup label="Select an option" name="a" value="1">
@@ -91,8 +88,7 @@ Radios and radio buttons can be disabled by adding the `disabled` attribute to t
```
```jsx:react
import SlRadio from '@shoelace-style/shoelace/dist/react/radio';
import SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRadioGroup label="Select an option" name="a" value="1">
@@ -127,8 +123,7 @@ The size of [Radios](/components/radio) and [Radio Buttons](/components/radio-bu
```jsx react
import { useState } from 'react';
import SlRadio from '@shoelace-style/shoelace/dist/react/radio';
import SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [size, setSize] = useState('medium');
@@ -182,10 +177,7 @@ Setting the `required` attribute to make selecting an option mandatory. If a val
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import SlRadio from '@shoelace-style/shoelace/dist/react/radio';
import SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
import { SlButton, SlIcon, SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => {
function handleSubmit(event) {
event.preventDefault();
@@ -255,10 +247,7 @@ Use the `setCustomValidity()` method to set a custom validation message. This wi
```jsx:react
import { useEffect, useRef } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import SlRadio from '@shoelace-style/shoelace/dist/react/radio';
import SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
import { SlButton, SlIcon, SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const radioGroup = useRef(null);
const errorMessage = 'You must choose this option';

View File

@@ -16,8 +16,7 @@ Radios are designed to be used with [radio groups](/components/radio-group).
```
```jsx:react
import SlRadio from '@shoelace-style/shoelace/dist/react/radio';
import SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRadioGroup label="Select an option" name="a" value="1">
@@ -47,8 +46,7 @@ To set the initial value and checked state, use the `value` attribute on the con
```
```jsx:react
import SlRadio from '@shoelace-style/shoelace/dist/react/radio';
import SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRadioGroup label="Select an option" name="a" value="3">
@@ -72,8 +70,7 @@ Use the `disabled` attribute to disable a radio.
```
```jsx:react
import SlRadio from '@shoelace-style/shoelace/dist/react/radio';
import SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRadioGroup label="Select an option" name="a" value="1">
@@ -115,7 +112,7 @@ Add the `size` attribute to the [Radio Group](/components/radio-group) to change
```
```jsx react
import SlRadio from '@shoelace-style/shoelace/dist/react/radio';
import { SlRadio } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>

View File

@@ -10,7 +10,7 @@ layout: component
```
```jsx:react
import SlRange from '@shoelace-style/shoelace/dist/react/range';
import { SlRange } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlRange />;
```
@@ -30,7 +30,7 @@ Use the `label` attribute to give the range an accessible label. For labels that
```
```jsx:react
import SlRange from '@shoelace-style/shoelace/dist/react/range';
import { SlRange } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlRange label="Volume" min={0} max={100} />;
```
@@ -44,7 +44,7 @@ Add descriptive help text to a range with the `help-text` attribute. For help te
```
```jsx:react
import SlRange from '@shoelace-style/shoelace/dist/react/range';
import { SlRange } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlRange label="Volume" help-text="Controls the volume of the current song." min={0} max={100} />;
```
@@ -58,7 +58,7 @@ Use the `min` and `max` attributes to set the range's minimum and maximum values
```
```jsx:react
import SlRange from '@shoelace-style/shoelace/dist/react/range';
import { SlRange } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlRange min={0} max={10} step={1} />;
```
@@ -72,7 +72,7 @@ Use the `disabled` attribute to disable a slider.
```
```jsx:react
import SlRange from '@shoelace-style/shoelace/dist/react/range';
import { SlRange } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlRange disabled />;
```
@@ -86,7 +86,7 @@ By default, the tooltip is shown on top. Set `tooltip` to `bottom` to show it be
```
```jsx:react
import SlRange from '@shoelace-style/shoelace/dist/react/range';
import { SlRange } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlRange tooltip="bottom" />;
```
@@ -100,7 +100,7 @@ To disable the tooltip, set `tooltip` to `none`.
```
```jsx:react
import SlRange from '@shoelace-style/shoelace/dist/react/range';
import { SlRange } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlRange tooltip="none" />;
```
@@ -121,7 +121,7 @@ You can customize the active and inactive portions of the track using the `--tra
{% raw %}
```jsx:react
import SlRange from '@shoelace-style/shoelace/dist/react/range';
import { SlRange } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRange
@@ -154,7 +154,7 @@ You can customize the initial offset of the active track using the `--track-acti
{% raw %}
```jsx:react
import SlRange from '@shoelace-style/shoelace/dist/react/range';
import { SlRange } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRange
@@ -185,7 +185,7 @@ You can change the tooltip's content by setting the `tooltipFormatter` property
```
```jsx:react
import SlRange from '@shoelace-style/shoelace/dist/react/range';
import { SlRange } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlRange min={0} max={100} step={1} tooltipFormatter={value => `Total - ${value}%`} />;
```

View File

@@ -10,7 +10,7 @@ layout: component
```
```jsx:react
import SlRating from '@shoelace-style/shoelace/dist/react/rating';
import { SlRating } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlRating label="Rating" />;
```
@@ -26,7 +26,7 @@ Ratings are commonly identified contextually, so labels aren't displayed. Howeve
```
```jsx:react
import SlRating from '@shoelace-style/shoelace/dist/react/rating';
import { SlRating } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlRating label="Rate this component" />;
```
@@ -40,7 +40,7 @@ Ratings are 0-5 by default. To change the maximum possible value, use the `max`
```
```jsx:react
import SlRating from '@shoelace-style/shoelace/dist/react/rating';
import { SlRating } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlRating label="Rating" max={3} />;
```
@@ -54,7 +54,7 @@ Use the `precision` attribute to let users select fractional ratings.
```
```jsx:react
import SlRating from '@shoelace-style/shoelace/dist/react/rating';
import { SlRating } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlRating label="Rating" precision={0.5} value={2.5} />;
```
@@ -70,7 +70,7 @@ Set the `--symbol-size` custom property to adjust the size.
{% raw %}
```jsx:react
import SlRating from '@shoelace-style/shoelace/dist/react/rating';
import { SlRating } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlRating label="Rating" style={{ '--symbol-size': '2rem' }} />;
```
@@ -86,7 +86,7 @@ Use the `readonly` attribute to display a rating that users can't change.
```
```jsx:react
import SlRating from '@shoelace-style/shoelace/dist/react/rating';
import { SlRating } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlRating label="Rating" readonly value={3} />;
```
@@ -100,7 +100,7 @@ Use the `disable` attribute to disable the rating.
```
```jsx:react
import SlRating from '@shoelace-style/shoelace/dist/react/rating';
import { SlRating } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlRating label="Rating" disabled value={3} />;
```
@@ -152,7 +152,7 @@ The event has a payload with `phase` and `value` properties. The `phase` propert
```jsx:react
import { useState } from 'react';
import SlRating from '@shoelace-style/shoelace/dist/react/rating';
import { SlRating } from '@shoelace-style/shoelace/dist/react';
const terms = ['No rating', 'Terrible', 'Bad', 'OK', 'Good', 'Excellent'];
const css = `
@@ -214,7 +214,7 @@ You can provide custom icons by passing a function to the `getSymbol` property.
{% raw %}
```jsx:react
import SlRating from '@shoelace-style/shoelace/dist/react/rating';
import { SlRating } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRating
@@ -245,7 +245,7 @@ You can also use the `getSymbol` property to render different icons based on val
```
```jsx:react
import SlRating from '@shoelace-style/shoelace/dist/react/rating';
import { SlRating } from '@shoelace-style/shoelace/dist/react';
function getSymbol(value) {
const icons = ['emoji-angry', 'emoji-frown', 'emoji-expressionless', 'emoji-smile', 'emoji-laughing'];

View File

@@ -13,7 +13,7 @@ Localization is handled by the browser's [`Intl.RelativeTimeFormat` API](https:/
```
```jsx:react
import SlRelativeTime from '@shoelace-style/shoelace/dist/react/relative-time';
import { SlRelativeTime } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlRelativeTime date="2020-07-15T09:17:00-04:00" />;
```
@@ -44,7 +44,7 @@ Use the `sync` attribute to update the displayed value automatically as time pas
```
```jsx:react
import SlRelativeTime from '@shoelace-style/shoelace/dist/react/relative-time';
import { SlRelativeTime } from '@shoelace-style/shoelace/dist/react';
const date = new Date(new Date().getTime() - 60000);
@@ -62,7 +62,7 @@ You can change how the time is displayed using the `format` attribute. Note that
```
```jsx:react
import SlRelativeTime from '@shoelace-style/shoelace/dist/react/relative-time';
import { SlRelativeTime } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -88,7 +88,7 @@ Russian: <sl-relative-time date="2020-07-15T09:17:00-04:00" lang="ru"></sl-relat
```
```jsx:react
import SlRelativeTime from '@shoelace-style/shoelace/dist/react/relative-time';
import { SlRelativeTime } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>

View File

@@ -36,7 +36,7 @@ The resize observer will report changes to the dimensions of the elements it wra
```
```jsx:react
import SlResizeObserver from '@shoelace-style/shoelace/dist/react/resize-observer';
import { SlResizeObserver } from '@shoelace-style/shoelace/dist/react';
const css = `
.resize-observer-overview div {

View File

@@ -17,8 +17,7 @@ layout: component
```
```jsx:react
import SlOption from '@shoelace-style/shoelace/dist/react/option';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSelect>
@@ -51,8 +50,7 @@ Use the `label` attribute to give the select an accessible label. For labels tha
```
```jsx:react
import SlOption from '@shoelace-style/shoelace/dist/react/option';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSelect label="Select one">
@@ -76,8 +74,7 @@ Add descriptive help text to a select with the `help-text` attribute. For help t
```
```jsx:react
import SlOption from '@shoelace-style/shoelace/dist/react/option';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSelect label="Experience" help-text="Please tell us your skill level.">
@@ -101,8 +98,7 @@ Use the `placeholder` attribute to add a placeholder.
```
```jsx:react
import SlOption from '@shoelace-style/shoelace/dist/react/option';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSelect placeholder="Select one">
@@ -126,8 +122,7 @@ Use the `clearable` attribute to make the control clearable. The clear button on
```
```jsx:react
import SlOption from '@shoelace-style/shoelace/dist/react/option';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSelect placeholder="Clearable" clearable>
@@ -151,8 +146,7 @@ Add the `filled` attribute to draw a filled select.
```
```jsx:react
import SlOption from '@shoelace-style/shoelace/dist/react/option';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSelect filled>
@@ -176,8 +170,7 @@ Use the `pill` attribute to give selects rounded edges.
```
```jsx:react
import SlOption from '@shoelace-style/shoelace/dist/react/option';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSelect pill>
@@ -201,8 +194,7 @@ Use the `disabled` attribute to disable a select.
```
```jsx:react
import SlOption from '@shoelace-style/shoelace/dist/react/option';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSelect placeholder="Disabled" disabled>
@@ -229,8 +221,7 @@ To allow multiple options to be selected, use the `multiple` attribute. It's a g
```
```jsx:react
import SlOption from '@shoelace-style/shoelace/dist/react/option';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSelect label="Select a Few" value="option-1 option-2 option-3" multiple clearable>
@@ -262,9 +253,7 @@ Use the `value` attribute to set the initial selection. When using `multiple`, u
```
```jsx:react
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
import SlOption from '@shoelace-style/shoelace/dist/react/option';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import { SlDivider, SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSelect value="option-1 option-2" multiple clearable>
@@ -294,8 +283,7 @@ Use `<sl-divider>` to group listbox items visually. You can also use `<small>` t
```
```jsx:react
import SlOption from '@shoelace-style/shoelace/dist/react/option';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSelect>
@@ -338,8 +326,7 @@ Use the `size` attribute to change a select's size. Note that size does not appl
```
```jsx:react
import SlOption from '@shoelace-style/shoelace/dist/react/option';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -423,9 +410,7 @@ Use the `prefix` slot to prepend an icon to the control.
```
```jsx:react
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import SlOption from '@shoelace-style/shoelace/dist/react/option';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import { SlIcon, SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>

View File

@@ -56,7 +56,7 @@ Skeletons try not to be opinionated, as there are endless possibilities for desi
```
```jsx:react
import SlSkeleton from '@shoelace-style/shoelace/dist/react/skeleton';
import { SlSkeleton } from '@shoelace-style/shoelace/dist/react';
const css = `
.skeleton-overview header {
@@ -139,7 +139,7 @@ There are two built-in effects, `sheen` and `pulse`. Effects are intentionally s
```
```jsx:react
import SlSkeleton from '@shoelace-style/shoelace/dist/react/skeleton';
import { SlSkeleton } from '@shoelace-style/shoelace/dist/react';
const css = `
.skeleton-effects {
@@ -200,7 +200,7 @@ Use multiple skeletons and some clever styles to simulate paragraphs.
```
```jsx:react
import SlSkeleton from '@shoelace-style/shoelace/dist/react/skeleton';
import { SlSkeleton } from '@shoelace-style/shoelace/dist/react';
const css = `
.skeleton-paragraphs sl-skeleton {
@@ -265,7 +265,7 @@ Set a matching width and height to make a circle, square, or rounded avatar skel
```
```jsx:react
import SlSkeleton from '@shoelace-style/shoelace/dist/react/skeleton';
import { SlSkeleton } from '@shoelace-style/shoelace/dist/react';
const css = `
.skeleton-avatars sl-skeleton {
@@ -360,7 +360,7 @@ Use the `--border-radius` custom property to make circles, squares, and rectangl
```
```jsx:react
import SlSkeleton from '@shoelace-style/shoelace/dist/react/skeleton';
import { SlSkeleton } from '@shoelace-style/shoelace/dist/react';
const css = `
.skeleton-shapes sl-skeleton {
@@ -423,7 +423,7 @@ Set the `--color` and `--sheen-color` custom properties to adjust the skeleton's
{% raw %}
```jsx:react
import SlSkeleton from '@shoelace-style/shoelace/dist/react/skeleton';
import { SlSkeleton } from '@shoelace-style/shoelace/dist/react';
const css = `
.skeleton-avatars sl-skeleton {

View File

@@ -10,7 +10,7 @@ layout: component
```
```jsx:react
import SlSpinner from '@shoelace-style/shoelace/dist/react/spinner';
import { SlSpinner } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlSpinner />;
```
@@ -30,7 +30,7 @@ Spinners are sized based on the current font size. To change their size, set the
{% raw %}
```jsx:react
import SlSpinner from '@shoelace-style/shoelace/dist/react/spinner';
import { SlSpinner } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -54,7 +54,7 @@ The width of the spinner's track can be changed by setting the `--track-width` c
{% raw %}
```jsx:react
import SlSpinner from '@shoelace-style/shoelace/dist/react/spinner';
import { SlSpinner } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSpinner
@@ -79,7 +79,7 @@ The spinner's colors can be changed by setting the `--indicator-color` and `--tr
{% raw %}
```jsx:react
import SlSpinner from '@shoelace-style/shoelace/dist/react/spinner';
import { SlSpinner } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSpinner

View File

@@ -9,13 +9,13 @@ layout: component
<sl-split-panel>
<div
slot="start"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
Start
</div>
<div
slot="end"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
End
</div>
@@ -25,7 +25,7 @@ layout: component
{% raw %}
```jsx:react
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';
import { SlSplitPanel } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSplitPanel>
@@ -69,13 +69,13 @@ To set the initial position, use the `position` attribute. If no position is pro
<sl-split-panel position="75">
<div
slot="start"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
Start
</div>
<div
slot="end"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
End
</div>
@@ -90,13 +90,13 @@ To set the initial position in pixels instead of a percentage, use the `position
<sl-split-panel position-in-pixels="150">
<div
slot="start"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
Start
</div>
<div
slot="end"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
End
</div>
@@ -106,7 +106,7 @@ To set the initial position in pixels instead of a percentage, use the `position
{% raw %}
```jsx:react
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';
import { SlSplitPanel } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSplitPanel position="200">
@@ -148,13 +148,13 @@ Add the `vertical` attribute to render the split panel in a vertical orientation
<sl-split-panel vertical style="height: 400px;">
<div
slot="start"
style="height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
Start
</div>
<div
slot="end"
style="height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
End
</div>
@@ -164,7 +164,7 @@ Add the `vertical` attribute to render the split panel in a vertical orientation
{% raw %}
```jsx:react
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';
import { SlSplitPanel } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSplitPanel vertical style={{ height: '400px' }}>
@@ -207,13 +207,13 @@ To snap panels at specific positions while dragging, add the `snap` attribute wi
<sl-split-panel snap="100px 50%">
<div
slot="start"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
Start
</div>
<div
slot="end"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
End
</div>
@@ -252,7 +252,7 @@ To snap panels at specific positions while dragging, add the `snap` attribute wi
{% raw %}
```jsx:react
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';
import { SlSplitPanel } from '@shoelace-style/shoelace/dist/react';
const css = `
.split-panel-snapping {
@@ -328,13 +328,13 @@ Add the `disabled` attribute to prevent the divider from being repositioned.
<sl-split-panel disabled>
<div
slot="start"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
Start
</div>
<div
slot="end"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
End
</div>
@@ -344,7 +344,7 @@ Add the `disabled` attribute to prevent the divider from being repositioned.
{% raw %}
```jsx:react
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';
import { SlSplitPanel } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSplitPanel disabled>
@@ -389,13 +389,13 @@ Try resizing the example below with each option and notice how the panels respon
<sl-split-panel>
<div
slot="start"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
Start
</div>
<div
slot="end"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
End
</div>
@@ -421,9 +421,7 @@ Try resizing the example below with each option and notice how the panels respon
```jsx:react
import { useState } from 'react';
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import { SlSplitPanel, SlSelect, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [primary, setPrimary] = useState('');
@@ -484,13 +482,13 @@ This examples demonstrates how you can ensure both panels are at least 150px usi
<sl-split-panel style="--min: 150px; --max: calc(100% - 150px);">
<div
slot="start"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
Start
</div>
<div
slot="end"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
End
</div>
@@ -500,7 +498,7 @@ This examples demonstrates how you can ensure both panels are at least 150px usi
{% raw %}
```jsx:react
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';
import { SlSplitPanel } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSplitPanel style={{ '--min': '150px', '--max': 'calc(100% - 150px)' }}>
@@ -542,7 +540,7 @@ Create complex layouts that can be repositioned independently by nesting split p
<sl-split-panel>
<div
slot="start"
style="height: 400px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden"
style="height: 400px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
Start
</div>
@@ -550,13 +548,13 @@ Create complex layouts that can be repositioned independently by nesting split p
<sl-split-panel vertical style="height: 400px;">
<div
slot="start"
style="height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden"
style="height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
Top
</div>
<div
slot="end"
style="height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden"
style="height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
Bottom
</div>
@@ -568,7 +566,7 @@ Create complex layouts that can be repositioned independently by nesting split p
{% raw %}
```jsx:react
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';
import { SlSplitPanel } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSplitPanel>
@@ -627,13 +625,13 @@ You can target the `divider` part to apply CSS properties to the divider. To add
<sl-icon slot="divider" name="grip-vertical"></sl-icon>
<div
slot="start"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
Start
</div>
<div
slot="end"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
End
</div>
@@ -643,8 +641,7 @@ You can target the `divider` part to apply CSS properties to the divider. To add
{% raw %}
```jsx:react
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlSplitPanel, SlIcon } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSplitPanel style={{ '--divider-width': '20px' }}>
@@ -687,13 +684,13 @@ Here's a more elaborate example that changes the divider's color and width and a
<sl-icon slot="divider" name="grip-vertical"></sl-icon>
<div
slot="start"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
Start
</div>
<div
slot="end"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
>
End
</div>
@@ -731,8 +728,7 @@ Here's a more elaborate example that changes the divider's color and width and a
{% raw %}
```jsx:react
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import { SlSplitPanel, SlIcon } from '@shoelace-style/shoelace/dist/react';
const css = `
.split-panel-divider sl-split-panel {

View File

@@ -10,7 +10,7 @@ layout: component
```
```jsx:react
import SlSwitch from '@shoelace-style/shoelace/dist/react/switch';
import { SlSwitch } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlSwitch>Switch</SlSwitch>;
```
@@ -30,7 +30,7 @@ Use the `checked` attribute to activate the switch.
```
```jsx:react
import SlSwitch from '@shoelace-style/shoelace/dist/react/switch';
import { SlSwitch } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlSwitch checked>Checked</SlSwitch>;
```
@@ -44,7 +44,7 @@ Use the `disabled` attribute to disable the switch.
```
```jsx:react
import SlSwitch from '@shoelace-style/shoelace/dist/react/switch';
import { SlSwitch } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlSwitch disabled>Disabled</SlSwitch>;
```
@@ -62,7 +62,7 @@ Use the `size` attribute to change a switch's size.
```
```jsx:react
import SlSwitch from '@shoelace-style/shoelace/dist/react/switch';
import { SlSwitch } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -86,7 +86,7 @@ Use the available custom properties to change how the switch is styled.
{% raw %}
```jsx:react
import SlSwitch from '@shoelace-style/shoelace/dist/react/switch';
import { SlSwitch } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlSwitch

View File

@@ -22,9 +22,7 @@ Tab groups make use of [tabs](/components/tab) and [tab panels](/components/tab-
```
```jsx:react
import SlTab from '@shoelace-style/shoelace/dist/react/tab';
import SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';
import SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';
import { SlTab, SlTabGroup, SlTabPanel } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTabGroup>
@@ -70,9 +68,7 @@ Tabs can be shown on the bottom by setting `placement` to `bottom`.
```
```jsx:react
import SlTab from '@shoelace-style/shoelace/dist/react/tab';
import SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';
import SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';
import { SlTab, SlTabGroup, SlTabPanel } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTabGroup placement="bottom">
@@ -116,9 +112,7 @@ Tabs can be shown on the starting side by setting `placement` to `start`.
```
```jsx:react
import SlTab from '@shoelace-style/shoelace/dist/react/tab';
import SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';
import SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';
import { SlTab, SlTabGroup, SlTabPanel } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTabGroup placement="start">
@@ -162,9 +156,7 @@ Tabs can be shown on the ending side by setting `placement` to `end`.
```
```jsx:react
import SlTab from '@shoelace-style/shoelace/dist/react/tab';
import SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';
import SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';
import { SlTab, SlTabGroup, SlTabPanel } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTabGroup placement="end">
@@ -226,9 +218,7 @@ Add the `closable` attribute to a tab to show a close button. This example shows
```
```jsx:react
import SlTab from '@shoelace-style/shoelace/dist/react/tab';
import SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';
import SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';
import { SlTab, SlTabGroup, SlTabPanel } from '@shoelace-style/shoelace/dist/react';
const App = () => {
function handleClose(event) {
@@ -320,9 +310,7 @@ When there are more tabs than horizontal space allows, the nav will be scrollabl
```
```jsx:react
import SlTab from '@shoelace-style/shoelace/dist/react/tab';
import SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';
import SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';
import { SlTab, SlTabGroup, SlTabPanel } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTabGroup>
@@ -430,9 +418,7 @@ When focused, keyboard users can press [[Left]] or [[Right]] to select the desir
```
```jsx:react
import SlTab from '@shoelace-style/shoelace/dist/react/tab';
import SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';
import SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';
import { SlTab, SlTabGroup, SlTabPanel } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTabGroup activation="manual">

View File

@@ -20,9 +20,7 @@ layout: component
```
```jsx:react
import SlTab from '@shoelace-style/shoelace/dist/react/tab';
import SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';
import SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';
import { SlTab, SlTabGroup, SlTabPanel } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTabGroup>

View File

@@ -13,7 +13,7 @@ layout: component
```
```jsx:react
import SlTab from '@shoelace-style/shoelace/dist/react/tab';
import { SlTab } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>

View File

@@ -14,7 +14,7 @@ layout: component
```
```jsx:react
import SlTag from '@shoelace-style/shoelace/dist/react/tag';
import { SlTag } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -40,7 +40,7 @@ Use the `size` attribute to change a tab's size.
```
```jsx:react
import SlTag from '@shoelace-style/shoelace/dist/react/tag';
import { SlTag } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -62,7 +62,7 @@ Use the `pill` attribute to give tabs rounded edges.
```
```jsx:react
import SlTag from '@shoelace-style/shoelace/dist/react/tag';
import { SlTag } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -108,7 +108,7 @@ Use the `removable` attribute to add a remove button to the tag.
```
```jsx:react
import SlTag from '@shoelace-style/shoelace/dist/react/tag';
import { SlTag } from '@shoelace-style/shoelace/dist/react';
const css = `
.tags-removable sl-tag {

View File

@@ -10,7 +10,7 @@ layout: component
```
```jsx:react
import SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlTextarea />;
```
@@ -30,7 +30,7 @@ Use the `label` attribute to give the textarea an accessible label. For labels t
```
```jsx:react
import SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlTextarea label="Comments" />;
```
@@ -44,7 +44,7 @@ Add descriptive help text to a textarea with the `help-text` attribute. For help
```
```jsx:react
import SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlTextarea label="Feedback" help-text="Please tell us what you think." />;
```
@@ -58,7 +58,7 @@ Use the `rows` attribute to change the number of text rows that get shown.
```
```jsx:react
import SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlTextarea rows={2} />;
```
@@ -72,7 +72,7 @@ Use the `placeholder` attribute to add a placeholder.
```
```jsx:react
import SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlTextarea placeholder="Type something" />;
```
@@ -86,7 +86,7 @@ Add the `filled` attribute to draw a filled textarea.
```
```jsx:react
import SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlTextarea placeholder="Type something" filled />;
```
@@ -100,7 +100,7 @@ Use the `disabled` attribute to disable a textarea.
```
```jsx:react
import SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlTextarea placeholder="Textarea" disabled />;
```
@@ -118,7 +118,7 @@ Use the `size` attribute to change a textarea's size.
```
```jsx:react
import SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<>
@@ -140,7 +140,7 @@ By default, textareas can be resized vertically by the user. To prevent resizing
```
```jsx:react
import SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlTextarea resize="none" />;
```
@@ -154,7 +154,7 @@ Textareas will automatically resize to expand to fit their content when `resize`
```
```jsx:react
import SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
const App = () => <SlTextarea resize="auto" />;
```

View File

@@ -16,8 +16,7 @@ Tooltips use `display: contents` so they won't interfere with how elements are p
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';
import { SlButton, SlTooltip } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTooltip content="This is a tooltip">
@@ -126,8 +125,7 @@ Use the `placement` attribute to set the preferred placement of the tooltip.
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';
import { SlButton, SlTooltip } from '@shoelace-style/shoelace/dist/react';
const css = `
.tooltip-placement-example {
@@ -237,8 +235,7 @@ Set the `trigger` attribute to `click` to toggle the tooltip on click instead of
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';
import { SlButton, SlTooltip } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTooltip content="Click again to dismiss" trigger="click">
@@ -270,9 +267,7 @@ Tooltips can be controller programmatically by setting the `trigger` attribute t
```jsx:react
import { useState } from 'react';
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';
import { SlAvatar, SlButton, SlTooltip } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [open, setOpen] = useState(false);
@@ -306,8 +301,7 @@ You can control the size of tooltip arrows by overriding the `--sl-tooltip-arrow
{% raw %}
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';
import { SlButton, SlTooltip } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<div style={{ '--sl-tooltip-arrow-size': '0' }}>
@@ -345,8 +339,7 @@ Use the `content` slot to create tooltips with HTML content. Tooltips are design
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';
import { SlButton, SlTooltip } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTooltip>
@@ -372,8 +365,7 @@ Use the `--max-width` custom property to change the width the tooltip can grow t
{% raw %}
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';
import { SlButton, SlTooltip } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTooltip style={{ '--max-width': '80px' }} content="This tooltip will wrap after only 80 pixels.">
@@ -410,8 +402,7 @@ Tooltips will be clipped if they're inside a container that has `overflow: auto|
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';
import { SlButton, SlTooltip } from '@shoelace-style/shoelace/dist/react';
const css = `
.tooltip-hoist {

View File

@@ -20,8 +20,7 @@ layout: component
<!-- prettier-ignore -->
```jsx:react
import SlTree from '@shoelace-style/shoelace/dist/react/tree';
import SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';
import { SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTree>
@@ -63,8 +62,7 @@ A tree item can contain other tree items. This allows the node to be expanded or
<!-- prettier-ignore -->
```jsx:react
import SlTree from '@shoelace-style/shoelace/dist/react/tree';
import SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';
import { SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTree>
@@ -104,8 +102,7 @@ Use the `selected` attribute to select a tree item initially.
<!-- prettier-ignore -->
```jsx:react
import SlTree from '@shoelace-style/shoelace/dist/react/tree';
import SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';
import { SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTree>
@@ -145,8 +142,7 @@ Use the `expanded` attribute to expand a tree item initially.
<!-- prettier-ignore -->
```jsx:react
import SlTree from '@shoelace-style/shoelace/dist/react/tree';
import SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';
import { SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTree>

View File

@@ -37,8 +37,7 @@ layout: component
<!-- prettier-ignore -->
```jsx:react
import SlTree from '@shoelace-style/shoelace/dist/react/tree';
import SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';
import { SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTree>
@@ -119,8 +118,7 @@ The `selection` attribute lets you change the selection behavior of the tree.
<!-- prettier-ignore -->
```jsx:react
import SlTree from '@shoelace-style/shoelace/dist/react/tree';
import SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';
import { SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [selection, setSelection] = useState('single');
@@ -199,8 +197,7 @@ Indent guides can be drawn by setting `--indent-guide-width`. You can also chang
<!-- prettier-ignore -->
```jsx:react
import SlTree from '@shoelace-style/shoelace/dist/react/tree';
import SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';
import { SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTree class="tree-with-lines" style={{ '--indent-guide-width': '1px' }}>
@@ -268,8 +265,7 @@ If you want to disable this behavior after the first load, simply remove the `la
```
```jsx:react
import SlTree from '@shoelace-style/shoelace/dist/react/tree';
import SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';
import { SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [childItems, setChildItems] = useState([]);
@@ -344,8 +340,7 @@ Use the `expand-icon` and `collapse-icon` slots to change the expand and collaps
<!-- prettier-ignore -->
```jsx:react
import SlTree from '@shoelace-style/shoelace/dist/react/tree';
import SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';
import { SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTree>
@@ -429,9 +424,7 @@ Decorative icons can be used before labels to provide hints for each node.
```
```jsx:react
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
import SlTree from '@shoelace-style/shoelace/dist/react/tree';
import SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';
import { SlIcon, SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
const App = () => {
return (

View File

@@ -39,28 +39,13 @@ Now you can start using components!
Every Shoelace component is available to import as a React component. Note that we're importing the `<SlButton>` _React component_ instead of the `<sl-button>` _custom element_ in the example below.
```jsx
import SlButton from '@shoelace-style/shoelace/%NPMDIR%/react/button';
import { SlButton } from '@shoelace-style/shoelace/%NPMDIR%/react';
const MyComponent = () => <SlButton variant="primary">Click me</SlButton>;
export default MyComponent;
```
#### Notes about tree shaking
Previously, it was recommended to import from a single entrypoint like so:
```jsx
import { SlButton } from '@shoelace-style/shoelace/%NPMDIR%/react';
```
However, tree-shaking extra Shoelace components proved to be a challenge. As a result, we now recommend cherry-picking components you want to use, rather than importing from a single entrypoint.
```diff
- import { SlButton } from '@shoelace-style/shoelace/%NPMDIR%/react';
+ import SlButton from '@shoelace-style/shoelace/%NPMDIR%/react/button';
```
You can find a copy + paste import for each component in the "importing" section of its documentation.
### Event Handling
@@ -71,7 +56,7 @@ Here's how you can bind the input's value to a state variable.
```jsx
import { useState } from 'react';
import SlInput from '@shoelace-style/shoelace/%NPMDIR%/react/input';
import { SlInput } from '@shoelace-style/shoelace/%NPMDIR%/react';
function MyComponent() {
const [value, setValue] = useState('');
@@ -86,7 +71,7 @@ If you're using TypeScript, it's important to note that `event.target` will be a
```tsx
import { useState } from 'react';
import SlInput from '@shoelace-style/shoelace/%NPMDIR%/react/input';
import { SlInput } from '@shoelace-style/shoelace/%NPMDIR%/react';
import type SlInputElement from '@shoelace-style/shoelace/%NPMDIR%/components/input/input';
function MyComponent() {
@@ -98,25 +83,6 @@ function MyComponent() {
export default MyComponent;
```
You can also import the event type for use in your callbacks, shown below.
```tsx
import { useCallback, useState } from 'react';
import SlInput, { type SlInputEvent } from '@shoelace-style/shoelace/%NPMDIR%/react/input';
import type SlInputElement from '@shoelace-style/shoelace/%NPMDIR%/components/input/input';
function MyComponent() {
const [value, setValue] = useState('');
const onInput = useCallback((event: SlInputEvent) => {
setValue(event.detail);
}, []);
return <SlInput value={value} onSlInput={event => setValue((event.target as SlInputElement).value)} />;
}
export default MyComponent;
```
## Testing with Jest
Testing with web components can be challenging if your test environment runs in a Node environment (i.e. it doesn't run in a real browser). Fortunately, [Jest](https://jestjs.io/) has made a number of strides to support web components and provide additional browser APIs. However, it's still not a complete replication of a browser environment.
@@ -174,7 +140,7 @@ To fix this, add the following to your `package.json` which tells the transpiler
```js
{
"jest": {
"transformIgnorePatterns": ["node_modules/(?!(@shoelace))"]
"transformIgnorePatterns": ["node_modules/?!(@shoelace)"]
}
}
```

View File

@@ -80,31 +80,15 @@ The form will not be submitted if a required field is incomplete.
<script type="module">
const form = document.querySelector('.input-validation-required');
// Wait for controls to be defined before attaching form listeners
await Promise.all([
customElements.whenDefined('sl-button'),
customElements.whenDefined('sl-checkbox'),
customElements.whenDefined('sl-input'),
customElements.whenDefined('sl-option'),
customElements.whenDefined('sl-select'),
customElements.whenDefined('sl-textarea')
]).then(() => {
form.addEventListener('submit', event => {
event.preventDefault();
alert('All fields are valid!');
});
form.addEventListener('submit', event => {
event.preventDefault();
alert('All fields are valid!');
});
</script>
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';
import { SlButton, SlCheckbox, SlInput, SlMenuItem, SlSelect, SlTextarea } from '@shoelace-style/shoelace/dist/react';
const App = () => {
function handleSubmit(event) {
@@ -150,23 +134,15 @@ To restrict a value to a specific [pattern](https://developer.mozilla.org/en-US/
<script type="module">
const form = document.querySelector('.input-validation-pattern');
// Wait for controls to be defined before attaching form listeners
await Promise.all([
customElements.whenDefined('sl-button'),
customElements.whenDefined('sl-input')
]).then(() => {
form.addEventListener('submit', event => {
event.preventDefault();
alert('All fields are valid!');
});
form.addEventListener('submit', event => {
event.preventDefault();
alert('All fields are valid!');
});
</script>
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlButton, SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => {
function handleSubmit(event) {
@@ -202,23 +178,15 @@ Some input types will automatically trigger constraints, such as `email` and `ur
<script type="module">
const form = document.querySelector('.input-validation-type');
// Wait for controls to be defined before attaching form listeners
await Promise.all([
customElements.whenDefined('sl-button'),
customElements.whenDefined('sl-input')
]).then(() => {
form.addEventListener('submit', event => {
event.preventDefault();
alert('All fields are valid!');
});
form.addEventListener('submit', event => {
event.preventDefault();
alert('All fields are valid!');
});
</script>
```
```jsx:react
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlButton, SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => {
function handleSubmit(event) {
@@ -256,31 +224,24 @@ To create a custom validation error, pass a non-empty string to the `setCustomVa
const form = document.querySelector('.input-validation-custom');
const input = form.querySelector('sl-input');
// Wait for controls to be defined before attaching form listeners
await Promise.all([
customElements.whenDefined('sl-button'),
customElements.whenDefined('sl-input')
]).then(() => {
form.addEventListener('submit', event => {
event.preventDefault();
alert('All fields are valid!');
});
form.addEventListener('submit', event => {
event.preventDefault();
alert('All fields are valid!');
});
input.addEventListener('sl-input', () => {
if (input.value === 'shoelace') {
input.setCustomValidity('');
} else {
input.setCustomValidity("Hey, you're supposed to type 'shoelace' before submitting this!");
}
});
input.addEventListener('sl-input', () => {
if (input.value === 'shoelace') {
input.setCustomValidity('');
} else {
input.setCustomValidity("Hey, you're supposed to type 'shoelace' before submitting this!");
}
});
</script>
```
```jsx:react
import { useRef, useState } from 'react';
import SlButton from '@shoelace-style/shoelace/dist/react/button';
import SlInput from '@shoelace-style/shoelace/dist/react/input';
import { SlButton, SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const input = useRef(null);
@@ -365,19 +326,9 @@ This example demonstrates custom validation styles using `data-user-invalid` and
<script type="module">
const form = document.querySelector('.validity-styles');
// Wait for controls to be defined before attaching form listeners
await Promise.all([
customElements.whenDefined('sl-button'),
customElements.whenDefined('sl-checkbox'),
customElements.whenDefined('sl-input'),
customElements.whenDefined('sl-option'),
customElements.whenDefined('sl-select')
]).then(() => {
form.addEventListener('submit', event => {
event.preventDefault();
alert('All fields are valid!');
});
form.addEventListener('submit', event => {
event.preventDefault();
alert('All fields are valid!');
});
</script>
@@ -466,39 +417,33 @@ To disable the browser's error messages, you need to cancel the `sl-invalid` eve
const form = document.querySelector('.inline-validation');
const nameError = document.querySelector('#name-error');
// Wait for controls to be defined before attaching form listeners
await Promise.all([
customElements.whenDefined('sl-button'),
customElements.whenDefined('sl-input')
]).then(() => {
// A form control is invalid
form.addEventListener(
'sl-invalid',
event => {
// Suppress the browser's constraint validation message
event.preventDefault();
nameError.textContent = `Error: ${event.target.validationMessage}`;
nameError.hidden = false;
event.target.focus();
},
{ capture: true } // you must use capture since sl-invalid doesn't bubble!
);
// Handle form submit
form.addEventListener('submit', event => {
// A form control is invalid
form.addEventListener(
'sl-invalid',
event => {
// Suppress the browser's constraint validation message
event.preventDefault();
nameError.hidden = true;
nameError.textContent = '';
setTimeout(() => alert('All fields are valid'), 50);
});
// Handle form reset
form.addEventListener('reset', event => {
nameError.hidden = true;
nameError.textContent = '';
});
nameError.textContent = `Error: ${event.target.validationMessage}`;
nameError.hidden = false;
event.target.focus();
},
{ capture: true } // you must use capture since sl-invalid doesn't bubble!
);
// Handle form submit
form.addEventListener('submit', event => {
event.preventDefault();
nameError.hidden = true;
nameError.textContent = '';
setTimeout(() => alert('All fields are valid'), 50);
});
// Handle form reset
form.addEventListener('reset', event => {
nameError.hidden = true;
nameError.textContent = '';
});
</script>

View File

@@ -112,29 +112,15 @@ However, if you're [cherry picking](#cherry-picking) or [bundling](#bundling) Sh
```
:::tip
An easy way to make sure the base path is configured properly is to check if [icons](/components/icon) are loading.
When setting a basePath, and easy way to check if it was down properly is by checking if an icon exists.
For example, if I set the basePath to `/dist`, I should be able to go to:
`https://<my-site>/dist/assets/icons/arrow-left.svg` and the browser should show me the SVG.
Shoelace also exports a `getBasePath()` method you can use to reference assets.
:::
### Referencing Assets
Most of the magic behind assets is handled internally by Shoelace, but if you need to reference the base path for any reason, the same module exports a function called `getBasePath()`. An optional string argument can be passed, allowing you to get the full path to any asset.
```html
<script type="module">
import { getBasePath, setBasePath } from '@shoelace-style/shoelace/%NPMDIR%/utilities/base-path.js';
setBasePath('/path/to/assets');
// ...
// Get the base path, e.g. /path/to/assets
const basePath = getBasePath();
// Get the path to an asset, e.g. /path/to/assets/file.ext
const assetPath = getBasePath('file.ext');
</script>
```
## Cherry Picking
Cherry picking can be done from [the CDN](#cdn-installation-easiest) or from [npm](#npm-installation). This approach will load only the components you need up front, while limiting the number of files the browser has to download. The disadvantage is that you need to import each individual component.
@@ -196,21 +182,6 @@ setBasePath('/path/to/shoelace/%NPMDIR%
Component modules include side effects for registration purposes. Because of this, importing directly from `@shoelace-style/shoelace` may result in a larger bundle size than necessary. For optimal tree shaking, always cherry pick, i.e. import components and utilities from their respective files, as shown above.
:::
### Avoiding auto-registering imports
By default, imports to components will auto-register themselves. This may not be ideal in all cases. To import just the component's class without auto-registering it's tag we can do the following:
```diff
- import SlButton from '@shoelace-style/shoelace/%NPMDIR%/components/button/button.js';
+ import SlButton from '@shoelace-style/shoelace/%NPMDIR%/components/button/button.component.js';
```
Notice how the import ends with `.component.js`. This is the current convention to convey the import does not register itself.
:::danger
While you can override the class or re-register the shoelace class under a different tag name, if you do so, many components wont work as expected.
:::
## The difference between CDN and npm
You'll notice that the CDN links all start with `/%CDNDIR%/<path>` and npm imports use `/%NPMDIR%/<path>`. The `/%CDNDIR%` files are bundled separately from the `/%NPMDIR%` files. The `/%CDNDIR%` files come pre-bundled, which means all dependencies are inlined so you do not need to worry about loading additional libraries. The `/%NPMDIR%` files **DO NOT** come pre-bundled, allowing your bundler of choice to more efficiently deduplicate dependencies, resulting in smaller bundles and optimal code sharing.

View File

@@ -27,8 +27,6 @@ toc: false
</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)<br>
@@ -36,8 +34,6 @@ toc: false
[![Twitter](https://img.shields.io/badge/Twitter-Follow-00acee.svg?style=flat-square&logo=twitter&logoColor=white)](https://twitter.com/shoelace_style)
[![Sponsor](https://img.shields.io/badge/GitHub-Code-232323.svg?style=flat-square&logo=github&logoColor=white)](https://github.com/shoelace-style/shoelace)
</div>
## Quick Start
Add the following code to your page.

View File

@@ -14,30 +14,8 @@ New versions of Shoelace are released as-needed and generally occur when a criti
## Next
- Added the `<sl-copy-button>` component [#1473]
- Fixed a bug in `<sl-dropdown>` where pressing [[Up]] or [[Down]] when focused on the trigger wouldn't focus the first/last menu items [#1472]
- Fixed a bug that caused key presses in text fields to be hijacked when used inside `<sl-tree>` [#1492]
- Fixed an upstream bug that caused React CodePen examples to stop working
- Improved the behavior of the clear button in `<sl-input>` to prevent the component's width from shifting when toggled [#1496]
- Improved `<sl-tooltip>` to prevent user selection so the tooltip doesn't get highlighted when dragging selections
- Moved tag type definitions out of component files and into definition files
- Removed `sideEffects` key from `package.json`. Update React docs to use cherry-picking. [#1485]
- Updated Bootstrap Icons to 1.10.5
## 2.6.0
- Added JSDoc comments to React Wrappers for better documentation when hovering a component. [#1450]
- Added `displayName` to React Wrappers for better debugging. [#1450]
- Added non-auto-registering routes for Components to fix a number of issues around auto-registration. [#1450]
- Added a console warning if you attempt to register the same Shoelace component twice. [#1450]
- Added tests for `<sl-qr-code>` [#1416]
- Added support for pressing [[Space]] to select/toggle selected `<sl-menu-item>` elements [#1429]
- Added support for virtual elements in `<sl-popup>` [#1449]
- Added the `spinner` part to `<sl-button>` [#1460]
- Added a `shoelace.js` and `shoelace-autoloader.js` to exportmaps. [#1450]
- Added types to events emitted by React wrapped components [#1419]
- Fixed React component treeshaking by introducing `sideEffects` key in `package.json`. [#1450]
- Fixed a bug in `<sl-tree>` where it was auto-defining `<sl-tree-item>`. [#1450]
- Fixed a bug in focus trapping of modal elements like `<sl-dialog>`. We now manually handle focus ordering as well as added `offsetParent()` check for tabbable boundaries in Safari. Test cases added for `<sl-dialog>` inside a shadowRoot [#1403]
- Fixed a bug in `valueAsDate` on `<sl-input>` where it would always set `type="date"` for the underlying `<input>` element. It now falls back to the native browser implementation for the in-memory input. This may cause unexpected behavior if you're using `valueAsDate` on any input elements that aren't `type="date"`. [#1399]
- Fixed a bug in `<sl-qr-code>` where the `background` attribute was never passed to the QR code [#1416]
@@ -45,10 +23,7 @@ New versions of Shoelace are released as-needed and generally occur when a criti
- Fixed a bug in `<sl-carousel>` that caused navigation to work incorrectly in some case [#1420]
- Fixed a number of slots that incorrectly had aria- and/or role attributes directly on them [#1422]
- Fixed a bug in `<sl-tree>` that caused focus to be stolen when removing focused tree items [#1430]
- Fixed a bug in `<sl-dialog>` and `<sl-drawer>` that caused nested modals to respond too eagerly to the [[Esc]] key [#1457]
- Improved `<sl-details>` to use `<details>` internally for better semantics and to enable search to find in supportive browsers when collapsed [#1470]
- Updated ESLint and related plugins to the latest versions
- Changed the default entrypoint for jsDelivr to point to the autoloader. [#1450]
## 2.5.2

View File

@@ -367,6 +367,7 @@ Then use the following syntax for comments so they appear in the generated docs.
* @cssproperty --color: The component's text color.
* @cssproperty --background-color: The component's background color.
*/
@customElement('sl-example')
export default class SlExample {
// ...
}

118
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@shoelace-style/shoelace",
"version": "2.6.0",
"version": "2.5.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@shoelace-style/shoelace",
"version": "2.6.0",
"version": "2.5.2",
"license": "MIT",
"dependencies": {
"@ctrl/tinycolor": "^3.5.0",
@@ -30,18 +30,17 @@
"@web/test-runner": "^0.15.0",
"@web/test-runner-commands": "^0.6.5",
"@web/test-runner-playwright": "^0.9.0",
"bootstrap-icons": "^1.10.5",
"bootstrap-icons": "^1.10.3",
"browser-sync": "^2.29.3",
"cem-plugin-vs-code-custom-data-generator": "^1.4.1",
"chalk": "^5.2.0",
"change-case": "^4.1.2",
"command-line-args": "^5.2.1",
"comment-parser": "^1.3.1",
"cspell": "^6.18.1",
"custom-element-vs-code-integration": "^1.1.0",
"del": "^7.0.0",
"download": "^8.0.0",
"esbuild": "^0.18.2",
"esbuild-plugin-replace": "^1.4.0",
"eslint": "^8.44.0",
"eslint-plugin-chai-expect": "^3.0.0",
"eslint-plugin-chai-friendly": "^0.7.2",
@@ -3987,20 +3986,10 @@
}
},
"node_modules/bootstrap-icons": {
"version": "1.10.5",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.10.5.tgz",
"integrity": "sha512-oSX26F37V7QV7NCE53PPEL45d7EGXmBgHG3pDpZvcRaKVzWMqIRL9wcqJUyEha1esFtM3NJzvmxFXDxjJYD0jQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
]
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.10.3.tgz",
"integrity": "sha512-7Qvj0j0idEm/DdX9Q0CpxAnJYqBCFCiUI6qzSPYfERMcokVuV9Mdm/AJiVZI8+Gawe4h/l6zFcOzvV7oXCZArw==",
"dev": true
},
"node_modules/boxen": {
"version": "7.0.0",
@@ -4545,6 +4534,15 @@
"upper-case-first": "^2.0.2"
}
},
"node_modules/cem-plugin-vs-code-custom-data-generator": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/cem-plugin-vs-code-custom-data-generator/-/cem-plugin-vs-code-custom-data-generator-1.4.1.tgz",
"integrity": "sha512-mulzg6I2wJVNKCM9ml4ttxTnGK25kHHdkhX979vbrKwSIIplFnPOgGa0Sj14pQWnfDwbGr6pSbLgBmi4nVHFxA==",
"dev": true,
"dependencies": {
"prettier": "^2.7.1"
}
},
"node_modules/chai-a11y-axe": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/chai-a11y-axe/-/chai-a11y-axe-1.4.0.tgz",
@@ -5687,15 +5685,6 @@
"integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==",
"dev": true
},
"node_modules/custom-element-vs-code-integration": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/custom-element-vs-code-integration/-/custom-element-vs-code-integration-1.1.0.tgz",
"integrity": "sha512-M7f4zQIAzpdZGRcZpWmpONyf8zpiGZCU8U7z7s5q6460deIebLLQP/klTLLcI3XyWoCjUhwDwGJiZz9he8Y2ig==",
"dev": true,
"dependencies": {
"prettier": "^2.7.1"
}
},
"node_modules/custom-elements-manifest": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/custom-elements-manifest/-/custom-elements-manifest-1.0.0.tgz",
@@ -6888,15 +6877,6 @@
"@esbuild/win32-x64": "0.18.2"
}
},
"node_modules/esbuild-plugin-replace": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/esbuild-plugin-replace/-/esbuild-plugin-replace-1.4.0.tgz",
"integrity": "sha512-lP3ZAyzyRa5JXoOd59lJbRKNObtK8pJ/RO7o6vdjwLi71GfbL32NR22ZuS7/cLZkr10/L1lutoLma8E4DLngYg==",
"dev": true,
"dependencies": {
"magic-string": "^0.25.7"
}
},
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -11263,15 +11243,6 @@
"node": ">=12"
}
},
"node_modules/magic-string": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
"dev": true,
"dependencies": {
"sourcemap-codec": "^1.4.8"
}
},
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -15523,13 +15494,6 @@
"node": ">=0.10.0"
}
},
"node_modules/sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"deprecated": "Please use @jridgewell/sourcemap-codec instead",
"dev": true
},
"node_modules/spawn-please": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/spawn-please/-/spawn-please-2.0.1.tgz",
@@ -20206,9 +20170,9 @@
}
},
"bootstrap-icons": {
"version": "1.10.5",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.10.5.tgz",
"integrity": "sha512-oSX26F37V7QV7NCE53PPEL45d7EGXmBgHG3pDpZvcRaKVzWMqIRL9wcqJUyEha1esFtM3NJzvmxFXDxjJYD0jQ==",
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.10.3.tgz",
"integrity": "sha512-7Qvj0j0idEm/DdX9Q0CpxAnJYqBCFCiUI6qzSPYfERMcokVuV9Mdm/AJiVZI8+Gawe4h/l6zFcOzvV7oXCZArw==",
"dev": true
},
"boxen": {
@@ -20621,6 +20585,15 @@
"upper-case-first": "^2.0.2"
}
},
"cem-plugin-vs-code-custom-data-generator": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/cem-plugin-vs-code-custom-data-generator/-/cem-plugin-vs-code-custom-data-generator-1.4.1.tgz",
"integrity": "sha512-mulzg6I2wJVNKCM9ml4ttxTnGK25kHHdkhX979vbrKwSIIplFnPOgGa0Sj14pQWnfDwbGr6pSbLgBmi4nVHFxA==",
"dev": true,
"requires": {
"prettier": "^2.7.1"
}
},
"chai-a11y-axe": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/chai-a11y-axe/-/chai-a11y-axe-1.4.0.tgz",
@@ -21507,15 +21480,6 @@
"integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==",
"dev": true
},
"custom-element-vs-code-integration": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/custom-element-vs-code-integration/-/custom-element-vs-code-integration-1.1.0.tgz",
"integrity": "sha512-M7f4zQIAzpdZGRcZpWmpONyf8zpiGZCU8U7z7s5q6460deIebLLQP/klTLLcI3XyWoCjUhwDwGJiZz9he8Y2ig==",
"dev": true,
"requires": {
"prettier": "^2.7.1"
}
},
"custom-elements-manifest": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/custom-elements-manifest/-/custom-elements-manifest-1.0.0.tgz",
@@ -22429,15 +22393,6 @@
"@esbuild/win32-x64": "0.18.2"
}
},
"esbuild-plugin-replace": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/esbuild-plugin-replace/-/esbuild-plugin-replace-1.4.0.tgz",
"integrity": "sha512-lP3ZAyzyRa5JXoOd59lJbRKNObtK8pJ/RO7o6vdjwLi71GfbL32NR22ZuS7/cLZkr10/L1lutoLma8E4DLngYg==",
"dev": true,
"requires": {
"magic-string": "^0.25.7"
}
},
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -25706,15 +25661,6 @@
"integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==",
"dev": true
},
"magic-string": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
"dev": true,
"requires": {
"sourcemap-codec": "^1.4.8"
}
},
"make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -29018,12 +28964,6 @@
}
}
},
"sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"dev": true
},
"spawn-please": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/spawn-please/-/spawn-please-2.0.1.tgz",

View File

@@ -1,7 +1,7 @@
{
"name": "@shoelace-style/shoelace",
"description": "A forward-thinking library of web components.",
"version": "2.6.0",
"version": "2.5.2",
"homepage": "https://github.com/shoelace-style/shoelace",
"author": "Cory LaViska",
"license": "MIT",
@@ -9,15 +9,12 @@
"web-types": "dist/web-types.json",
"type": "module",
"types": "dist/shoelace.d.ts",
"jsdelivr": "./cdn/shoelace-autoloader.js",
"exports": {
".": {
"types": "./dist/shoelace.d.ts",
"import": "./dist/shoelace.js"
},
"./dist/custom-elements.json": "./dist/custom-elements.json",
"./dist/shoelace.js": "./dist/shoelace.js",
"./dist/shoelace-autoloader.js": "./dist/shoelace-autoloader.js",
"./dist/themes/*": "./dist/themes/*",
"./dist/components/*": "./dist/components/*",
"./dist/utilities/*": "./dist/utilities/*",
@@ -88,18 +85,17 @@
"@web/test-runner": "^0.15.0",
"@web/test-runner-commands": "^0.6.5",
"@web/test-runner-playwright": "^0.9.0",
"bootstrap-icons": "^1.10.5",
"bootstrap-icons": "^1.10.3",
"browser-sync": "^2.29.3",
"cem-plugin-vs-code-custom-data-generator": "^1.4.1",
"chalk": "^5.2.0",
"change-case": "^4.1.2",
"command-line-args": "^5.2.1",
"comment-parser": "^1.3.1",
"cspell": "^6.18.1",
"custom-element-vs-code-integration": "^1.1.0",
"del": "^7.0.0",
"download": "^8.0.0",
"esbuild": "^0.18.2",
"esbuild-plugin-replace": "^1.4.0",
"eslint": "^8.44.0",
"eslint-plugin-chai-expect": "^3.0.0",
"eslint-plugin-chai-friendly": "^0.7.2",

View File

@@ -11,8 +11,6 @@ import getPort, { portNumbers } from 'get-port';
import ora from 'ora';
import util from 'util';
import * as path from 'path';
import { readFileSync } from 'fs';
import { replace } from 'esbuild-plugin-replace';
const { serve } = commandLineArgs([{ name: 'serve', type: Boolean }]);
const outdir = 'dist';
@@ -24,8 +22,6 @@ let childProcess;
let buildResults;
const bundleDirectories = [cdndir, outdir];
let packageData = JSON.parse(readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'));
const shoelaceVersion = JSON.stringify(packageData.version.toString());
//
// Runs 11ty and builds the docs. The returned promise resolves after the initial publish has completed. The child
@@ -112,18 +108,13 @@ async function buildTheSource() {
//
external: alwaysExternal,
splitting: true,
plugins: [
replace({
__SHOELACE_VERSION__: shoelaceVersion
})
]
plugins: []
};
const npmConfig = {
...cdnConfig,
bundle: false,
external: undefined,
minify: false,
packages: 'external',
outdir
};
@@ -272,16 +263,13 @@ if (serve) {
});
// Rebuild and reload when source files change
bs.watch('src/**/!(*.test).*').on('change', async filename => {
console.log('[build] File changed: ', filename);
bs.watch(['src/**/!(*.test).*']).on('change', async filename => {
try {
const isTheme = /^src\/themes/.test(filename);
const isStylesheet = /(\.css|\.styles\.ts)$/.test(filename);
// Rebuild the source
const rebuildResults = buildResults.map(result => result.rebuild());
await Promise.all(rebuildResults);
await Promise.all([buildResults.map(result => result.rebuild())]);
// Rebuild stylesheets when a theme file changes
if (isTheme) {

View File

@@ -32,7 +32,7 @@ await deleteAsync([iconDir]);
await fs.mkdir(iconDir, { recursive: true });
await Promise.all([
copy(`${srcPath}/icons`, iconDir),
copy(`${srcPath}/LICENSE`, path.join(iconDir, 'LICENSE')),
copy(`${srcPath}/LICENSE.md`, path.join(iconDir, 'LICENSE.md')),
copy(`${srcPath}/bootstrap-icons.svg`, './docs/assets/images/sprite.svg', { overwrite: true })
]);

View File

@@ -24,59 +24,25 @@ components.map(component => {
const tagWithoutPrefix = component.tagName.replace(/^sl-/, '');
const componentDir = path.join(reactDir, tagWithoutPrefix);
const componentFile = path.join(componentDir, 'index.ts');
const importPath = component.path.replace(/\.js$/, '.component.js');
const eventImports = (component.events || [])
.map(event => `import type { ${event.eventName} } from '../../../src/events/events';`)
.join('\n');
const eventExports = (component.events || [])
.map(event => `export type { ${event.eventName} } from '../../../src/events/events';`)
.join('\n');
const eventNameImport =
(component.events || []).length > 0 ? `import { type EventName } from '@lit-labs/react';` : ``;
const events = (component.events || [])
.map(event => `${event.reactName}: '${event.name}' as EventName<${event.eventName}>`)
.join(',\n');
const importPath = component.path;
const events = (component.events || []).map(event => `${event.reactName}: '${event.name}'`).join(',\n');
fs.mkdirSync(componentDir, { recursive: true });
const jsDoc = component.jsDoc || '';
const source = prettier.format(
`
import * as React from 'react';
import { createComponent } from '@lit-labs/react';
import Component from '../../${importPath}';
${eventNameImport}
${eventImports}
${eventExports}
const tagName = '${component.tagName}'
const component = createComponent({
tagName,
export default createComponent({
tagName: '${component.tagName}',
elementClass: Component,
react: React,
events: {
${events}
},
displayName: "${component.name}"
})
${jsDoc}
class SlComponent extends React.Component<Parameters<typeof component>[0]> {
constructor (...args: Parameters<typeof component>) {
super(...args)
Component.define(tagName)
}
render () {
const { children, ...props } = this.props
return React.createElement(component, props, children)
}
}
export default SlComponent;
});
`,
Object.assign(prettierConfig, {
parser: 'babel-ts'

View File

@@ -33,11 +33,6 @@ export default function (plop) {
{
type: 'add',
path: '../../src/components/{{ tagWithoutPrefix tag }}/{{ tagWithoutPrefix tag }}.ts',
templateFile: 'templates/component/define.hbs'
},
{
type: 'add',
path: '../../src/components/{{ tagWithoutPrefix tag }}/{{ tagWithoutPrefix tag }}.component.ts',
templateFile: 'templates/component/component.hbs'
},
{

View File

@@ -1,4 +1,4 @@
import { property } from 'lit/decorators.js';
import { customElement, property } from 'lit/decorators.js';
import { html } from 'lit';
import { LocalizeController } from '../../utilities/localize.js';
import { watch } from '../../internal/watch.js';
@@ -23,6 +23,7 @@ import type { CSSResultGroup } from 'lit';
*
* @cssproperty --example - An example CSS custom property.
*/
@customElement('{{ tag }}')
export default class {{ properCase tag }} extends ShoelaceElement {
static styles: CSSResultGroup = styles;
@@ -40,3 +41,9 @@ export default class {{ properCase tag }} extends ShoelaceElement {
return html` <slot></slot> `;
}
}
declare global {
interface HTMLElementTagNameMap {
'{{ tag }}': {{ properCase tag }};
}
}

View File

@@ -1,12 +0,0 @@
import {{ properCase tag }} from './{{ tagWithoutPrefix tag }}.component.js';
export * from './{{ tagWithoutPrefix tag }}.component.js';
export default {{ properCase tag }};
{{ properCase tag }}.define('{{ tag }}');
declare global {
interface HTMLElementTagNameMap {
'{{ tag }}': {{ properCase tag }};
}
}

View File

@@ -1,241 +0,0 @@
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 ShoelaceElement from '../../internal/shoelace-element.js';
import SlIconButton from '../icon-button/icon-button.component.js';
import styles from './alert.styles.js';
import type { CSSResultGroup } from 'lit';
const toastStack = Object.assign(document.createElement('div'), { className: 'sl-toast-stack' });
/**
* @summary Alerts are used to display important messages inline or as toast notifications.
* @documentation https://shoelace.style/components/alert
* @status stable
* @since 2.0
*
* @dependency sl-icon-button
*
* @slot - The alert's main content.
* @slot icon - An icon to show in the alert. Works best with `<sl-icon>`.
*
* @event sl-show - Emitted when the alert opens.
* @event sl-after-show - Emitted after the alert opens and all animations are complete.
* @event sl-hide - Emitted when the alert closes.
* @event sl-after-hide - Emitted after the alert closes and all animations are complete.
*
* @csspart base - The component's base wrapper.
* @csspart icon - The container that wraps the optional icon.
* @csspart message - The container that wraps the alert's main content.
* @csspart close-button - The close button, an `<sl-icon-button>`.
* @csspart close-button__base - The close button's exported `base` part.
*
* @animation alert.show - The animation to use when showing the alert.
* @animation alert.hide - The animation to use when hiding the alert.
*/
export default class SlAlert extends ShoelaceElement {
static styles: CSSResultGroup = styles;
static dependencies = { 'sl-icon-button': SlIconButton };
private autoHideTimeout: number;
private readonly hasSlotController = new HasSlotController(this, 'icon', 'suffix');
private readonly localize = new LocalizeController(this);
@query('[part~="base"]') base: HTMLElement;
/**
* Indicates whether or not the alert is open. You can toggle this attribute to show and hide the alert, or you can
* use the `show()` and `hide()` methods and this attribute will reflect the alert's open state.
*/
@property({ type: Boolean, reflect: true }) open = false;
/** Enables a close button that allows the user to dismiss the alert. */
@property({ type: Boolean, reflect: true }) closable = false;
/** The alert's theme variant. */
@property({ reflect: true }) variant: 'primary' | 'success' | 'neutral' | 'warning' | 'danger' = 'primary';
/**
* The length of time, in milliseconds, the alert will show before closing itself. If the user interacts with
* the alert before it closes (e.g. moves the mouse over it), the timer will restart. Defaults to `Infinity`, meaning
* the alert will not close on its own.
*/
@property({ type: Number }) duration = Infinity;
firstUpdated() {
this.base.hidden = !this.open;
}
private restartAutoHide() {
clearTimeout(this.autoHideTimeout);
if (this.open && this.duration < Infinity) {
this.autoHideTimeout = window.setTimeout(() => this.hide(), this.duration);
}
}
private handleCloseClick() {
this.hide();
}
private handleMouseMove() {
this.restartAutoHide();
}
@watch('open', { waitUntilFirstUpdate: true })
async handleOpenChange() {
if (this.open) {
// Show
this.emit('sl-show');
if (this.duration < Infinity) {
this.restartAutoHide();
}
await stopAnimations(this.base);
this.base.hidden = false;
const { keyframes, options } = getAnimation(this, 'alert.show', { dir: this.localize.dir() });
await animateTo(this.base, keyframes, options);
this.emit('sl-after-show');
} else {
// Hide
this.emit('sl-hide');
clearTimeout(this.autoHideTimeout);
await stopAnimations(this.base);
const { keyframes, options } = getAnimation(this, 'alert.hide', { dir: this.localize.dir() });
await animateTo(this.base, keyframes, options);
this.base.hidden = true;
this.emit('sl-after-hide');
}
}
@watch('duration')
handleDurationChange() {
this.restartAutoHide();
}
/** Shows the alert. */
async show() {
if (this.open) {
return undefined;
}
this.open = true;
return waitForEvent(this, 'sl-after-show');
}
/** Hides the alert */
async hide() {
if (!this.open) {
return undefined;
}
this.open = false;
return waitForEvent(this, 'sl-after-hide');
}
/**
* Displays the alert as a toast notification. This will move the alert out of its position in the DOM and, when
* dismissed, it will be removed from the DOM completely. By storing a reference to the alert, you can reuse it by
* calling this method again. The returned promise will resolve after the alert is hidden.
*/
async toast() {
return new Promise<void>(resolve => {
if (toastStack.parentElement === null) {
document.body.append(toastStack);
}
toastStack.appendChild(this);
// Wait for the toast stack to render
requestAnimationFrame(() => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- force a reflow for the initial transition
this.clientWidth;
this.show();
});
this.addEventListener(
'sl-after-hide',
() => {
toastStack.removeChild(this);
resolve();
// Remove the toast stack from the DOM when there are no more alerts
if (toastStack.querySelector('sl-alert') === null) {
toastStack.remove();
}
},
{ once: true }
);
});
}
render() {
return html`
<div
part="base"
class=${classMap({
alert: true,
'alert--open': this.open,
'alert--closable': this.closable,
'alert--has-icon': this.hasSlotController.test('icon'),
'alert--primary': this.variant === 'primary',
'alert--success': this.variant === 'success',
'alert--neutral': this.variant === 'neutral',
'alert--warning': this.variant === 'warning',
'alert--danger': this.variant === 'danger'
})}
role="alert"
aria-hidden=${this.open ? 'false' : 'true'}
@mousemove=${this.handleMouseMove}
>
<div part="icon" class="alert__icon">
<slot name="icon"></slot>
</div>
<div part="message" class="alert__message" aria-live="polite">
<slot></slot>
</div>
${this.closable
? html`
<sl-icon-button
part="close-button"
exportparts="base:close-button__base"
class="alert__close-button"
name="x-lg"
library="system"
label=${this.localize.term('close')}
@click=${this.handleCloseClick}
></sl-icon-button>
`
: ''}
</div>
`;
}
}
setDefaultAnimation('alert.show', {
keyframes: [
{ opacity: 0, scale: 0.8 },
{ opacity: 1, scale: 1 }
],
options: { duration: 250, easing: 'ease' }
});
setDefaultAnimation('alert.hide', {
keyframes: [
{ opacity: 1, scale: 1 },
{ opacity: 0, scale: 0.8 }
],
options: { duration: 250, easing: 'ease' }
});

View File

@@ -1,9 +1,245 @@
import SlAlert from './alert.component.js';
import '../icon-button/icon-button.js';
import { animateTo, stopAnimations } from '../../internal/animate.js';
import { classMap } from 'lit/directives/class-map.js';
import { customElement, property, query } from 'lit/decorators.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 { waitForEvent } from '../../internal/event.js';
import { watch } from '../../internal/watch.js';
import ShoelaceElement from '../../internal/shoelace-element.js';
import styles from './alert.styles.js';
import type { CSSResultGroup } from 'lit';
export * from './alert.component.js';
export default SlAlert;
const toastStack = Object.assign(document.createElement('div'), { className: 'sl-toast-stack' });
SlAlert.define('sl-alert');
/**
* @summary Alerts are used to display important messages inline or as toast notifications.
* @documentation https://shoelace.style/components/alert
* @status stable
* @since 2.0
*
* @dependency sl-icon-button
*
* @slot - The alert's main content.
* @slot icon - An icon to show in the alert. Works best with `<sl-icon>`.
*
* @event sl-show - Emitted when the alert opens.
* @event sl-after-show - Emitted after the alert opens and all animations are complete.
* @event sl-hide - Emitted when the alert closes.
* @event sl-after-hide - Emitted after the alert closes and all animations are complete.
*
* @csspart base - The component's base wrapper.
* @csspart icon - The container that wraps the optional icon.
* @csspart message - The container that wraps the alert's main content.
* @csspart close-button - The close button, an `<sl-icon-button>`.
* @csspart close-button__base - The close button's exported `base` part.
*
* @animation alert.show - The animation to use when showing the alert.
* @animation alert.hide - The animation to use when hiding the alert.
*/
@customElement('sl-alert')
export default class SlAlert extends ShoelaceElement {
static styles: CSSResultGroup = styles;
private autoHideTimeout: number;
private readonly hasSlotController = new HasSlotController(this, 'icon', 'suffix');
private readonly localize = new LocalizeController(this);
@query('[part~="base"]') base: HTMLElement;
/**
* Indicates whether or not the alert is open. You can toggle this attribute to show and hide the alert, or you can
* use the `show()` and `hide()` methods and this attribute will reflect the alert's open state.
*/
@property({ type: Boolean, reflect: true }) open = false;
/** Enables a close button that allows the user to dismiss the alert. */
@property({ type: Boolean, reflect: true }) closable = false;
/** The alert's theme variant. */
@property({ reflect: true }) variant: 'primary' | 'success' | 'neutral' | 'warning' | 'danger' = 'primary';
/**
* The length of time, in milliseconds, the alert will show before closing itself. If the user interacts with
* the alert before it closes (e.g. moves the mouse over it), the timer will restart. Defaults to `Infinity`, meaning
* the alert will not close on its own.
*/
@property({ type: Number }) duration = Infinity;
firstUpdated() {
this.base.hidden = !this.open;
}
private restartAutoHide() {
clearTimeout(this.autoHideTimeout);
if (this.open && this.duration < Infinity) {
this.autoHideTimeout = window.setTimeout(() => this.hide(), this.duration);
}
}
private handleCloseClick() {
this.hide();
}
private handleMouseMove() {
this.restartAutoHide();
}
@watch('open', { waitUntilFirstUpdate: true })
async handleOpenChange() {
if (this.open) {
// Show
this.emit('sl-show');
if (this.duration < Infinity) {
this.restartAutoHide();
}
await stopAnimations(this.base);
this.base.hidden = false;
const { keyframes, options } = getAnimation(this, 'alert.show', { dir: this.localize.dir() });
await animateTo(this.base, keyframes, options);
this.emit('sl-after-show');
} else {
// Hide
this.emit('sl-hide');
clearTimeout(this.autoHideTimeout);
await stopAnimations(this.base);
const { keyframes, options } = getAnimation(this, 'alert.hide', { dir: this.localize.dir() });
await animateTo(this.base, keyframes, options);
this.base.hidden = true;
this.emit('sl-after-hide');
}
}
@watch('duration')
handleDurationChange() {
this.restartAutoHide();
}
/** Shows the alert. */
async show() {
if (this.open) {
return undefined;
}
this.open = true;
return waitForEvent(this, 'sl-after-show');
}
/** Hides the alert */
async hide() {
if (!this.open) {
return undefined;
}
this.open = false;
return waitForEvent(this, 'sl-after-hide');
}
/**
* Displays the alert as a toast notification. This will move the alert out of its position in the DOM and, when
* dismissed, it will be removed from the DOM completely. By storing a reference to the alert, you can reuse it by
* calling this method again. The returned promise will resolve after the alert is hidden.
*/
async toast() {
return new Promise<void>(resolve => {
if (toastStack.parentElement === null) {
document.body.append(toastStack);
}
toastStack.appendChild(this);
// Wait for the toast stack to render
requestAnimationFrame(() => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- force a reflow for the initial transition
this.clientWidth;
this.show();
});
this.addEventListener(
'sl-after-hide',
() => {
toastStack.removeChild(this);
resolve();
// Remove the toast stack from the DOM when there are no more alerts
if (toastStack.querySelector('sl-alert') === null) {
toastStack.remove();
}
},
{ once: true }
);
});
}
render() {
return html`
<div
part="base"
class=${classMap({
alert: true,
'alert--open': this.open,
'alert--closable': this.closable,
'alert--has-icon': this.hasSlotController.test('icon'),
'alert--primary': this.variant === 'primary',
'alert--success': this.variant === 'success',
'alert--neutral': this.variant === 'neutral',
'alert--warning': this.variant === 'warning',
'alert--danger': this.variant === 'danger'
})}
role="alert"
aria-hidden=${this.open ? 'false' : 'true'}
@mousemove=${this.handleMouseMove}
>
<div part="icon" class="alert__icon">
<slot name="icon"></slot>
</div>
<div part="message" class="alert__message" aria-live="polite">
<slot></slot>
</div>
${this.closable
? html`
<sl-icon-button
part="close-button"
exportparts="base:close-button__base"
class="alert__close-button"
name="x-lg"
library="system"
label=${this.localize.term('close')}
@click=${this.handleCloseClick}
></sl-icon-button>
`
: ''}
</div>
`;
}
}
setDefaultAnimation('alert.show', {
keyframes: [
{ opacity: 0, scale: 0.8 },
{ opacity: 1, scale: 1 }
],
options: { duration: 250, easing: 'ease' }
});
setDefaultAnimation('alert.hide', {
keyframes: [
{ opacity: 1, scale: 1 },
{ opacity: 0, scale: 0.8 }
],
options: { duration: 250, easing: 'ease' }
});
declare global {
interface HTMLElementTagNameMap {

View File

@@ -1,116 +0,0 @@
import { html } from 'lit';
import { property, query, state } from 'lit/decorators.js';
import { watch } from '../../internal/watch.js';
import ShoelaceElement from '../../internal/shoelace-element.js';
import SlIcon from '../icon/icon.component.js';
import styles from './animated-image.styles.js';
import type { CSSResultGroup } from 'lit';
/**
* @summary A component for displaying animated GIFs and WEBPs that play and pause on interaction.
* @documentation https://shoelace.style/components/animated-image
* @status stable
* @since 2.0
*
* @dependency sl-icon
*
* @event sl-load - Emitted when the image loads successfully.
* @event sl-error - Emitted when the image fails to load.
*
* @slot play-icon - Optional play icon to use instead of the default. Works best with `<sl-icon>`.
* @slot pause-icon - Optional pause icon to use instead of the default. Works best with `<sl-icon>`.
*
* @part - control-box - The container that surrounds the pause/play icons and provides their background.
*
* @cssproperty --control-box-size - The size of the icon box.
* @cssproperty --icon-size - The size of the play/pause icons.
*/
export default class SlAnimatedImage extends ShoelaceElement {
static styles: CSSResultGroup = styles;
static dependencies = { 'sl-icon': SlIcon };
@query('.animated-image__animated') animatedImage: HTMLImageElement;
@state() frozenFrame: string;
@state() isLoaded = false;
/** The path to the image to load. */
@property() src: string;
/** A description of the image used by assistive devices. */
@property() alt: string;
/** Plays the animation. When this attribute is remove, the animation will pause. */
@property({ type: Boolean, reflect: true }) play: boolean;
private handleClick() {
this.play = !this.play;
}
private handleLoad() {
const canvas = document.createElement('canvas');
const { width, height } = this.animatedImage;
canvas.width = width;
canvas.height = height;
canvas.getContext('2d')!.drawImage(this.animatedImage, 0, 0, width, height);
this.frozenFrame = canvas.toDataURL('image/gif');
if (!this.isLoaded) {
this.emit('sl-load');
this.isLoaded = true;
}
}
private handleError() {
this.emit('sl-error');
}
@watch('play', { waitUntilFirstUpdate: true })
handlePlayChange() {
// When the animation starts playing, reset the src so it plays from the beginning. Since the src is cached, this
// won't trigger another request.
if (this.play) {
this.animatedImage.src = '';
this.animatedImage.src = this.src;
}
}
@watch('src')
handleSrcChange() {
this.isLoaded = false;
}
render() {
return html`
<div class="animated-image">
<img
class="animated-image__animated"
src=${this.src}
alt=${this.alt}
crossorigin="anonymous"
aria-hidden=${this.play ? 'false' : 'true'}
@click=${this.handleClick}
@load=${this.handleLoad}
@error=${this.handleError}
/>
${this.isLoaded
? html`
<img
class="animated-image__frozen"
src=${this.frozenFrame}
alt=${this.alt}
aria-hidden=${this.play ? 'true' : 'false'}
@click=${this.handleClick}
/>
<div part="control-box" class="animated-image__control-box">
<slot name="play-icon"><sl-icon name="play-fill" library="system"></sl-icon></slot>
<slot name="pause-icon"><sl-icon name="pause-fill" library="system"></sl-icon></slot>
</div>
`
: ''}
</div>
`;
}
}

View File

@@ -1,9 +1,119 @@
import SlAnimatedImage from './animated-image.component.js';
import '../icon/icon.js';
import { customElement, property, query, state } from 'lit/decorators.js';
import { html } from 'lit';
import { watch } from '../../internal/watch.js';
import ShoelaceElement from '../../internal/shoelace-element.js';
import styles from './animated-image.styles.js';
import type { CSSResultGroup } from 'lit';
export * from './animated-image.component.js';
export default SlAnimatedImage;
/**
* @summary A component for displaying animated GIFs and WEBPs that play and pause on interaction.
* @documentation https://shoelace.style/components/animated-image
* @status stable
* @since 2.0
*
* @dependency sl-icon
*
* @event sl-load - Emitted when the image loads successfully.
* @event sl-error - Emitted when the image fails to load.
*
* @slot play-icon - Optional play icon to use instead of the default. Works best with `<sl-icon>`.
* @slot pause-icon - Optional pause icon to use instead of the default. Works best with `<sl-icon>`.
*
* @part - control-box - The container that surrounds the pause/play icons and provides their background.
*
* @cssproperty --control-box-size - The size of the icon box.
* @cssproperty --icon-size - The size of the play/pause icons.
*/
@customElement('sl-animated-image')
export default class SlAnimatedImage extends ShoelaceElement {
static styles: CSSResultGroup = styles;
SlAnimatedImage.define('sl-animated-image');
@query('.animated-image__animated') animatedImage: HTMLImageElement;
@state() frozenFrame: string;
@state() isLoaded = false;
/** The path to the image to load. */
@property() src: string;
/** A description of the image used by assistive devices. */
@property() alt: string;
/** Plays the animation. When this attribute is remove, the animation will pause. */
@property({ type: Boolean, reflect: true }) play: boolean;
private handleClick() {
this.play = !this.play;
}
private handleLoad() {
const canvas = document.createElement('canvas');
const { width, height } = this.animatedImage;
canvas.width = width;
canvas.height = height;
canvas.getContext('2d')!.drawImage(this.animatedImage, 0, 0, width, height);
this.frozenFrame = canvas.toDataURL('image/gif');
if (!this.isLoaded) {
this.emit('sl-load');
this.isLoaded = true;
}
}
private handleError() {
this.emit('sl-error');
}
@watch('play', { waitUntilFirstUpdate: true })
handlePlayChange() {
// When the animation starts playing, reset the src so it plays from the beginning. Since the src is cached, this
// won't trigger another request.
if (this.play) {
this.animatedImage.src = '';
this.animatedImage.src = this.src;
}
}
@watch('src')
handleSrcChange() {
this.isLoaded = false;
}
render() {
return html`
<div class="animated-image">
<img
class="animated-image__animated"
src=${this.src}
alt=${this.alt}
crossorigin="anonymous"
aria-hidden=${this.play ? 'false' : 'true'}
@click=${this.handleClick}
@load=${this.handleLoad}
@error=${this.handleError}
/>
${this.isLoaded
? html`
<img
class="animated-image__frozen"
src=${this.frozenFrame}
alt=${this.alt}
aria-hidden=${this.play ? 'true' : 'false'}
@click=${this.handleClick}
/>
<div part="control-box" class="animated-image__control-box">
<slot name="play-icon"><sl-icon name="play-fill" library="system"></sl-icon></slot>
<slot name="pause-icon"><sl-icon name="pause-fill" library="system"></sl-icon></slot>
</div>
`
: ''}
</div>
`;
}
}
declare global {
interface HTMLElementTagNameMap {

View File

@@ -1,220 +0,0 @@
import { animations } from './animations.js';
import { html } from 'lit';
import { property, queryAsync } from 'lit/decorators.js';
import { watch } from '../../internal/watch.js';
import ShoelaceElement from '../../internal/shoelace-element.js';
import styles from './animation.styles.js';
import type { CSSResultGroup } from 'lit';
/**
* @summary Animate elements declaratively with nearly 100 baked-in presets, or roll your own with custom keyframes. Powered by the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API).
* @documentation https://shoelace.style/components/animation
* @status stable
* @since 2.0
*
* @event sl-cancel - Emitted when the animation is canceled.
* @event sl-finish - Emitted when the animation finishes.
* @event sl-start - Emitted when the animation starts or restarts.
*
* @slot - The element to animate. Avoid slotting in more than one element, as subsequent ones will be ignored. To
* animate multiple elements, either wrap them in a single container or use multiple `<sl-animation>` elements.
*/
export default class SlAnimation extends ShoelaceElement {
static styles: CSSResultGroup = styles;
private animation?: Animation;
private hasStarted = false;
@queryAsync('slot') defaultSlot: Promise<HTMLSlotElement>;
/** The name of the built-in animation to use. For custom animations, use the `keyframes` prop. */
@property() name = 'none';
/**
* Plays the animation. When omitted, the animation will be paused. This attribute will be automatically removed when
* the animation finishes or gets canceled.
*/
@property({ type: Boolean, reflect: true }) play = false;
/** The number of milliseconds to delay the start of the animation. */
@property({ type: Number }) delay = 0;
/**
* Determines the direction of playback as well as the behavior when reaching the end of an iteration.
* [Learn more](https://developer.mozilla.org/en-US/docs/Web/CSS/animation-direction)
*/
@property() direction: PlaybackDirection = 'normal';
/** The number of milliseconds each iteration of the animation takes to complete. */
@property({ type: Number }) duration = 1000;
/**
* The easing function to use for the animation. This can be a Shoelace easing function or a custom easing function
* such as `cubic-bezier(0, 1, .76, 1.14)`.
*/
@property() easing = 'linear';
/** The number of milliseconds to delay after the active period of an animation sequence. */
@property({ attribute: 'end-delay', type: Number }) endDelay = 0;
/** Sets how the animation applies styles to its target before and after its execution. */
@property() fill: FillMode = 'auto';
/** The number of iterations to run before the animation completes. Defaults to `Infinity`, which loops. */
@property({ type: Number }) iterations = Infinity;
/** The offset at which to start the animation, usually between 0 (start) and 1 (end). */
@property({ attribute: 'iteration-start', type: Number }) iterationStart = 0;
/** The keyframes to use for the animation. If this is set, `name` will be ignored. */
@property({ attribute: false }) keyframes?: Keyframe[];
/**
* Sets the animation's playback rate. The default is `1`, which plays the animation at a normal speed. Setting this
* to `2`, for example, will double the animation's speed. A negative value can be used to reverse the animation. This
* value can be changed without causing the animation to restart.
*/
@property({ attribute: 'playback-rate', type: Number }) playbackRate = 1;
/** Gets and sets the current animation time. */
get currentTime(): CSSNumberish {
return this.animation?.currentTime ?? 0;
}
set currentTime(time: number) {
if (this.animation) {
this.animation.currentTime = time;
}
}
connectedCallback() {
super.connectedCallback();
this.createAnimation();
}
disconnectedCallback() {
super.disconnectedCallback();
this.destroyAnimation();
}
private handleAnimationFinish = () => {
this.play = false;
this.hasStarted = false;
this.emit('sl-finish');
};
private handleAnimationCancel = () => {
this.play = false;
this.hasStarted = false;
this.emit('sl-cancel');
};
private handleSlotChange() {
this.destroyAnimation();
this.createAnimation();
}
private async createAnimation() {
const easing = animations.easings[this.easing] ?? this.easing;
const keyframes = this.keyframes ?? (animations as unknown as Partial<Record<string, Keyframe[]>>)[this.name];
const slot = await this.defaultSlot;
const element = slot.assignedElements()[0] as HTMLElement | undefined;
if (!element || !keyframes) {
return false;
}
this.destroyAnimation();
this.animation = element.animate(keyframes, {
delay: this.delay,
direction: this.direction,
duration: this.duration,
easing,
endDelay: this.endDelay,
fill: this.fill,
iterationStart: this.iterationStart,
iterations: this.iterations
});
this.animation.playbackRate = this.playbackRate;
this.animation.addEventListener('cancel', this.handleAnimationCancel);
this.animation.addEventListener('finish', this.handleAnimationFinish);
if (this.play) {
this.hasStarted = true;
this.emit('sl-start');
} else {
this.animation.pause();
}
return true;
}
private destroyAnimation() {
if (this.animation) {
this.animation.cancel();
this.animation.removeEventListener('cancel', this.handleAnimationCancel);
this.animation.removeEventListener('finish', this.handleAnimationFinish);
this.hasStarted = false;
}
}
@watch([
'name',
'delay',
'direction',
'duration',
'easing',
'endDelay',
'fill',
'iterations',
'iterationsStart',
'keyframes'
])
handleAnimationChange() {
if (!this.hasUpdated) {
return;
}
this.createAnimation();
}
@watch('play')
handlePlayChange() {
if (this.animation) {
if (this.play && !this.hasStarted) {
this.hasStarted = true;
this.emit('sl-start');
}
if (this.play) {
this.animation.play();
} else {
this.animation.pause();
}
return true;
}
return false;
}
@watch('playbackRate')
handlePlaybackRateChange() {
if (this.animation) {
this.animation.playbackRate = this.playbackRate;
}
}
/** Clears all keyframe effects caused by this animation and aborts its playback. */
cancel() {
this.animation?.cancel();
}
/** Sets the playback time to the end of the animation corresponding to the current playback direction. */
finish() {
this.animation?.finish();
}
render() {
return html` <slot @slotchange=${this.handleSlotChange}></slot> `;
}
}

View File

@@ -1,9 +1,224 @@
import SlAnimation from './animation.component.js';
import { animations } from './animations.js';
import { customElement, property, queryAsync } from 'lit/decorators.js';
import { html } from 'lit';
import { watch } from '../../internal/watch.js';
import ShoelaceElement from '../../internal/shoelace-element.js';
import styles from './animation.styles.js';
import type { CSSResultGroup } from 'lit';
export * from './animation.component.js';
export default SlAnimation;
/**
* @summary Animate elements declaratively with nearly 100 baked-in presets, or roll your own with custom keyframes. Powered by the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API).
* @documentation https://shoelace.style/components/animation
* @status stable
* @since 2.0
*
* @event sl-cancel - Emitted when the animation is canceled.
* @event sl-finish - Emitted when the animation finishes.
* @event sl-start - Emitted when the animation starts or restarts.
*
* @slot - The element to animate. Avoid slotting in more than one element, as subsequent ones will be ignored. To
* animate multiple elements, either wrap them in a single container or use multiple `<sl-animation>` elements.
*/
@customElement('sl-animation')
export default class SlAnimation extends ShoelaceElement {
static styles: CSSResultGroup = styles;
SlAnimation.define('sl-animation');
private animation?: Animation;
private hasStarted = false;
@queryAsync('slot') defaultSlot: Promise<HTMLSlotElement>;
/** The name of the built-in animation to use. For custom animations, use the `keyframes` prop. */
@property() name = 'none';
/**
* Plays the animation. When omitted, the animation will be paused. This attribute will be automatically removed when
* the animation finishes or gets canceled.
*/
@property({ type: Boolean, reflect: true }) play = false;
/** The number of milliseconds to delay the start of the animation. */
@property({ type: Number }) delay = 0;
/**
* Determines the direction of playback as well as the behavior when reaching the end of an iteration.
* [Learn more](https://developer.mozilla.org/en-US/docs/Web/CSS/animation-direction)
*/
@property() direction: PlaybackDirection = 'normal';
/** The number of milliseconds each iteration of the animation takes to complete. */
@property({ type: Number }) duration = 1000;
/**
* The easing function to use for the animation. This can be a Shoelace easing function or a custom easing function
* such as `cubic-bezier(0, 1, .76, 1.14)`.
*/
@property() easing = 'linear';
/** The number of milliseconds to delay after the active period of an animation sequence. */
@property({ attribute: 'end-delay', type: Number }) endDelay = 0;
/** Sets how the animation applies styles to its target before and after its execution. */
@property() fill: FillMode = 'auto';
/** The number of iterations to run before the animation completes. Defaults to `Infinity`, which loops. */
@property({ type: Number }) iterations = Infinity;
/** The offset at which to start the animation, usually between 0 (start) and 1 (end). */
@property({ attribute: 'iteration-start', type: Number }) iterationStart = 0;
/** The keyframes to use for the animation. If this is set, `name` will be ignored. */
@property({ attribute: false }) keyframes?: Keyframe[];
/**
* Sets the animation's playback rate. The default is `1`, which plays the animation at a normal speed. Setting this
* to `2`, for example, will double the animation's speed. A negative value can be used to reverse the animation. This
* value can be changed without causing the animation to restart.
*/
@property({ attribute: 'playback-rate', type: Number }) playbackRate = 1;
/** Gets and sets the current animation time. */
get currentTime(): CSSNumberish {
return this.animation?.currentTime ?? 0;
}
set currentTime(time: number) {
if (this.animation) {
this.animation.currentTime = time;
}
}
connectedCallback() {
super.connectedCallback();
this.createAnimation();
}
disconnectedCallback() {
super.disconnectedCallback();
this.destroyAnimation();
}
private handleAnimationFinish = () => {
this.play = false;
this.hasStarted = false;
this.emit('sl-finish');
};
private handleAnimationCancel = () => {
this.play = false;
this.hasStarted = false;
this.emit('sl-cancel');
};
private handleSlotChange() {
this.destroyAnimation();
this.createAnimation();
}
private async createAnimation() {
const easing = animations.easings[this.easing] ?? this.easing;
const keyframes = this.keyframes ?? (animations as unknown as Partial<Record<string, Keyframe[]>>)[this.name];
const slot = await this.defaultSlot;
const element = slot.assignedElements()[0] as HTMLElement | undefined;
if (!element || !keyframes) {
return false;
}
this.destroyAnimation();
this.animation = element.animate(keyframes, {
delay: this.delay,
direction: this.direction,
duration: this.duration,
easing,
endDelay: this.endDelay,
fill: this.fill,
iterationStart: this.iterationStart,
iterations: this.iterations
});
this.animation.playbackRate = this.playbackRate;
this.animation.addEventListener('cancel', this.handleAnimationCancel);
this.animation.addEventListener('finish', this.handleAnimationFinish);
if (this.play) {
this.hasStarted = true;
this.emit('sl-start');
} else {
this.animation.pause();
}
return true;
}
private destroyAnimation() {
if (this.animation) {
this.animation.cancel();
this.animation.removeEventListener('cancel', this.handleAnimationCancel);
this.animation.removeEventListener('finish', this.handleAnimationFinish);
this.hasStarted = false;
}
}
@watch([
'name',
'delay',
'direction',
'duration',
'easing',
'endDelay',
'fill',
'iterations',
'iterationsStart',
'keyframes'
])
handleAnimationChange() {
if (!this.hasUpdated) {
return;
}
this.createAnimation();
}
@watch('play')
handlePlayChange() {
if (this.animation) {
if (this.play && !this.hasStarted) {
this.hasStarted = true;
this.emit('sl-start');
}
if (this.play) {
this.animation.play();
} else {
this.animation.pause();
}
return true;
}
return false;
}
@watch('playbackRate')
handlePlaybackRateChange() {
if (this.animation) {
this.animation.playbackRate = this.playbackRate;
}
}
/** Clears all keyframe effects caused by this animation and aborts its playback. */
cancel() {
this.animation?.cancel();
}
/** Sets the playback time to the end of the animation corresponding to the current playback direction. */
finish() {
this.animation?.finish();
}
render() {
return html` <slot @slotchange=${this.handleSlotChange}></slot> `;
}
}
declare global {
interface HTMLElementTagNameMap {

View File

@@ -1,98 +0,0 @@
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 ShoelaceElement from '../../internal/shoelace-element.js';
import SlIcon from '../icon/icon.component.js';
import styles from './avatar.styles.js';
import type { CSSResultGroup } from 'lit';
/**
* @summary Avatars are used to represent a person or object.
* @documentation https://shoelace.style/components/avatar
* @status stable
* @since 2.0
*
* @dependency sl-icon
*
* @slot icon - The default icon to use when no image or initials are present. Works best with `<sl-icon>`.
*
* @csspart base - The component's base wrapper.
* @csspart icon - The container that wraps the avatar's icon.
* @csspart initials - The container that wraps the avatar's initials.
* @csspart image - The avatar image. Only shown when the `image` attribute is set.
*
* @cssproperty --size - The size of the avatar.
*/
export default class SlAvatar extends ShoelaceElement {
static styles: CSSResultGroup = styles;
static dependencies = {
'sl-icon': SlIcon
};
@state() private hasError = false;
/** The image source to use for the avatar. */
@property() image = '';
/** A label to use to describe the avatar to assistive devices. */
@property() label = '';
/** Initials to use as a fallback when no image is available (1-2 characters max recommended). */
@property() initials = '';
/** Indicates how the browser should load the image. */
@property() loading: 'eager' | 'lazy' = 'eager';
/** The shape of the avatar. */
@property({ reflect: true }) shape: 'circle' | 'square' | 'rounded' = 'circle';
@watch('image')
handleImageChange() {
// Reset the error when a new image is provided
this.hasError = false;
}
render() {
const avatarWithImage = html`
<img
part="image"
class="avatar__image"
src="${this.image}"
loading="${this.loading}"
alt=""
@error="${() => (this.hasError = true)}"
/>
`;
let avatarWithoutImage = html``;
if (this.initials) {
avatarWithoutImage = html`<div part="initials" class="avatar__initials">${this.initials}</div>`;
} else {
avatarWithoutImage = html`
<div part="icon" class="avatar__icon" aria-hidden="true">
<slot name="icon">
<sl-icon name="person-fill" library="system"></sl-icon>
</slot>
</div>
`;
}
return html`
<div
part="base"
class=${classMap({
avatar: true,
'avatar--circle': this.shape === 'circle',
'avatar--rounded': this.shape === 'rounded',
'avatar--square': this.shape === 'square'
})}
role="img"
aria-label=${this.label}
>
${this.image && !this.hasError ? avatarWithImage : avatarWithoutImage}
</div>
`;
}
}

View File

@@ -1,9 +1,99 @@
import SlAvatar from './avatar.component.js';
import '../icon/icon.js';
import { classMap } from 'lit/directives/class-map.js';
import { customElement, property, state } from 'lit/decorators.js';
import { html } from 'lit';
import { watch } from '../../internal/watch.js';
import ShoelaceElement from '../../internal/shoelace-element.js';
import styles from './avatar.styles.js';
import type { CSSResultGroup } from 'lit';
export * from './avatar.component.js';
export default SlAvatar;
/**
* @summary Avatars are used to represent a person or object.
* @documentation https://shoelace.style/components/avatar
* @status stable
* @since 2.0
*
* @dependency sl-icon
*
* @slot icon - The default icon to use when no image or initials are present. Works best with `<sl-icon>`.
*
* @csspart base - The component's base wrapper.
* @csspart icon - The container that wraps the avatar's icon.
* @csspart initials - The container that wraps the avatar's initials.
* @csspart image - The avatar image. Only shown when the `image` attribute is set.
*
* @cssproperty --size - The size of the avatar.
*/
@customElement('sl-avatar')
export default class SlAvatar extends ShoelaceElement {
static styles: CSSResultGroup = styles;
SlAvatar.define('sl-avatar');
@state() private hasError = false;
/** The image source to use for the avatar. */
@property() image = '';
/** A label to use to describe the avatar to assistive devices. */
@property() label = '';
/** Initials to use as a fallback when no image is available (1-2 characters max recommended). */
@property() initials = '';
/** Indicates how the browser should load the image. */
@property() loading: 'eager' | 'lazy' = 'eager';
/** The shape of the avatar. */
@property({ reflect: true }) shape: 'circle' | 'square' | 'rounded' = 'circle';
@watch('image')
handleImageChange() {
// Reset the error when a new image is provided
this.hasError = false;
}
render() {
const avatarWithImage = html`
<img
part="image"
class="avatar__image"
src="${this.image}"
loading="${this.loading}"
alt=""
@error="${() => (this.hasError = true)}"
/>
`;
let avatarWithoutImage = html``;
if (this.initials) {
avatarWithoutImage = html`<div part="initials" class="avatar__initials">${this.initials}</div>`;
} else {
avatarWithoutImage = html`
<div part="icon" class="avatar__icon" aria-hidden="true">
<slot name="icon">
<sl-icon name="person-fill" library="system"></sl-icon>
</slot>
</div>
`;
}
return html`
<div
part="base"
class=${classMap({
avatar: true,
'avatar--circle': this.shape === 'circle',
'avatar--rounded': this.shape === 'rounded',
'avatar--square': this.shape === 'square'
})}
role="img"
aria-label=${this.label}
>
${this.image && !this.hasError ? avatarWithImage : avatarWithoutImage}
</div>
`;
}
}
declare global {
interface HTMLElementTagNameMap {

View File

@@ -1,50 +0,0 @@
import { classMap } from 'lit/directives/class-map.js';
import { html } from 'lit';
import { property } from 'lit/decorators.js';
import ShoelaceElement from '../../internal/shoelace-element.js';
import styles from './badge.styles.js';
import type { CSSResultGroup } from 'lit';
/**
* @summary Badges are used to draw attention and display statuses or counts.
* @documentation https://shoelace.style/components/badge
* @status stable
* @since 2.0
*
* @slot - The badge's content.
*
* @csspart base - The component's base wrapper.
*/
export default class SlBadge extends ShoelaceElement {
static styles: CSSResultGroup = styles;
/** The badge's theme variant. */
@property({ reflect: true }) variant: 'primary' | 'success' | 'neutral' | 'warning' | 'danger' = 'primary';
/** Draws a pill-style badge with rounded edges. */
@property({ type: Boolean, reflect: true }) pill = false;
/** Makes the badge pulsate to draw attention. */
@property({ type: Boolean, reflect: true }) pulse = false;
render() {
return html`
<span
part="base"
class=${classMap({
badge: true,
'badge--primary': this.variant === 'primary',
'badge--success': this.variant === 'success',
'badge--neutral': this.variant === 'neutral',
'badge--warning': this.variant === 'warning',
'badge--danger': this.variant === 'danger',
'badge--pill': this.pill,
'badge--pulse': this.pulse
})}
role="status"
>
<slot></slot>
</span>
`;
}
}

View File

@@ -1,9 +1,54 @@
import SlBadge from './badge.component.js';
import { classMap } from 'lit/directives/class-map.js';
import { customElement, property } from 'lit/decorators.js';
import { html } from 'lit';
import ShoelaceElement from '../../internal/shoelace-element.js';
import styles from './badge.styles.js';
import type { CSSResultGroup } from 'lit';
export * from './badge.component.js';
export default SlBadge;
/**
* @summary Badges are used to draw attention and display statuses or counts.
* @documentation https://shoelace.style/components/badge
* @status stable
* @since 2.0
*
* @slot - The badge's content.
*
* @csspart base - The component's base wrapper.
*/
@customElement('sl-badge')
export default class SlBadge extends ShoelaceElement {
static styles: CSSResultGroup = styles;
SlBadge.define('sl-badge');
/** The badge's theme variant. */
@property({ reflect: true }) variant: 'primary' | 'success' | 'neutral' | 'warning' | 'danger' = 'primary';
/** Draws a pill-style badge with rounded edges. */
@property({ type: Boolean, reflect: true }) pill = false;
/** Makes the badge pulsate to draw attention. */
@property({ type: Boolean, reflect: true }) pulse = false;
render() {
return html`
<span
part="base"
class=${classMap({
badge: true,
'badge--primary': this.variant === 'primary',
'badge--success': this.variant === 'success',
'badge--neutral': this.variant === 'neutral',
'badge--warning': this.variant === 'warning',
'badge--danger': this.variant === 'danger',
'badge--pill': this.pill,
'badge--pulse': this.pulse
})}
role="status"
>
<slot></slot>
</span>
`;
}
}
declare global {
interface HTMLElementTagNameMap {

View File

@@ -1,89 +0,0 @@
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 ShoelaceElement from '../../internal/shoelace-element.js';
import styles from './breadcrumb-item.styles.js';
import type { CSSResultGroup } from 'lit';
/**
* @summary Breadcrumb Items are used inside [breadcrumbs](/components/breadcrumb) to represent different links.
* @documentation https://shoelace.style/components/breadcrumb-item
* @status stable
* @since 2.0
*
* @slot - The breadcrumb item's label.
* @slot prefix - An optional prefix, usually an icon or icon button.
* @slot suffix - An optional suffix, usually an icon or icon button.
* @slot separator - The separator to use for the breadcrumb item. This will only change the separator for this item. If
* you want to change it for all items in the group, set the separator on `<sl-breadcrumb>` instead.
*
* @csspart base - The component's base wrapper.
* @csspart label - The breadcrumb item's label.
* @csspart prefix - The container that wraps the prefix.
* @csspart suffix - The container that wraps the suffix.
* @csspart separator - The container that wraps the separator.
*/
export default class SlBreadcrumbItem extends ShoelaceElement {
static styles: CSSResultGroup = styles;
private readonly hasSlotController = new HasSlotController(this, 'prefix', 'suffix');
/**
* Optional URL to direct the user to when the breadcrumb item is activated. When set, a link will be rendered
* internally. When unset, a button will be rendered instead.
*/
@property() href?: string;
/** Tells the browser where to open the link. Only used when `href` is set. */
@property() target?: '_blank' | '_parent' | '_self' | '_top';
/** The `rel` attribute to use on the link. Only used when `href` is set. */
@property() rel = 'noreferrer noopener';
render() {
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')
})}
>
<span part="prefix" class="breadcrumb-item__prefix">
<slot name="prefix"></slot>
</span>
${isLink
? html`
<a
part="label"
class="breadcrumb-item__label breadcrumb-item__label--link"
href="${this.href!}"
target="${ifDefined(this.target ? this.target : undefined)}"
rel=${ifDefined(this.target ? this.rel : undefined)}
>
<slot></slot>
</a>
`
: html`
<button part="label" type="button" class="breadcrumb-item__label breadcrumb-item__label--button">
<slot></slot>
</button>
`}
<span part="suffix" class="breadcrumb-item__suffix">
<slot name="suffix"></slot>
</span>
<span part="separator" class="breadcrumb-item__separator" aria-hidden="true">
<slot name="separator"></slot>
</span>
</div>
`;
}
}

View File

@@ -1,9 +1,93 @@
import SlBreadcrumbItem from './breadcrumb-item.component.js';
import { classMap } from 'lit/directives/class-map.js';
import { customElement, property } from 'lit/decorators.js';
import { HasSlotController } from '../../internal/slot.js';
import { html } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import ShoelaceElement from '../../internal/shoelace-element.js';
import styles from './breadcrumb-item.styles.js';
import type { CSSResultGroup } from 'lit';
export * from './breadcrumb-item.component.js';
export default SlBreadcrumbItem;
/**
* @summary Breadcrumb Items are used inside [breadcrumbs](/components/breadcrumb) to represent different links.
* @documentation https://shoelace.style/components/breadcrumb-item
* @status stable
* @since 2.0
*
* @slot - The breadcrumb item's label.
* @slot prefix - An optional prefix, usually an icon or icon button.
* @slot suffix - An optional suffix, usually an icon or icon button.
* @slot separator - The separator to use for the breadcrumb item. This will only change the separator for this item. If
* you want to change it for all items in the group, set the separator on `<sl-breadcrumb>` instead.
*
* @csspart base - The component's base wrapper.
* @csspart label - The breadcrumb item's label.
* @csspart prefix - The container that wraps the prefix.
* @csspart suffix - The container that wraps the suffix.
* @csspart separator - The container that wraps the separator.
*/
@customElement('sl-breadcrumb-item')
export default class SlBreadcrumbItem extends ShoelaceElement {
static styles: CSSResultGroup = styles;
SlBreadcrumbItem.define('sl-breadcrumb-item');
private readonly hasSlotController = new HasSlotController(this, 'prefix', 'suffix');
/**
* Optional URL to direct the user to when the breadcrumb item is activated. When set, a link will be rendered
* internally. When unset, a button will be rendered instead.
*/
@property() href?: string;
/** Tells the browser where to open the link. Only used when `href` is set. */
@property() target?: '_blank' | '_parent' | '_self' | '_top';
/** The `rel` attribute to use on the link. Only used when `href` is set. */
@property() rel = 'noreferrer noopener';
render() {
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')
})}
>
<span part="prefix" class="breadcrumb-item__prefix">
<slot name="prefix"></slot>
</span>
${isLink
? html`
<a
part="label"
class="breadcrumb-item__label breadcrumb-item__label--link"
href="${this.href!}"
target="${ifDefined(this.target ? this.target : undefined)}"
rel=${ifDefined(this.target ? this.rel : undefined)}
>
<slot></slot>
</a>
`
: html`
<button part="label" type="button" class="breadcrumb-item__label breadcrumb-item__label--button">
<slot></slot>
</button>
`}
<span part="suffix" class="breadcrumb-item__suffix">
<slot name="suffix"></slot>
</span>
<span part="separator" class="breadcrumb-item__separator" aria-hidden="true">
<slot name="separator"></slot>
</span>
</div>
`;
}
}
declare global {
interface HTMLElementTagNameMap {

View File

@@ -1,100 +0,0 @@
import { html } from 'lit';
import { LocalizeController } from '../../utilities/localize.js';
import { property, query } from 'lit/decorators.js';
import ShoelaceElement from '../../internal/shoelace-element.js';
import SlIcon from '../icon/icon.component.js';
import styles from './breadcrumb.styles.js';
import type { CSSResultGroup } from 'lit';
import type SlBreadcrumbItem from '../breadcrumb-item/breadcrumb-item.js';
/**
* @summary Breadcrumbs provide a group of links so users can easily navigate a website's hierarchy.
* @documentation https://shoelace.style/components/breadcrumb
* @status stable
* @since 2.0
*
* @slot - One or more breadcrumb items to display.
* @slot separator - The separator to use between breadcrumb items. Works best with `<sl-icon>`.
*
* @dependency sl-icon
*
* @csspart base - The component's base wrapper.
*/
export default class SlBreadcrumb extends ShoelaceElement {
static styles: CSSResultGroup = styles;
static dependencies = { 'sl-icon': SlIcon };
private readonly localize = new LocalizeController(this);
private separatorDir = this.localize.dir();
@query('slot') defaultSlot: HTMLSlotElement;
@query('slot[name="separator"]') separatorSlot: HTMLSlotElement;
/**
* The label to use for the breadcrumb control. This will not be shown on the screen, but it will be announced by
* screen readers and other assistive devices to provide more context for users.
*/
@property() label = '';
// Generates a clone of the separator element to use for each breadcrumb item
private getSeparator() {
const separator = this.separatorSlot.assignedElements({ flatten: true })[0] as HTMLElement;
// Clone it, remove ids, and slot it
const clone = separator.cloneNode(true) as HTMLElement;
[clone, ...clone.querySelectorAll('[id]')].forEach(el => el.removeAttribute('id'));
clone.setAttribute('data-default', '');
clone.slot = 'separator';
return clone;
}
private handleSlotChange() {
const items = [...this.defaultSlot.assignedElements({ flatten: true })].filter(
item => item.tagName.toLowerCase() === 'sl-breadcrumb-item'
) as SlBreadcrumbItem[];
items.forEach((item, index) => {
// Append separators to each item if they don't already have one
const separator = item.querySelector('[slot="separator"]');
if (separator === null) {
// No separator exists, add one
item.append(this.getSeparator());
} else if (separator.hasAttribute('data-default')) {
// A default separator exists, replace it
separator.replaceWith(this.getSeparator());
} else {
// The user provided a custom separator, leave it alone
}
// The last breadcrumb item is the "current page"
if (index === items.length - 1) {
item.setAttribute('aria-current', 'page');
} else {
item.removeAttribute('aria-current');
}
});
}
render() {
// We clone the separator and inject them into breadcrumb items, so we need to regenerate the default ones when
// directionality changes. We do this by storing the current separator direction, waiting for render, then calling
// the function that regenerates them.
if (this.separatorDir !== this.localize.dir()) {
this.separatorDir = this.localize.dir();
this.updateComplete.then(() => this.handleSlotChange());
}
return html`
<nav part="base" class="breadcrumb" aria-label=${this.label}>
<slot @slotchange=${this.handleSlotChange}></slot>
</nav>
<span hidden aria-hidden="true">
<slot name="separator">
<sl-icon name=${this.localize.dir() === 'rtl' ? 'chevron-left' : 'chevron-right'} library="system"></sl-icon>
</slot>
</span>
`;
}
}

View File

@@ -1,9 +1,103 @@
import SlBreadcrumb from './breadcrumb.component.js';
import '../icon/icon.js';
import { customElement, property, query } from 'lit/decorators.js';
import { html } from 'lit';
import { LocalizeController } from '../../utilities/localize.js';
import ShoelaceElement from '../../internal/shoelace-element.js';
import styles from './breadcrumb.styles.js';
import type { CSSResultGroup } from 'lit';
import type SlBreadcrumbItem from '../breadcrumb-item/breadcrumb-item.js';
export * from './breadcrumb.component.js';
export default SlBreadcrumb;
/**
* @summary Breadcrumbs provide a group of links so users can easily navigate a website's hierarchy.
* @documentation https://shoelace.style/components/breadcrumb
* @status stable
* @since 2.0
*
* @slot - One or more breadcrumb items to display.
* @slot separator - The separator to use between breadcrumb items. Works best with `<sl-icon>`.
*
* @dependency sl-icon
*
* @csspart base - The component's base wrapper.
*/
@customElement('sl-breadcrumb')
export default class SlBreadcrumb extends ShoelaceElement {
static styles: CSSResultGroup = styles;
SlBreadcrumb.define('sl-breadcrumb');
private readonly localize = new LocalizeController(this);
private separatorDir = this.localize.dir();
@query('slot') defaultSlot: HTMLSlotElement;
@query('slot[name="separator"]') separatorSlot: HTMLSlotElement;
/**
* The label to use for the breadcrumb control. This will not be shown on the screen, but it will be announced by
* screen readers and other assistive devices to provide more context for users.
*/
@property() label = '';
// Generates a clone of the separator element to use for each breadcrumb item
private getSeparator() {
const separator = this.separatorSlot.assignedElements({ flatten: true })[0] as HTMLElement;
// Clone it, remove ids, and slot it
const clone = separator.cloneNode(true) as HTMLElement;
[clone, ...clone.querySelectorAll('[id]')].forEach(el => el.removeAttribute('id'));
clone.setAttribute('data-default', '');
clone.slot = 'separator';
return clone;
}
private handleSlotChange() {
const items = [...this.defaultSlot.assignedElements({ flatten: true })].filter(
item => item.tagName.toLowerCase() === 'sl-breadcrumb-item'
) as SlBreadcrumbItem[];
items.forEach((item, index) => {
// Append separators to each item if they don't already have one
const separator = item.querySelector('[slot="separator"]');
if (separator === null) {
// No separator exists, add one
item.append(this.getSeparator());
} else if (separator.hasAttribute('data-default')) {
// A default separator exists, replace it
separator.replaceWith(this.getSeparator());
} else {
// The user provided a custom separator, leave it alone
}
// The last breadcrumb item is the "current page"
if (index === items.length - 1) {
item.setAttribute('aria-current', 'page');
} else {
item.removeAttribute('aria-current');
}
});
}
render() {
// We clone the separator and inject them into breadcrumb items, so we need to regenerate the default ones when
// directionality changes. We do this by storing the current separator direction, waiting for render, then calling
// the function that regenerates them.
if (this.separatorDir !== this.localize.dir()) {
this.separatorDir = this.localize.dir();
this.updateComplete.then(() => this.handleSlotChange());
}
return html`
<nav part="base" class="breadcrumb" aria-label=${this.label}>
<slot @slotchange=${this.handleSlotChange}></slot>
</nav>
<span hidden aria-hidden="true">
<slot name="separator">
<sl-icon name=${this.localize.dir() === 'rtl' ? 'chevron-left' : 'chevron-right'} library="system"></sl-icon>
</slot>
</span>
`;
}
}
declare global {
interface HTMLElementTagNameMap {

View File

@@ -1,91 +0,0 @@
import { html } from 'lit';
import { property, query, state } from 'lit/decorators.js';
import ShoelaceElement from '../../internal/shoelace-element.js';
import styles from './button-group.styles.js';
import type { CSSResultGroup } from 'lit';
/**
* @summary Button groups can be used to group related buttons into sections.
* @documentation https://shoelace.style/components/button-group
* @status stable
* @since 2.0
*
* @slot - One or more `<sl-button>` elements to display in the button group.
*
* @csspart base - The component's base wrapper.
*/
export default class SlButtonGroup extends ShoelaceElement {
static styles: CSSResultGroup = styles;
@query('slot') defaultSlot: HTMLSlotElement;
@state() disableRole = false;
/**
* A label to use for the button group. This won't be displayed on the screen, but it will be announced by assistive
* devices when interacting with the control and is strongly recommended.
*/
@property() label = '';
private handleFocus(event: Event) {
const button = findButton(event.target as HTMLElement);
button?.classList.add('sl-button-group__button--focus');
}
private handleBlur(event: Event) {
const button = findButton(event.target as HTMLElement);
button?.classList.remove('sl-button-group__button--focus');
}
private handleMouseOver(event: Event) {
const button = findButton(event.target as HTMLElement);
button?.classList.add('sl-button-group__button--hover');
}
private handleMouseOut(event: Event) {
const button = findButton(event.target as HTMLElement);
button?.classList.remove('sl-button-group__button--hover');
}
private handleSlotChange() {
const slottedElements = [...this.defaultSlot.assignedElements({ flatten: true })] as HTMLElement[];
slottedElements.forEach(el => {
const index = slottedElements.indexOf(el);
const button = findButton(el);
if (button !== null) {
button.classList.add('sl-button-group__button');
button.classList.toggle('sl-button-group__button--first', index === 0);
button.classList.toggle('sl-button-group__button--inner', index > 0 && index < slottedElements.length - 1);
button.classList.toggle('sl-button-group__button--last', index === slottedElements.length - 1);
button.classList.toggle('sl-button-group__button--radio', button.tagName.toLowerCase() === 'sl-radio-button');
}
});
}
render() {
// eslint-disable-next-line lit-a11y/mouse-events-have-key-events
return html`
<div
part="base"
class="button-group"
role="${this.disableRole ? 'presentation' : 'group'}"
aria-label=${this.label}
@focusout=${this.handleBlur}
@focusin=${this.handleFocus}
@mouseover=${this.handleMouseOver}
@mouseout=${this.handleMouseOut}
>
<slot @slotchange=${this.handleSlotChange}></slot>
</div>
`;
}
}
function findButton(el: HTMLElement) {
const selector = 'sl-button, sl-radio-button';
// The button could be the target element or a child of it (e.g. a dropdown or tooltip anchor)
return el.closest(selector) ?? el.querySelector(selector);
}

View File

@@ -1,9 +1,95 @@
import SlButtonGroup from './button-group.component.js';
import { customElement, property, query, state } from 'lit/decorators.js';
import { html } from 'lit';
import ShoelaceElement from '../../internal/shoelace-element.js';
import styles from './button-group.styles.js';
import type { CSSResultGroup } from 'lit';
export * from './button-group.component.js';
export default SlButtonGroup;
/**
* @summary Button groups can be used to group related buttons into sections.
* @documentation https://shoelace.style/components/button-group
* @status stable
* @since 2.0
*
* @slot - One or more `<sl-button>` elements to display in the button group.
*
* @csspart base - The component's base wrapper.
*/
@customElement('sl-button-group')
export default class SlButtonGroup extends ShoelaceElement {
static styles: CSSResultGroup = styles;
SlButtonGroup.define('sl-button-group');
@query('slot') defaultSlot: HTMLSlotElement;
@state() disableRole = false;
/**
* A label to use for the button group. This won't be displayed on the screen, but it will be announced by assistive
* devices when interacting with the control and is strongly recommended.
*/
@property() label = '';
private handleFocus(event: Event) {
const button = findButton(event.target as HTMLElement);
button?.classList.add('sl-button-group__button--focus');
}
private handleBlur(event: Event) {
const button = findButton(event.target as HTMLElement);
button?.classList.remove('sl-button-group__button--focus');
}
private handleMouseOver(event: Event) {
const button = findButton(event.target as HTMLElement);
button?.classList.add('sl-button-group__button--hover');
}
private handleMouseOut(event: Event) {
const button = findButton(event.target as HTMLElement);
button?.classList.remove('sl-button-group__button--hover');
}
private handleSlotChange() {
const slottedElements = [...this.defaultSlot.assignedElements({ flatten: true })] as HTMLElement[];
slottedElements.forEach(el => {
const index = slottedElements.indexOf(el);
const button = findButton(el);
if (button !== null) {
button.classList.add('sl-button-group__button');
button.classList.toggle('sl-button-group__button--first', index === 0);
button.classList.toggle('sl-button-group__button--inner', index > 0 && index < slottedElements.length - 1);
button.classList.toggle('sl-button-group__button--last', index === slottedElements.length - 1);
button.classList.toggle('sl-button-group__button--radio', button.tagName.toLowerCase() === 'sl-radio-button');
}
});
}
render() {
// eslint-disable-next-line lit-a11y/mouse-events-have-key-events
return html`
<div
part="base"
class="button-group"
role="${this.disableRole ? 'presentation' : 'group'}"
aria-label=${this.label}
@focusout=${this.handleBlur}
@focusin=${this.handleFocus}
@mouseover=${this.handleMouseOver}
@mouseout=${this.handleMouseOut}
>
<slot @slotchange=${this.handleSlotChange}></slot>
</div>
`;
}
}
function findButton(el: HTMLElement) {
const selector = 'sl-button, sl-radio-button';
// The button could be the target element or a child of it (e.g. a dropdown or tooltip anchor)
return el.closest(selector) ?? el.querySelector(selector);
}
declare global {
interface HTMLElementTagNameMap {

View File

@@ -1,330 +0,0 @@
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 ShoelaceElement from '../../internal/shoelace-element.js';
import SlIcon from '../icon/icon.component.js';
import SlSpinner from '../spinner/spinner.component.js';
import styles from './button.styles.js';
import type { CSSResultGroup } from 'lit';
import type { ShoelaceFormControl } from '../../internal/shoelace-element.js';
/**
* @summary Buttons represent actions that are available to the user.
* @documentation https://shoelace.style/components/button
* @status stable
* @since 2.0
*
* @dependency sl-icon
* @dependency sl-spinner
*
* @event sl-blur - Emitted when the button loses focus.
* @event sl-focus - Emitted when the button gains focus.
* @event sl-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.
*
* @slot - The button's label.
* @slot prefix - A presentational prefix icon or similar element.
* @slot suffix - A presentational suffix icon or similar element.
*
* @csspart base - The component's base wrapper.
* @csspart prefix - The container that wraps the prefix.
* @csspart label - The button's label.
* @csspart suffix - The container that wraps the suffix.
* @csspart caret - The button's caret icon, an `<sl-icon>` element.
* @csspart spinner - The spinner that shows when the button is in the loading state.
*/
export default class SlButton extends ShoelaceElement implements ShoelaceFormControl {
static styles: CSSResultGroup = styles;
static dependencies = {
'sl-icon': SlIcon,
'sl-spinner': SlSpinner
};
private readonly formControlController = new FormControlController(this, {
form: input => {
// Buttons support a form attribute that points to an arbitrary form, so if this attribute is set we need to query
// the form from the same root using its id
if (input.hasAttribute('form')) {
const doc = input.getRootNode() as Document | ShadowRoot;
const formId = input.getAttribute('form')!;
return doc.getElementById(formId) as HTMLFormElement;
}
// Fall back to the closest containing form
return input.closest('form');
},
assumeInteractionOn: ['click']
});
private readonly hasSlotController = new HasSlotController(this, '[default]', 'prefix', 'suffix');
private readonly localize = new LocalizeController(this);
@query('.button') button: HTMLButtonElement | HTMLLinkElement;
@state() private hasFocus = false;
@state() invalid = false;
@property() title = ''; // make reactive to pass through
/** The button's theme variant. */
@property({ reflect: true }) variant: 'default' | 'primary' | 'success' | 'neutral' | 'warning' | 'danger' | 'text' =
'default';
/** The button's size. */
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
/** Draws the button with a caret. Used to indicate that the button triggers a dropdown menu or similar behavior. */
@property({ type: Boolean, reflect: true }) caret = false;
/** Disables the button. */
@property({ type: Boolean, reflect: true }) disabled = false;
/** Draws the button in a loading state. */
@property({ type: Boolean, reflect: true }) loading = false;
/** Draws an outlined button. */
@property({ type: Boolean, reflect: true }) outline = false;
/** Draws a pill-style button with rounded edges. */
@property({ type: Boolean, reflect: true }) pill = false;
/**
* Draws a circular icon button. When this attribute is present, the button expects a single `<sl-icon>` in the
* default slot.
*/
@property({ type: Boolean, reflect: true }) circle = false;
/**
* The type of button. Note that the default value is `button` instead of `submit`, which is opposite of how native
* `<button>` elements behave. When the type is `submit`, the button will submit the surrounding form.
*/
@property() type: 'button' | 'submit' | 'reset' = 'button';
/**
* The name of the button, submitted as a name/value pair with form data, but only when this button is the submitter.
* This attribute is ignored when `href` is present.
*/
@property() name = '';
/**
* The value of the button, submitted as a pair with the button's name as part of the form data, but only when this
* button is the submitter. This attribute is ignored when `href` is present.
*/
@property() value = '';
/** When set, the underlying button will be rendered as an `<a>` with this `href` instead of a `<button>`. */
@property() href = '';
/** Tells the browser where to open the link. Only used when `href` is present. */
@property() target: '_blank' | '_parent' | '_self' | '_top';
/**
* When using `href`, this attribute will map to the underlying link's `rel` attribute. Unlike regular links, the
* default is `noreferrer noopener` to prevent security exploits. However, if you're using `target` to point to a
* specific tab/window, this will prevent that from working correctly. You can remove or change the default value by
* setting the attribute to an empty string or a value of your choice, respectively.
*/
@property() rel = 'noreferrer noopener';
/** Tells the browser to download the linked file as this filename. Only used when `href` is present. */
@property() download?: string;
/**
* The "form owner" to associate the button with. If omitted, the closest containing form will be used instead. The
* value of this attribute must be an id of a form in the same document or shadow root as the button.
*/
@property() form: string;
/** Used to override the form owner's `action` attribute. */
@property({ attribute: 'formaction' }) formAction: string;
/** Used to override the form owner's `enctype` attribute. */
@property({ attribute: 'formenctype' })
formEnctype: 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain';
/** Used to override the form owner's `method` attribute. */
@property({ attribute: 'formmethod' }) formMethod: 'post' | 'get';
/** Used to override the form owner's `novalidate` attribute. */
@property({ attribute: 'formnovalidate', type: Boolean }) formNoValidate: boolean;
/** Used to override the form owner's `target` attribute. */
@property({ attribute: 'formtarget' }) formTarget: '_self' | '_blank' | '_parent' | '_top' | string;
/** Gets the validity state object */
get validity() {
if (this.isButton()) {
return (this.button as HTMLButtonElement).validity;
}
return validValidityState;
}
/** Gets the validation message */
get validationMessage() {
if (this.isButton()) {
return (this.button as HTMLButtonElement).validationMessage;
}
return '';
}
firstUpdated() {
if (this.isButton()) {
this.formControlController.updateValidity();
}
}
private handleBlur() {
this.hasFocus = false;
this.emit('sl-blur');
}
private handleFocus() {
this.hasFocus = true;
this.emit('sl-focus');
}
private handleClick() {
if (this.type === 'submit') {
this.formControlController.submit(this);
}
if (this.type === 'reset') {
this.formControlController.reset(this);
}
}
private handleInvalid(event: Event) {
this.formControlController.setValidity(false);
this.formControlController.emitInvalidEvent(event);
}
private isButton() {
return this.href ? false : true;
}
private isLink() {
return this.href ? true : false;
}
@watch('disabled', { waitUntilFirstUpdate: true })
handleDisabledChange() {
if (this.isButton()) {
// Disabled form controls are always valid
this.formControlController.setValidity(this.disabled);
}
}
/** Simulates a click on the button. */
click() {
this.button.click();
}
/** Sets focus on the button. */
focus(options?: FocusOptions) {
this.button.focus(options);
}
/** Removes focus from the button. */
blur() {
this.button.blur();
}
/** Checks for validity but does not show a validation message. Returns `true` when valid and `false` when invalid. */
checkValidity() {
if (this.isButton()) {
return (this.button as HTMLButtonElement).checkValidity();
}
return true;
}
/** Gets the associated form, if one exists. */
getForm(): HTMLFormElement | null {
return this.formControlController.getForm();
}
/** Checks for validity and shows the browser's validation message if the control is invalid. */
reportValidity() {
if (this.isButton()) {
return (this.button as HTMLButtonElement).reportValidity();
}
return true;
}
/** Sets a custom validation message. Pass an empty string to restore validity. */
setCustomValidity(message: string) {
if (this.isButton()) {
(this.button as HTMLButtonElement).setCustomValidity(message);
this.formControlController.updateValidity();
}
}
render() {
const isLink = this.isLink();
const tag = isLink ? literal`a` : literal`button`;
/* eslint-disable lit/no-invalid-html */
/* eslint-disable lit/binding-positions */
return html`
<${tag}
part="base"
class=${classMap({
button: true,
'button--default': this.variant === 'default',
'button--primary': this.variant === 'primary',
'button--success': this.variant === 'success',
'button--neutral': this.variant === 'neutral',
'button--warning': this.variant === 'warning',
'button--danger': this.variant === 'danger',
'button--text': this.variant === 'text',
'button--small': this.size === 'small',
'button--medium': this.size === 'medium',
'button--large': this.size === 'large',
'button--caret': this.caret,
'button--circle': this.circle,
'button--disabled': this.disabled,
'button--focused': this.hasFocus,
'button--loading': this.loading,
'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')
})}
?disabled=${ifDefined(isLink ? undefined : this.disabled)}
type=${ifDefined(isLink ? undefined : this.type)}
title=${this.title /* An empty title prevents browser validation tooltips from appearing on hover */}
name=${ifDefined(isLink ? undefined : this.name)}
value=${ifDefined(isLink ? undefined : this.value)}
href=${ifDefined(isLink ? this.href : undefined)}
target=${ifDefined(isLink ? this.target : undefined)}
download=${ifDefined(isLink ? this.download : undefined)}
rel=${ifDefined(isLink ? this.rel : undefined)}
role=${ifDefined(isLink ? undefined : 'button')}
aria-disabled=${this.disabled ? 'true' : 'false'}
tabindex=${this.disabled ? '-1' : '0'}
@blur=${this.handleBlur}
@focus=${this.handleFocus}
@invalid=${this.isButton() ? this.handleInvalid : null}
@click=${this.handleClick}
>
<slot name="prefix" part="prefix" class="button__prefix"></slot>
<slot part="label" class="button__label"></slot>
<slot name="suffix" part="suffix" class="button__suffix"></slot>
${
this.caret ? html` <sl-icon part="caret" class="button__caret" library="system" name="caret"></sl-icon> ` : ''
}
${this.loading ? html`<sl-spinner part="spinner"></sl-spinner>` : ''}
</${tag}>
`;
/* eslint-enable lit/no-invalid-html */
/* eslint-enable lit/binding-positions */
}
}

View File

@@ -68,6 +68,27 @@ describe('<sl-button>', () => {
const el = await fixture<SlButton>(html` <sl-button href="some/path" disabled>Button Label</sl-button> `);
expect(el.shadowRoot!.querySelector('a[disabled]')).not.to.exist;
});
it('should not bubble up clicks', async () => {
const button = await fixture<SlButton>(html` <sl-button disabled>Button Label</sl-button> `);
const handleClick = sinon.spy();
button.addEventListener('click', handleClick);
button.click();
expect(handleClick).not.to.have.been.called;
button.shadowRoot!.querySelector('button')!.click();
expect(handleClick).not.to.have.been.called;
const buttonLink = await fixture<SlButton>(html` <sl-button href="some/path" disabled>Button Label</sl-button> `);
buttonLink.addEventListener('click', handleClick);
buttonLink.click();
expect(handleClick).not.to.have.been.called;
buttonLink.shadowRoot!.querySelector('a')!.click();
expect(handleClick).not.to.have.been.called;
});
});
it('should have title if title attribute is set', async () => {

View File

@@ -1,9 +1,342 @@
import SlButton from './button.component.js';
import '../icon/icon.js';
import '../spinner/spinner.js';
import { classMap } from 'lit/directives/class-map.js';
import { customElement, property, query, state } from 'lit/decorators.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 { watch } from '../../internal/watch.js';
import ShoelaceElement from '../../internal/shoelace-element.js';
import styles from './button.styles.js';
import type { CSSResultGroup } from 'lit';
import type { ShoelaceFormControl } from '../../internal/shoelace-element.js';
export * from './button.component.js';
export default SlButton;
/**
* @summary Buttons represent actions that are available to the user.
* @documentation https://shoelace.style/components/button
* @status stable
* @since 2.0
*
* @dependency sl-icon
* @dependency sl-spinner
*
* @event sl-blur - Emitted when the button loses focus.
* @event sl-focus - Emitted when the button gains focus.
* @event sl-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.
*
* @slot - The button's label.
* @slot prefix - A presentational prefix icon or similar element.
* @slot suffix - A presentational suffix icon or similar element.
*
* @csspart base - The component's base wrapper.
* @csspart prefix - The container that wraps the prefix.
* @csspart label - The button's label.
* @csspart suffix - The container that wraps the suffix.
* @csspart caret - The button's caret icon, an `<sl-icon>` element.
*/
@customElement('sl-button')
export default class SlButton extends ShoelaceElement implements ShoelaceFormControl {
static styles: CSSResultGroup = styles;
SlButton.define('sl-button');
private readonly formControlController = new FormControlController(this, {
form: input => {
// Buttons support a form attribute that points to an arbitrary form, so if this attribute is set we need to query
// the form from the same root using its id
if (input.hasAttribute('form')) {
const doc = input.getRootNode() as Document | ShadowRoot;
const formId = input.getAttribute('form')!;
return doc.getElementById(formId) as HTMLFormElement;
}
// Fall back to the closest containing form
return input.closest('form');
},
assumeInteractionOn: ['click']
});
private readonly hasSlotController = new HasSlotController(this, '[default]', 'prefix', 'suffix');
private readonly localize = new LocalizeController(this);
@query('.button') button: HTMLButtonElement | HTMLLinkElement;
@state() private hasFocus = false;
@state() invalid = false;
@property() title = ''; // make reactive to pass through
/** The button's theme variant. */
@property({ reflect: true }) variant: 'default' | 'primary' | 'success' | 'neutral' | 'warning' | 'danger' | 'text' =
'default';
/** The button's size. */
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
/** Draws the button with a caret. Used to indicate that the button triggers a dropdown menu or similar behavior. */
@property({ type: Boolean, reflect: true }) caret = false;
/** Disables the button. */
@property({ type: Boolean, reflect: true }) disabled = false;
/** Draws the button in a loading state. */
@property({ type: Boolean, reflect: true }) loading = false;
/** Draws an outlined button. */
@property({ type: Boolean, reflect: true }) outline = false;
/** Draws a pill-style button with rounded edges. */
@property({ type: Boolean, reflect: true }) pill = false;
/**
* Draws a circular icon button. When this attribute is present, the button expects a single `<sl-icon>` in the
* default slot.
*/
@property({ type: Boolean, reflect: true }) circle = false;
/**
* The type of button. Note that the default value is `button` instead of `submit`, which is opposite of how native
* `<button>` elements behave. When the type is `submit`, the button will submit the surrounding form.
*/
@property() type: 'button' | 'submit' | 'reset' = 'button';
/**
* The name of the button, submitted as a name/value pair with form data, but only when this button is the submitter.
* This attribute is ignored when `href` is present.
*/
@property() name = '';
/**
* The value of the button, submitted as a pair with the button's name as part of the form data, but only when this
* button is the submitter. This attribute is ignored when `href` is present.
*/
@property() value = '';
/** When set, the underlying button will be rendered as an `<a>` with this `href` instead of a `<button>`. */
@property() href = '';
/** Tells the browser where to open the link. Only used when `href` is present. */
@property() target: '_blank' | '_parent' | '_self' | '_top';
/**
* When using `href`, this attribute will map to the underlying link's `rel` attribute. Unlike regular links, the
* default is `noreferrer noopener` to prevent security exploits. However, if you're using `target` to point to a
* specific tab/window, this will prevent that from working correctly. You can remove or change the default value by
* setting the attribute to an empty string or a value of your choice, respectively.
*/
@property() rel = 'noreferrer noopener';
/** Tells the browser to download the linked file as this filename. Only used when `href` is present. */
@property() download?: string;
/**
* The "form owner" to associate the button with. If omitted, the closest containing form will be used instead. The
* value of this attribute must be an id of a form in the same document or shadow root as the button.
*/
@property() form: string;
/** Used to override the form owner's `action` attribute. */
@property({ attribute: 'formaction' }) formAction: string;
/** Used to override the form owner's `enctype` attribute. */
@property({ attribute: 'formenctype' })
formEnctype: 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain';
/** Used to override the form owner's `method` attribute. */
@property({ attribute: 'formmethod' }) formMethod: 'post' | 'get';
/** Used to override the form owner's `novalidate` attribute. */
@property({ attribute: 'formnovalidate', type: Boolean }) formNoValidate: boolean;
/** Used to override the form owner's `target` attribute. */
@property({ attribute: 'formtarget' }) formTarget: '_self' | '_blank' | '_parent' | '_top' | string;
/** Gets the validity state object */
get validity() {
if (this.isButton()) {
return (this.button as HTMLButtonElement).validity;
}
return validValidityState;
}
/** Gets the validation message */
get validationMessage() {
if (this.isButton()) {
return (this.button as HTMLButtonElement).validationMessage;
}
return '';
}
constructor() {
super();
this.addEventListener('click', this.handleHostClick);
}
firstUpdated() {
if (this.isButton()) {
this.formControlController.updateValidity();
}
}
private handleBlur() {
this.hasFocus = false;
this.emit('sl-blur');
}
private handleFocus() {
this.hasFocus = true;
this.emit('sl-focus');
}
private handleClick() {
if (this.type === 'submit') {
this.formControlController.submit(this);
}
if (this.type === 'reset') {
this.formControlController.reset(this);
}
}
private handleHostClick = (event: MouseEvent) => {
// Prevent the click event from being emitted when the button is disabled or loading
if (this.disabled || this.loading) {
event.preventDefault();
event.stopImmediatePropagation();
}
};
private handleInvalid(event: Event) {
this.formControlController.setValidity(false);
this.formControlController.emitInvalidEvent(event);
}
private isButton() {
return this.href ? false : true;
}
private isLink() {
return this.href ? true : false;
}
@watch('disabled', { waitUntilFirstUpdate: true })
handleDisabledChange() {
if (this.isButton()) {
// Disabled form controls are always valid
this.formControlController.setValidity(this.disabled);
}
}
/** Simulates a click on the button. */
click() {
this.button.click();
}
/** Sets focus on the button. */
focus(options?: FocusOptions) {
this.button.focus(options);
}
/** Removes focus from the button. */
blur() {
this.button.blur();
}
/** Checks for validity but does not show a validation message. Returns `true` when valid and `false` when invalid. */
checkValidity() {
if (this.isButton()) {
return (this.button as HTMLButtonElement).checkValidity();
}
return true;
}
/** Gets the associated form, if one exists. */
getForm(): HTMLFormElement | null {
return this.formControlController.getForm();
}
/** Checks for validity and shows the browser's validation message if the control is invalid. */
reportValidity() {
if (this.isButton()) {
return (this.button as HTMLButtonElement).reportValidity();
}
return true;
}
/** Sets a custom validation message. Pass an empty string to restore validity. */
setCustomValidity(message: string) {
if (this.isButton()) {
(this.button as HTMLButtonElement).setCustomValidity(message);
this.formControlController.updateValidity();
}
}
render() {
const isLink = this.isLink();
const tag = isLink ? literal`a` : literal`button`;
/* eslint-disable lit/no-invalid-html */
/* eslint-disable lit/binding-positions */
return html`
<${tag}
part="base"
class=${classMap({
button: true,
'button--default': this.variant === 'default',
'button--primary': this.variant === 'primary',
'button--success': this.variant === 'success',
'button--neutral': this.variant === 'neutral',
'button--warning': this.variant === 'warning',
'button--danger': this.variant === 'danger',
'button--text': this.variant === 'text',
'button--small': this.size === 'small',
'button--medium': this.size === 'medium',
'button--large': this.size === 'large',
'button--caret': this.caret,
'button--circle': this.circle,
'button--disabled': this.disabled,
'button--focused': this.hasFocus,
'button--loading': this.loading,
'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')
})}
?disabled=${ifDefined(isLink ? undefined : this.disabled)}
type=${ifDefined(isLink ? undefined : this.type)}
title=${this.title /* An empty title prevents browser validation tooltips from appearing on hover */}
name=${ifDefined(isLink ? undefined : this.name)}
value=${ifDefined(isLink ? undefined : this.value)}
href=${ifDefined(isLink ? this.href : undefined)}
target=${ifDefined(isLink ? this.target : undefined)}
download=${ifDefined(isLink ? this.download : undefined)}
rel=${ifDefined(isLink ? this.rel : undefined)}
role=${ifDefined(isLink ? undefined : 'button')}
aria-disabled=${this.disabled ? 'true' : 'false'}
tabindex=${this.disabled ? '-1' : '0'}
@blur=${this.handleBlur}
@focus=${this.handleFocus}
@invalid=${this.isButton() ? this.handleInvalid : null}
@click=${this.handleClick}
>
<slot name="prefix" part="prefix" class="button__prefix"></slot>
<slot part="label" class="button__label"></slot>
<slot name="suffix" part="suffix" class="button__suffix"></slot>
${
this.caret ? html` <sl-icon part="caret" class="button__caret" library="system" name="caret"></sl-icon> ` : ''
}
${this.loading ? html`<sl-spinner></sl-spinner>` : ''}
</${tag}>
`;
/* eslint-enable lit/no-invalid-html */
/* eslint-enable lit/binding-positions */
}
}
declare global {
interface HTMLElementTagNameMap {

View File

@@ -1,53 +0,0 @@
import { classMap } from 'lit/directives/class-map.js';
import { HasSlotController } from '../../internal/slot.js';
import { html } from 'lit';
import ShoelaceElement from '../../internal/shoelace-element.js';
import styles from './card.styles.js';
import type { CSSResultGroup } from 'lit';
/**
* @summary Cards can be used to group related subjects in a container.
* @documentation https://shoelace.style/components/card
* @status stable
* @since 2.0
*
* @slot - The card's main content.
* @slot header - An optional header for the card.
* @slot footer - An optional footer for the card.
* @slot image - An optional image to render at the start of the card.
*
* @csspart base - The component's base wrapper.
* @csspart image - The container that wraps the card's image.
* @csspart header - The container that wraps the card's header.
* @csspart body - The container that wraps the card's main content.
* @csspart footer - The container that wraps the card's footer.
*
* @cssproperty --border-color - The card's border color, including borders that occur inside the card.
* @cssproperty --border-radius - The border radius for the card's edges.
* @cssproperty --border-width - The width of the card's borders.
* @cssproperty --padding - The padding to use for the card's sections.
*/
export default class SlCard extends ShoelaceElement {
static styles: CSSResultGroup = styles;
private readonly hasSlotController = new HasSlotController(this, 'footer', 'header', 'image');
render() {
return html`
<div
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')
})}
>
<slot name="image" part="image" class="card__image"></slot>
<slot name="header" part="header" class="card__header"></slot>
<slot part="body" class="card__body"></slot>
<slot name="footer" part="footer" class="card__footer"></slot>
</div>
`;
}
}

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