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 `