diff --git a/.gitignore b/.gitignore
index 24a14172f..0a2fc3d4d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ docs/assets/images/sprite.svg
node_modules
src/react
cdn
+web-types.json
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
index 92476da66..e3f4ba0ae 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -7,6 +7,7 @@ docs/search.json
src/components/icon/icons
src/react/index.ts
node_modules
+package.json
package-lock.json
tsconfig.json
cdn
diff --git a/cspell.json b/cspell.json
index 1ae873361..028a62671 100644
--- a/cspell.json
+++ b/cspell.json
@@ -160,6 +160,7 @@
"unbundles",
"unbundling",
"unicons",
+ "unsanitized",
"unsupportive",
"valpha",
"valuenow",
diff --git a/custom-elements-manifest.config.js b/custom-elements-manifest.config.js
index 2e3277912..e5912cd06 100644
--- a/custom-elements-manifest.config.js
+++ b/custom-elements-manifest.config.js
@@ -1,4 +1,5 @@
import * as path from 'path';
+import { customElementJetBrainsPlugin } from 'custom-element-jet-brains-integration';
import { customElementVsCodePlugin } from 'custom-element-vs-code-integration';
import { parse } from 'comment-parser';
import { pascalCase } from 'pascal-case';
@@ -200,6 +201,15 @@ export default {
url: `https://shoelace.style/components/${tag.replace('sl-', '')}`
}
]
+ }),
+ customElementJetBrainsPlugin({
+ excludeCss: true,
+ referencesTemplate: (_, tag) => {
+ return {
+ name: 'Documentation',
+ url: `https://shoelace.style/components/${tag.replace('sl-', '')}`
+ };
+ }
})
]
};
diff --git a/docs/_includes/component.njk b/docs/_includes/component.njk
index f340c93fc..8b087b89d 100644
--- a/docs/_includes/component.njk
+++ b/docs/_includes/component.njk
@@ -137,15 +137,17 @@
{{ prop.name }}
- {% if prop.attribute != prop.name %}
-
-
-
-
- {{ prop.attribute }}
-
-
-
+ {% if prop.attribute | length > 0 %}
+ {% if prop.attribute != prop.name %}
+
+
+
+
+ {{ prop.attribute }}
+
+
+
+ {% endif %}
{% endif %}
|
@@ -185,7 +187,7 @@
- Learn more about attributes and properties.
+ Learn more about attributes and properties.
{% endif %}
{# Events #}
@@ -305,7 +307,7 @@
- Learn more about customizing CSS parts.
+ Learn more about customizing CSS parts.
{% endif %}
{# Animations #}
@@ -329,7 +331,7 @@
- Learn more about customizing animations.
+ Learn more about customizing animations.
{% endif %}
{# Dependencies #}
diff --git a/docs/_utilities/copy-code-buttons.cjs b/docs/_utilities/copy-code-buttons.cjs
index 306164b24..11b1c8c08 100644
--- a/docs/_utilities/copy-code-buttons.cjs
+++ b/docs/_utilities/copy-code-buttons.cjs
@@ -1,3 +1,5 @@
+let codeBlockId = 0;
+
/**
* Adds copy code buttons to code fields. The provided doc should be a document object provided by JSDOM. The same
* document will be returned with the appropriate DOM manipulations.
@@ -5,19 +7,14 @@
module.exports = function (doc) {
doc.querySelectorAll('pre > code').forEach(code => {
const pre = code.closest('pre');
- const button = doc.createElement('button');
- button.setAttribute('type', 'button');
- button.classList.add('copy-code-button');
- button.setAttribute('aria-label', 'Copy');
- button.innerHTML = `
-
+ const button = doc.createElement('sl-copy-button');
-
- `;
+ if (!code.id) {
+ code.id = `code-block-${++codeBlockId}`;
+ }
+
+ button.classList.add('copy-code-button');
+ button.setAttribute('from', code.id);
pre.append(button);
});
diff --git a/docs/assets/scripts/docs.js b/docs/assets/scripts/docs.js
index 60b1fa8ca..a98bc0b31 100644
--- a/docs/assets/scripts/docs.js
+++ b/docs/assets/scripts/docs.js
@@ -163,32 +163,6 @@
});
})();
-//
-// Copy code buttons
-//
-(() => {
- document.addEventListener('click', event => {
- const button = event.target.closest('.copy-code-button');
- const pre = button?.closest('pre');
- const code = pre?.querySelector('code');
- const copyIcon = button?.querySelector('.copy-code-button__copy-icon');
- const copiedIcon = button?.querySelector('.copy-code-button__copied-icon');
-
- if (button && code) {
- navigator.clipboard.writeText(code.innerText);
- copyIcon.style.display = 'none';
- copiedIcon.style.display = 'inline';
- button.classList.add('copy-code-button--copied');
-
- setTimeout(() => {
- copyIcon.style.display = 'inline';
- copiedIcon.style.display = 'none';
- button.classList.remove('copy-code-button--copied');
- }, 1000);
- }
- });
-})();
-
//
// Smooth links
//
diff --git a/docs/assets/styles/docs.css b/docs/assets/styles/docs.css
index 7b5219815..7d1caaaa5 100644
--- a/docs/assets/styles/docs.css
+++ b/docs/assets/styles/docs.css
@@ -506,46 +506,39 @@ pre .token.italic {
/* Copy code button */
.copy-code-button {
- display: flex;
- align-items: center;
- justify-content: center;
position: absolute;
- top: 0.5rem;
- right: 0.5rem;
- background: var(--sl-color-neutral-0);
- border-radius: calc(var(--docs-border-radius) * 0.875);
- border: solid 1px var(--sl-color-neutral-200);
+ top: 0;
+ right: 0;
+ white-space: normal;
color: var(--sl-color-neutral-800);
- text-transform: uppercase;
- padding: 0.5rem;
- margin: 0;
- cursor: pointer;
- transition: 100ms opacity, 100ms scale;
+ transition: 150ms opacity, 150ms scale;
}
-.copy-code-button svg {
- width: 1rem;
- height: 1rem;
+.copy-code-button::part(button) {
+ background-color: var(--sl-color-neutral-50);
+ border-radius: 0 var(--docs-border-radius) 0 var(--docs-border-radius);
+ padding: 0.75rem;
+}
+
+.copy-code-button::part(button):hover {
+ background-color: color-mix(in oklch, var(--sl-color-neutral-50), var(--sl-color-neutral-1000) 3%);
+}
+
+.copy-code-button::part(button):active {
+ background-color: color-mix(in oklch, var(--sl-color-neutral-50), var(--sl-color-neutral-1000) 6%);
}
pre .copy-code-button {
opacity: 0;
- scale: 0.9;
+ scale: 0.75;
}
pre:hover .copy-code-button,
-.copy-code-button:focus-visible {
+.copy-code-button:focus-within {
opacity: 1;
scale: 1;
}
-pre:hover .copy-code-button:hover,
-pre:hover .copy-code-button--copied {
- background: var(--sl-color-neutral-200);
- border-color: var(--sl-color-neutral-300);
- color: var(--sl-color-neutral-900);
-}
-
/* Callouts */
.callout {
position: relative;
diff --git a/docs/pages/components/dropdown.md b/docs/pages/components/dropdown.md
index da08f8816..b1876056e 100644
--- a/docs/pages/components/dropdown.md
+++ b/docs/pages/components/dropdown.md
@@ -310,6 +310,96 @@ const App = () => (
);
```
+### Submenus
+
+To create a submenu, nest an `` element in a [menu item](/components/menu-item).
+
+```html:preview
+
+ Edit
+
+
+ Undo
+ Redo
+
+ Cut
+ Copy
+ Paste
+
+
+ Find
+
+ Find…
+ Find Next
+ Find Previous
+
+
+
+ Transformations
+
+ Make uppercase
+ Make lowercase
+ Capitalize
+
+
+
+
+```
+
+```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';
+
+const css = `
+ .dropdown-hoist {
+ border: solid 2px var(--sl-panel-border-color);
+ padding: var(--sl-spacing-medium);
+ overflow: hidden;
+ }
+`;
+
+const App = () => (
+ <>
+
+ Edit
+
+
+ Undo
+ Redo
+
+ Cut
+ Copy
+ Paste
+
+
+ Find
+
+ Find…
+ Find Next
+ Find Previous
+
+
+
+ Transformations
+
+ Make uppercase
+ Make lowercase
+ Capitalize
+
+
+
+
+ >
+);
+```
+
+:::warning
+As a UX best practice, avoid using more than one level of submenu when possible.
+:::
+
### Hoisting
Dropdown panels will be clipped if they're inside a container that has `overflow: auto|hidden`. The `hoist` attribute forces the panel to use a fixed positioning strategy, allowing it to break out of the container. In this case, the panel will be positioned relative to its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.
@@ -349,7 +439,6 @@ Dropdown panels will be clipped if they're inside a container that has `overflow
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';
diff --git a/docs/pages/components/menu.md b/docs/pages/components/menu.md
index 2039960ae..dd9c662d5 100644
--- a/docs/pages/components/menu.md
+++ b/docs/pages/components/menu.md
@@ -44,3 +44,112 @@ const App = () => (
:::tip
Menus are intended for system menus (dropdown menus, select menus, context menus, etc.). They should not be mistaken for navigation menus which serve a different purpose and have a different semantic meaning. If you're building navigation, use ` |