mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-13 20:49:15 +00:00
Compare commits
245 Commits
v2.2.0
...
tree-focus
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
886049d714 | ||
|
|
848c059d51 | ||
|
|
8ffbd02db7 | ||
|
|
e88d57d17d | ||
|
|
5f4de6d9f5 | ||
|
|
2cce87deeb | ||
|
|
630b5b19a0 | ||
|
|
2ce1451a9f | ||
|
|
2d1badba96 | ||
|
|
1b5db078a7 | ||
|
|
91095bd63a | ||
|
|
d9703a64fd | ||
|
|
4c22e72390 | ||
|
|
d05b8fca20 | ||
|
|
afca2ad2e0 | ||
|
|
b2aa854d98 | ||
|
|
287fff7cf1 | ||
|
|
136ecae4a6 | ||
|
|
cac772d5e6 | ||
|
|
e1dedcb1b5 | ||
|
|
c4901eca68 | ||
|
|
a001c2d12b | ||
|
|
c4c622eabd | ||
|
|
1ae018bedd | ||
|
|
24929e27c1 | ||
|
|
33a8d92aec | ||
|
|
32d21fa560 | ||
|
|
347d8b7f79 | ||
|
|
8f9c15913b | ||
|
|
60d7f688eb | ||
|
|
15f914914c | ||
|
|
2914475821 | ||
|
|
8e831aa3e7 | ||
|
|
985d4585c4 | ||
|
|
854db13bd7 | ||
|
|
4ddf80459a | ||
|
|
89fc2ff643 | ||
|
|
d7145f1f84 | ||
|
|
441a957432 | ||
|
|
67cbb85682 | ||
|
|
0005d16a06 | ||
|
|
ca5ab03cd4 | ||
|
|
c9e30022df | ||
|
|
c167bdd80f | ||
|
|
b9f62bb1bc | ||
|
|
a01b2cf8a2 | ||
|
|
f4b2623c8f | ||
|
|
af8426579e | ||
|
|
6b9ba9becf | ||
|
|
c6cc7b6983 | ||
|
|
0e869ec18d | ||
|
|
1b347874ef | ||
|
|
ff5b1e8573 | ||
|
|
73ad76a2fa | ||
|
|
aadcb486a9 | ||
|
|
4c854d64a7 | ||
|
|
c2e02d34ad | ||
|
|
8c8977549c | ||
|
|
24ef154d42 | ||
|
|
b5a3045bae | ||
|
|
7404e496cb | ||
|
|
5ba2c7eeec | ||
|
|
514a7f3d51 | ||
|
|
15474b83b1 | ||
|
|
a5f1bc6c82 | ||
|
|
834d44e0e4 | ||
|
|
c070149ae6 | ||
|
|
b0b6ea943e | ||
|
|
65b72217ea | ||
|
|
c4c2e8e3a9 | ||
|
|
47018d61cd | ||
|
|
d18db9adfa | ||
|
|
41913c8c58 | ||
|
|
68b982a744 | ||
|
|
a582302a79 | ||
|
|
bd3b2c93ee | ||
|
|
4704d63791 | ||
|
|
415a1477bb | ||
|
|
f363d5e187 | ||
|
|
efb0ee9c48 | ||
|
|
96daee5e1a | ||
|
|
d236206cce | ||
|
|
1ef8e1cf73 | ||
|
|
dc63f858b0 | ||
|
|
b8a3952153 | ||
|
|
4b2a62f660 | ||
|
|
08c074e44b | ||
|
|
d1953b0215 | ||
|
|
b268d7dd8e | ||
|
|
90e56e2f07 | ||
|
|
45ddaa4d38 | ||
|
|
0de54b163a | ||
|
|
620b86cddb | ||
|
|
32d0ac4147 | ||
|
|
a16733eb93 | ||
|
|
b67eef484c | ||
|
|
5166964659 | ||
|
|
e72c2df6d2 | ||
|
|
cdafb3870c | ||
|
|
20abf21791 | ||
|
|
482d155af4 | ||
|
|
ece23c727e | ||
|
|
b7726cd514 | ||
|
|
c31fe1ed35 | ||
|
|
9e034810d9 | ||
|
|
217aabe55a | ||
|
|
1dea2f384e | ||
|
|
c24edec6b9 | ||
|
|
add09ca5b8 | ||
|
|
57bd3632e8 | ||
|
|
6f44b6ffa6 | ||
|
|
ca8ba2d16b | ||
|
|
c37e4ba6b5 | ||
|
|
82cc778a0f | ||
|
|
5085ef831b | ||
|
|
4721bd117b | ||
|
|
57b13d848a | ||
|
|
e01a43f01e | ||
|
|
ec92351acf | ||
|
|
8c44eb75d5 | ||
|
|
aa48566aef | ||
|
|
7cbb26cbdb | ||
|
|
b8b6175a64 | ||
|
|
de5ad1b1b9 | ||
|
|
0866785495 | ||
|
|
947b3a9ec4 | ||
|
|
6db27ca51f | ||
|
|
f966ba97d7 | ||
|
|
1af711bc89 | ||
|
|
c71da4a075 | ||
|
|
d609fa87b4 | ||
|
|
dd16b0f65f | ||
|
|
6144e5eff4 | ||
|
|
c4db99f5a3 | ||
|
|
21431b0e56 | ||
|
|
136e6bc915 | ||
|
|
05aeb78b01 | ||
|
|
22010a1f9b | ||
|
|
e0fd6b210e | ||
|
|
be1c38f0e5 | ||
|
|
b1bdedd3a3 | ||
|
|
429b47963b | ||
|
|
466c8a0883 | ||
|
|
3a212030c0 | ||
|
|
7f1ac48f5f | ||
|
|
9c3e344ae0 | ||
|
|
db147e77b0 | ||
|
|
0384b03528 | ||
|
|
fc06de7b11 | ||
|
|
5ada0a7093 | ||
|
|
f136b8eb12 | ||
|
|
56a160464f | ||
|
|
3dc9430932 | ||
|
|
afa69b4f9c | ||
|
|
102f46d185 | ||
|
|
9c5e184d82 | ||
|
|
385b5451c8 | ||
|
|
0e7487257b | ||
|
|
eab0e3219f | ||
|
|
cf89c901a2 | ||
|
|
902b08cc0f | ||
|
|
caf9a09efa | ||
|
|
65734dc993 | ||
|
|
0f02fffc3a | ||
|
|
931ecad8c5 | ||
|
|
c137f83df6 | ||
|
|
d3a0a38dce | ||
|
|
b76af1aa21 | ||
|
|
5cf6a37ee2 | ||
|
|
63194abf93 | ||
|
|
e196b0915a | ||
|
|
d2369d1de8 | ||
|
|
a9bbcc5556 | ||
|
|
8d9430e7a2 | ||
|
|
0411754949 | ||
|
|
91ffaa1a2d | ||
|
|
ae9972a91a | ||
|
|
478fa6f2bb | ||
|
|
6a52a04591 | ||
|
|
a8f87e0d5e | ||
|
|
cbc96fdf5c | ||
|
|
b4d24dd9af | ||
|
|
4b66cc2acb | ||
|
|
3766d5ce27 | ||
|
|
4b7d686754 | ||
|
|
b948a07a4d | ||
|
|
6d3505aefa | ||
|
|
b22650ff51 | ||
|
|
23a7f65b49 | ||
|
|
f4fba8eab4 | ||
|
|
1734bf54a7 | ||
|
|
88efec7815 | ||
|
|
e335189bb8 | ||
|
|
d03ca4ab95 | ||
|
|
257407758f | ||
|
|
2443c046aa | ||
|
|
d710eb3947 | ||
|
|
7b2f6f230d | ||
|
|
07cb6070cc | ||
|
|
bd7dc2a7be | ||
|
|
db931c12be | ||
|
|
765b311a08 | ||
|
|
ce198d9c0b | ||
|
|
8f5893931b | ||
|
|
221be48589 | ||
|
|
234ff2619d | ||
|
|
b37be46ba3 | ||
|
|
6e2ea508db | ||
|
|
0e6e2abd28 | ||
|
|
db1bdfbf65 | ||
|
|
7bf0f647b3 | ||
|
|
df25f8617b | ||
|
|
ad2099a27f | ||
|
|
708127f96d | ||
|
|
9deb51e95a | ||
|
|
67852ea657 | ||
|
|
7240f4f8f4 | ||
|
|
17ee89a5e8 | ||
|
|
f2177dccaf | ||
|
|
6aaf17b81a | ||
|
|
d113d13792 | ||
|
|
ab9cb5f185 | ||
|
|
76fd7aa28d | ||
|
|
8f17bf4e9d | ||
|
|
0f0f71af9b | ||
|
|
e624701022 | ||
|
|
4cedfc3201 | ||
|
|
d88d9fc81a | ||
|
|
051baa4ff5 | ||
|
|
57c3d7009b | ||
|
|
a27fd4d2e9 | ||
|
|
79ac425e2b | ||
|
|
857f318f9c | ||
|
|
c0966bf767 | ||
|
|
86cecc9e30 | ||
|
|
ec036d8e61 | ||
|
|
77b25f4581 | ||
|
|
a8d59b3329 | ||
|
|
2371c5490f | ||
|
|
a127b8722e | ||
|
|
9c573fb454 | ||
|
|
a346d18930 | ||
|
|
a32488baeb | ||
|
|
a4131caeda | ||
|
|
6c62a4f4c0 |
@@ -96,6 +96,12 @@ module.exports = {
|
||||
'@typescript-eslint/strict-boolean-expressions': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.cjs'],
|
||||
env: {
|
||||
node: true
|
||||
}
|
||||
},
|
||||
{
|
||||
extends: ['plugin:chai-expect/recommended', 'plugin:chai-friendly/recommended'],
|
||||
files: ['*.test.ts'],
|
||||
@@ -179,6 +185,17 @@ module.exports = {
|
||||
]
|
||||
}
|
||||
],
|
||||
'import/extensions': [
|
||||
'error',
|
||||
'always',
|
||||
{
|
||||
ignorePackages: true,
|
||||
pattern: {
|
||||
js: 'always',
|
||||
ts: 'never'
|
||||
}
|
||||
}
|
||||
],
|
||||
'import/no-duplicates': 'warn',
|
||||
'sort-imports-es6-autofix/sort-imports-es6': [
|
||||
2,
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,7 +1,8 @@
|
||||
.DS_Store
|
||||
_site
|
||||
.cache
|
||||
docs/dist
|
||||
docs/search.json
|
||||
.DS_Store
|
||||
dist
|
||||
docs/assets/images/sprite.svg
|
||||
node_modules
|
||||
src/react
|
||||
cdn
|
||||
|
||||
@@ -9,3 +9,5 @@ src/react/index.ts
|
||||
node_modules
|
||||
package-lock.json
|
||||
tsconfig.json
|
||||
cdn
|
||||
_site
|
||||
|
||||
@@ -51,9 +51,7 @@ Once you've cloned the repo, run the following command.
|
||||
npm start
|
||||
```
|
||||
|
||||
This will spin up the Shoelace dev server. After the initial build, a browser will open automatically. There is currently no hot module reloading (HMR), as browser's don't provide a way to reregister custom elements, but most changes to the source will reload the browser automatically.
|
||||
|
||||
The documentation is powered by Docsify, which uses raw markdown files to generate pages. As such, no static files are built for the docs.
|
||||
This will spin up the dev server. After the initial build, a browser will open automatically. There is currently no hot module reloading (HMR), as browser's don't provide a way to reregister custom elements, but most changes to the source will reload the browser automatically.
|
||||
|
||||
### Building
|
||||
|
||||
|
||||
16
cspell.json
16
cspell.json
@@ -9,11 +9,16 @@
|
||||
"atrule",
|
||||
"autocorrect",
|
||||
"autofix",
|
||||
"autoload",
|
||||
"autoloader",
|
||||
"autoloading",
|
||||
"autoplay",
|
||||
"bezier",
|
||||
"boxicons",
|
||||
"CACHEABLE",
|
||||
"callout",
|
||||
"callouts",
|
||||
"cdndir",
|
||||
"chatbubble",
|
||||
"checkmark",
|
||||
"claviska",
|
||||
@@ -23,6 +28,7 @@
|
||||
"colocated",
|
||||
"colour",
|
||||
"combobox",
|
||||
"Commonmark",
|
||||
"Composability",
|
||||
"Consolas",
|
||||
"contenteditable",
|
||||
@@ -36,8 +42,10 @@
|
||||
"datetime",
|
||||
"describedby",
|
||||
"Docsify",
|
||||
"dogfood",
|
||||
"dropdowns",
|
||||
"easings",
|
||||
"endraw",
|
||||
"enterkeyhint",
|
||||
"eqeqeq",
|
||||
"erroneou",
|
||||
@@ -76,6 +84,7 @@
|
||||
"labelledby",
|
||||
"Laravel",
|
||||
"LaViska",
|
||||
"linkify",
|
||||
"listbox",
|
||||
"listitem",
|
||||
"litelement",
|
||||
@@ -96,10 +105,13 @@
|
||||
"noopener",
|
||||
"noreferrer",
|
||||
"novalidate",
|
||||
"npmdir",
|
||||
"Numberish",
|
||||
"outdir",
|
||||
"ParamagicDev",
|
||||
"peta",
|
||||
"petabit",
|
||||
"prismjs",
|
||||
"progressbar",
|
||||
"radiogroup",
|
||||
"Railsbyte",
|
||||
@@ -107,6 +119,8 @@
|
||||
"reregister",
|
||||
"resizer",
|
||||
"resizers",
|
||||
"retargeted",
|
||||
"RETRYABLE",
|
||||
"rgba",
|
||||
"roadmap",
|
||||
"Roboto",
|
||||
@@ -119,7 +133,9 @@
|
||||
"scroller",
|
||||
"Segoe",
|
||||
"semibold",
|
||||
"sitedir",
|
||||
"slotchange",
|
||||
"smartquotes",
|
||||
"spacebar",
|
||||
"stylesheet",
|
||||
"Tabbable",
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# Not Found
|
||||
|
||||
<img class="not-found-image" src="/assets/images/undraw-not-found.svg" alt="Cute monsters hiding behind a tree">
|
||||
|
||||
Sorry, I couldn't find that page. Have you tried pressing <kbd>/</kbd> to search?
|
||||
347
docs/_includes/component.njk
Normal file
347
docs/_includes/component.njk
Normal file
@@ -0,0 +1,347 @@
|
||||
{% extends "default.njk" %}
|
||||
|
||||
{# Find the component based on the `tag` front matter #}
|
||||
{% set component = getComponent('sl-' + page.fileSlug) %}
|
||||
|
||||
{% block content %}
|
||||
{# Determine the badge variant #}
|
||||
{% if component.status == 'stable' %}
|
||||
{% set badgeVariant = 'primary' %}
|
||||
{% elseif component.status == 'experimental' %}
|
||||
{% set badgeVariant = 'warning' %}
|
||||
{% elseif component.status == 'planned' %}
|
||||
{% set badgeVariant = 'neutral' %}
|
||||
{% elseif component.status == 'deprecated' %}
|
||||
{% set badgeVariant = 'danger' %}
|
||||
{% else %}
|
||||
{% set badgeVariant = 'neutral' %}
|
||||
{% endif %}
|
||||
|
||||
{# Header #}
|
||||
<header class="component-header">
|
||||
<h1>{{ component.name | classNameToComponentName }}</h1>
|
||||
|
||||
<div class="component-header__tag">
|
||||
<code><{{ component.tagName }}> | {{ component.name }}</code>
|
||||
</div>
|
||||
|
||||
<div class="component-header__info">
|
||||
<sl-badge variant="neutral" pill>
|
||||
Since {{component.since or '?' }}
|
||||
</sl-badge>
|
||||
<sl-badge variant="{{ badgeVariant }}" pill style="text-transform: capitalize;">
|
||||
{{ component.status }}
|
||||
</sl-badge>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<p class="component-summary">
|
||||
{% if component.summary %}
|
||||
{{ component.summary | markdownInline | safe }}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{# Markdown content #}
|
||||
{{ content | safe }}
|
||||
|
||||
{# Importing #}
|
||||
<h2>Importing</h2>
|
||||
<p>
|
||||
If you're using the autoloader or the traditional loader, you can ignore this section. Otherwise, feel free to use
|
||||
any of the following snippets to <a href="/getting-started/installation#cherry-picking">cherry pick</a> this component.
|
||||
</p>
|
||||
|
||||
<sl-tab-group>
|
||||
<sl-tab slot="nav" panel="script">Script</sl-tab>
|
||||
<sl-tab slot="nav" panel="import">Import</sl-tab>
|
||||
<sl-tab slot="nav" panel="bundler">Bundler</sl-tab>
|
||||
<sl-tab slot="nav" panel="react">React</sl-tab>
|
||||
|
||||
<sl-tab-panel name="script">
|
||||
<p>
|
||||
To import this component from <a href="https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace">the CDN</a>
|
||||
using a script tag:
|
||||
</p>
|
||||
<pre><code class="language-html"><script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@{{ meta.version }}/{{ meta.cdndir }}/{{ component.path }}"></script></code></pre>
|
||||
</sl-tab-panel>
|
||||
|
||||
<sl-tab-panel name="import">
|
||||
<p>
|
||||
To import this component from <a href="https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace">the CDN</a>
|
||||
using a JavaScript import:
|
||||
</p>
|
||||
<pre><code class="language-js">import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@{{ meta.version }}/{{ meta.cdndir }}/{{ component.path }}';</code></pre>
|
||||
</sl-tab-panel>
|
||||
|
||||
<sl-tab-panel name="bundler">
|
||||
<p>
|
||||
To import this component using <a href="{{ rootUrl('/getting-started/installation#bundling') }}">a bundler</a>:
|
||||
</p>
|
||||
<pre><code class="language-js">import '@shoelace-style/shoelace/{{ meta.npmdir }}/{{ component.path }}';</code></pre>
|
||||
</sl-tab-panel>
|
||||
|
||||
<sl-tab-panel name="react">
|
||||
<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';</code></pre>
|
||||
</sl-tab-panel>
|
||||
</sl-tab-group>
|
||||
|
||||
{# Slots #}
|
||||
{% if component.slots.length %}
|
||||
<h2>Slots</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-name">Name</th>
|
||||
<th class="table-description">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for slot in component.slots %}
|
||||
<tr>
|
||||
<td class="nowrap">
|
||||
{% if slot.name %}
|
||||
<code>{{ slot.name }}</code>
|
||||
{% else %}
|
||||
(default)
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ slot.description | markdownInline | safe }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#slots') }}">using slots</a>.</em></p>
|
||||
{% endif %}
|
||||
|
||||
{# Properties #}
|
||||
{% if component.properties.length %}
|
||||
<h2>Properties</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-name">Name</th>
|
||||
<th class="table-description">Description</th>
|
||||
<th class="table-reflects">Reflects</th>
|
||||
<th class="table-type">Type</th>
|
||||
<th class="table-default">Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for prop in component.properties %}
|
||||
<tr>
|
||||
<td>
|
||||
<code class="nowrap">{{ prop.name }}</code>
|
||||
{% if prop.attribute != prop.name %}
|
||||
<br>
|
||||
<sl-tooltip content="This attribute is different from its property">
|
||||
<small>
|
||||
<code class="nowrap">
|
||||
{{ prop.attribute }}
|
||||
</code>
|
||||
</small>
|
||||
</sl-tooltip>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ prop.description | markdownInline | safe }}
|
||||
</td>
|
||||
<td style="text-align: center;">
|
||||
{% if prop.reflects %}
|
||||
<sl-icon label="yes" name="check-lg"></sl-icon>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if prop.type.text %}
|
||||
<code>{{ prop.type.text | markdownInline | safe }}</code>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if prop.default %}
|
||||
<code>{{ prop.default | markdownInline | safe }}</code>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<td class="nowrap"><code>updateComplete</code></td>
|
||||
<td>
|
||||
A read-only promise that resolves when the component has
|
||||
<a href="/getting-started/usage?#component-rendering-and-updating">finished updating</a>.
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#properties') }}">attributes and properties</a>.</em></p>
|
||||
{% endif %}
|
||||
|
||||
{# Events #}
|
||||
{% if component.events.length %}
|
||||
<h2>Events</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-name" data-flavor="html">Name</th>
|
||||
<th class="table-name" data-flavor="react">React Event</th>
|
||||
<th class="table-description">Description</th>
|
||||
<th class="table-event-detail">Event Detail</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for event in component.events %}
|
||||
<tr>
|
||||
<td data-flavor="html"><code class="nowrap">{{ event.name }}</code></td>
|
||||
<td data-flavor="react"><code class="nowrap">{{ event.reactName }}</code></td>
|
||||
<td>{{ event.description | markdownInline | safe }}</td>
|
||||
<td>
|
||||
{% if event.type.text %}
|
||||
<code>{{ event.type.text }}</code>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#events') }}">events</a>.</em></p>
|
||||
{% endif %}
|
||||
|
||||
{# Methods #}
|
||||
{% if component.methods.length %}
|
||||
<h2>Methods</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-name">Name</th>
|
||||
<th class="table-description">Description</th>
|
||||
<th class="table-arguments">Arguments</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for method in component.methods %}
|
||||
<tr>
|
||||
<td class="nowrap"><code>{{ method.name }}()</code></td>
|
||||
<td>{{ method.description | markdownInline | safe }}</td>
|
||||
<td>
|
||||
{% if method.parameters.length %}
|
||||
<code>
|
||||
{% for param in method.parameters %}
|
||||
{{ param.name }}: {{ param.type.text }}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
</code>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#methods') }}">methods</a>.</em></p>
|
||||
{% endif %}
|
||||
|
||||
{# Custom Properties #}
|
||||
{% if component.cssProperties.length %}
|
||||
<h2>Custom Properties</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-name">Name</th>
|
||||
<th class="table-description">Description</th>
|
||||
<th class="table-default">Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for cssProperty in component.cssProperties %}
|
||||
<tr>
|
||||
<td class="nowrap"><code>{{ cssProperty.name }}</code></td>
|
||||
<td>{{ cssProperty.description | markdownInline | safe }}</td>
|
||||
<td>{{ cssProperty.default }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#custom-properties') }}">customizing CSS custom properties</a>.</em></p>
|
||||
{% endif %}
|
||||
|
||||
{# CSS Parts #}
|
||||
{% if component.cssParts.length %}
|
||||
<h2>Parts</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-name">Name</th>
|
||||
<th class="table-description">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for cssPart in component.cssParts %}
|
||||
<tr>
|
||||
<td class="nowrap"><code>{{ cssPart.name }}</code></td>
|
||||
<td>{{ cssPart.description | markdownInline | safe }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#component-parts') }}">customizing CSS parts</a>.</em></p>
|
||||
{% endif %}
|
||||
|
||||
{# Animations #}
|
||||
{% if component.animations.length %}
|
||||
<h2>Animations</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-name">Name</th>
|
||||
<th class="table-description">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for animation in component.animations %}
|
||||
<tr>
|
||||
<td class="nowrap"><code>{{ animation.name }}</code></td>
|
||||
<td>{{ animation.description | markdownInline | safe }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#animations') }}">customizing animations</a>.</em></p>
|
||||
{% endif %}
|
||||
|
||||
{# Dependencies #}
|
||||
{% if component.dependencies.length %}
|
||||
<h2>Dependencies</h2>
|
||||
|
||||
<p>This component automatically imports the following dependencies.</p>
|
||||
|
||||
<ul>
|
||||
{% for dependency in component.dependencies %}
|
||||
<li><code><{{ dependency }}></code></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
150
docs/_includes/default.njk
Normal file
150
docs/_includes/default.njk
Normal file
@@ -0,0 +1,150 @@
|
||||
<!DOCTYPE html>
|
||||
<html
|
||||
lang="en"
|
||||
data-layout="{{ layout }}"
|
||||
data-shoelace-version="{{ meta.version }}"
|
||||
>
|
||||
<head>
|
||||
{# Metadata #}
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="{{ meta.description }}" />
|
||||
<title>{{ meta.title }}</title>
|
||||
|
||||
{# Opt out of Turbo caching #}
|
||||
<meta name="turbo-cache-control">
|
||||
|
||||
{# Stylesheets #}
|
||||
<link rel="stylesheet" href="{{ assetUrl('styles/docs.css') }}" />
|
||||
<link rel="stylesheet" href="{{ assetUrl('styles/code-previews.css') }}" />
|
||||
<link rel="stylesheet" href="{{ assetUrl('styles/search.css') }}" />
|
||||
|
||||
{# Favicons #}
|
||||
<link rel="icon" href="{{ assetUrl('images/logo.svg') }}" type="image/x-icon" />
|
||||
|
||||
{# Twitter Cards #}
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:creator" content="shoelace_style" />
|
||||
<meta name="twitter:image" content="{{ assetUrl(meta.image, true) }}" />
|
||||
|
||||
{# OpenGraph #}
|
||||
<meta property="og:url" content="{{ rootUrl(page.url, true) }}" />
|
||||
<meta property="og:title" content="{{ meta.title }}" />
|
||||
<meta property="og:description" content="{{ meta.description }}" />
|
||||
<meta property="og:image" content="{{ assetUrl(meta.image, true) }}" />
|
||||
|
||||
{# Shoelace #}
|
||||
<link rel="stylesheet" href="/dist/themes/light.css" />
|
||||
<link rel="stylesheet" href="/dist/themes/dark.css" />
|
||||
<script type="module" src="/dist/shoelace-autoloader.js"></script>
|
||||
|
||||
{# Set the initial theme and menu states here to prevent flashing #}
|
||||
<script>
|
||||
(() => {
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const theme = localStorage.getItem('theme') || 'auto';
|
||||
document.documentElement.classList.toggle('sl-theme-dark', theme === 'dark' || (theme === 'auto' && prefersDark));
|
||||
})();
|
||||
</script>
|
||||
|
||||
{# Turbo + Scroll positioning #}
|
||||
<script src="{{ assetUrl('scripts/turbo.js') }}" type="module"></script>
|
||||
<script src="{{ assetUrl('scripts/docs.js') }}" defer></script>
|
||||
<script src="{{ assetUrl('scripts/code-previews.js') }}" defer></script>
|
||||
<script src="{{ assetUrl('scripts/lunr.js') }}" defer></script>
|
||||
<script src="{{ assetUrl('scripts/search.js') }}" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<a id="skip-to-main" class="visually-hidden" href="#main-content" data-smooth-link="false">
|
||||
Skip to main content
|
||||
</a>
|
||||
|
||||
{# Menu toggle #}
|
||||
<button id="menu-toggle" type="button" aria-label="Menu">
|
||||
<svg width="148" height="148" viewBox="0 0 148 148" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="currentColor" stroke-width="18" fill="none" fill-rule="evenodd" stroke-linecap="round">
|
||||
<path d="M9.5 125.5h129M9.5 74.5h129M9.5 23.5h129"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{# Icon toolbar #}
|
||||
<div id="icon-toolbar">
|
||||
{# GitHub #}
|
||||
<a href="https://github.com/shoelace-style/shoelace" title="View Shoelace on GitHub">
|
||||
<sl-icon name="github"></sl-icon>
|
||||
</a>
|
||||
|
||||
{# Twitter #}
|
||||
<a href="https://twitter.com/shoelace_style" title="Follow Shoelace on Twitter">
|
||||
<sl-icon name="twitter"></sl-icon>
|
||||
</a>
|
||||
|
||||
{# Theme selector #}
|
||||
<sl-dropdown id="theme-selector" placement="bottom-end" distance="3">
|
||||
<sl-button slot="trigger" size="small" variant="text" caret title="Press \ to toggle">
|
||||
<sl-icon class="only-light" name="sun-fill"></sl-icon>
|
||||
<sl-icon class="only-dark" name="moon-fill"></sl-icon>
|
||||
</sl-button>
|
||||
<sl-menu>
|
||||
<sl-menu-item type="checkbox" value="light">Light</sl-menu-item>
|
||||
<sl-menu-item type="checkbox" value="dark">Dark</sl-menu-item>
|
||||
<sl-divider></sl-divider>
|
||||
<sl-menu-item type="checkbox" value="auto">System</sl-menu-item>
|
||||
</sl-menu>
|
||||
</sl-dropdown>
|
||||
</div>
|
||||
|
||||
<aside id="sidebar" data-preserve-scroll>
|
||||
<header>
|
||||
<a href="/">
|
||||
<img src="{{ assetUrl('images/wordmark.svg') }}" alt="Shoelace" />
|
||||
</a>
|
||||
<div class="sidebar-version">
|
||||
{{ meta.version }}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="sidebar-buttons">
|
||||
<sl-button size="small" class="repo-button repo-button--github" href="https://github.com/shoelace-style/shoelace" target="_blank">
|
||||
<sl-icon slot="prefix" name="github"></sl-icon> Code
|
||||
</sl-button>
|
||||
<sl-button size="small" class="repo-button repo-button--star" href="https://github.com/shoelace-style/shoelace/stargazers" target="_blank">
|
||||
<sl-icon slot="prefix" name="star-fill"></sl-icon> Star
|
||||
</sl-button>
|
||||
<sl-button size="small" class="repo-button repo-button--twitter" href="https://twitter.com/shoelace_style" target="_blank">
|
||||
<sl-icon slot="prefix" name="twitter"></sl-icon> Follow
|
||||
</sl-button>
|
||||
</div>
|
||||
|
||||
<button class="search-box" type="button" title="Press / to search" aria-label="Search" data-plugin="search">
|
||||
<sl-icon name="search"></sl-icon>
|
||||
<span>Search</span>
|
||||
</button>
|
||||
|
||||
<nav>
|
||||
{% include 'sidebar.njk' %}
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
{# Content #}
|
||||
<main>
|
||||
<a id="main-content"></a>
|
||||
<article id="content" class="content{% if toc %} content--with-toc{% endif %}">
|
||||
{% if toc %}
|
||||
<div class="content__toc">
|
||||
<ul>
|
||||
<li class="top"><a href="#">{{ meta.title }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="content__body">
|
||||
{% block content %}
|
||||
{{ content | safe }}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
65
docs/_includes/sidebar.njk
Normal file
65
docs/_includes/sidebar.njk
Normal file
@@ -0,0 +1,65 @@
|
||||
<ul>
|
||||
<li>
|
||||
<h2>Getting Started</h2>
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/getting-started/installation">Installation</a></li>
|
||||
<li><a href="/getting-started/usage">Usage</a></li>
|
||||
<li><a href="/getting-started/themes">Themes</a></li>
|
||||
<li><a href="/getting-started/customizing">Customizing</a></li>
|
||||
<li><a href="/getting-started/form-controls">Form Controls</a></li>
|
||||
<li><a href="/getting-started/localization">Localization</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h2>Frameworks</h2>
|
||||
<ul>
|
||||
<li><a href="/frameworks/react">React</a></li>
|
||||
<li><a href="/frameworks/vue">Vue</a></li>
|
||||
<li><a href="/frameworks/angular">Angular</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h2>Resources</h2>
|
||||
<ul>
|
||||
<li><a href="/resources/community">Community</a></li>
|
||||
<li><a href="https://github.com/shoelace-style/shoelace/discussions">Help & Support</a></li>
|
||||
<li><a href="/resources/accessibility">Accessibility</a></li>
|
||||
<li><a href="/resources/contributing">Contributing</a></li>
|
||||
<li><a href="/resources/changelog">Changelog</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h2>Components</h2>
|
||||
<ul>
|
||||
{% for component in meta.components %}
|
||||
<li>
|
||||
<a href="/components/{{ component.tagName | removeSlPrefix }}">
|
||||
{{ component.name | classNameToComponentName }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h2>Design Tokens</h2>
|
||||
<ul>
|
||||
<li><a href="/tokens/typography">Typography</a></li>
|
||||
<li><a href="/tokens/color">Color</a></li>
|
||||
<li><a href="/tokens/spacing">Spacing</a></li>
|
||||
<li><a href="/tokens/elevation">Elevation</a></li>
|
||||
<li><a href="/tokens/border-radius">Border Radius</a></li>
|
||||
<li><a href="/tokens/transition">Transition</a></li>
|
||||
<li><a href="/tokens/z-index">Z-index</a></li>
|
||||
<li><a href="/tokens/more">More Tokens</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h2>Tutorials</h2>
|
||||
<ul>
|
||||
<li><a href="/tutorials/integrating-with-laravel">Integrating with Laravel</a></li>
|
||||
<li><a href="/tutorials/integrating-with-nextjs">Integrating with NextJS</a></li>
|
||||
<li><a href="/tutorials/integrating-with-rails">Integrating with Rails</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
103
docs/_sidebar.md
103
docs/_sidebar.md
@@ -1,103 +0,0 @@
|
||||
- Getting Started
|
||||
|
||||
- [Overview](/)
|
||||
- [Installation](/getting-started/installation)
|
||||
- [Usage](/getting-started/usage)
|
||||
- [Themes](/getting-started/themes)
|
||||
- [Customizing](/getting-started/customizing)
|
||||
- [Form Controls](/getting-started/form-controls)
|
||||
- [Localization](/getting-started/localization)
|
||||
|
||||
- Frameworks
|
||||
|
||||
- [React](/frameworks/react)
|
||||
- [Vue](/frameworks/vue)
|
||||
- [Angular](/frameworks/angular)
|
||||
|
||||
- Resources
|
||||
|
||||
- [Community](/resources/community)
|
||||
- [Accessibility](/resources/accessibility)
|
||||
- [Contributing](/resources/contributing)
|
||||
- [Changelog](/resources/changelog)
|
||||
|
||||
- Components
|
||||
|
||||
- [Alert](/components/alert)
|
||||
- [Avatar](/components/avatar)
|
||||
- [Badge](/components/badge)
|
||||
- [Breadcrumb](/components/breadcrumb)
|
||||
- [Breadcrumb Item](/components/breadcrumb-item)
|
||||
- [Button](/components/button)
|
||||
- [Button Group](/components/button-group)
|
||||
- [Card](/components/card)
|
||||
- [Carousel](/components/carousel)
|
||||
- [Carousel Item](/components/carousel-item)
|
||||
- [Checkbox](/components/checkbox)
|
||||
- [Color Picker](/components/color-picker)
|
||||
- [Details](/components/details)
|
||||
- [Dialog](/components/dialog)
|
||||
- [Divider](/components/divider)
|
||||
- [Drawer](/components/drawer)
|
||||
- [Dropdown](/components/dropdown)
|
||||
- [Icon](/components/icon)
|
||||
- [Icon Button](/components/icon-button)
|
||||
- [Image Comparer](/components/image-comparer)
|
||||
- [Input](/components/input)
|
||||
- [Menu](/components/menu)
|
||||
- [Menu Item](/components/menu-item)
|
||||
- [Menu Label](/components/menu-label)
|
||||
- [Option](/components/option)
|
||||
- [Progress Bar](/components/progress-bar)
|
||||
- [Progress Ring](/components/progress-ring)
|
||||
- [QR Code](/components/qr-code)
|
||||
- [Radio](/components/radio)
|
||||
- [Radio Button](/components/radio-button)
|
||||
- [Radio Group](/components/radio-group)
|
||||
- [Range](/components/range)
|
||||
- [Rating](/components/rating)
|
||||
- [Select](/components/select)
|
||||
- [Skeleton](/components/skeleton)
|
||||
- [Spinner](/components/spinner)
|
||||
- [Split Panel](/components/split-panel)
|
||||
- [Switch](/components/switch)
|
||||
- [Tab Group](/components/tab-group)
|
||||
- [Tab](/components/tab)
|
||||
- [Tab Panel](/components/tab-panel)
|
||||
- [Tag](/components/tag)
|
||||
- [Textarea](/components/textarea)
|
||||
- [Tooltip](/components/tooltip)
|
||||
- [Tree](/components/tree)
|
||||
- [Tree Item](/components/tree-item)
|
||||
<!--plop:component-->
|
||||
|
||||
- Utilities
|
||||
|
||||
- [Animated Image](/components/animated-image)
|
||||
- [Animation](/components/animation)
|
||||
- [Format Bytes](/components/format-bytes)
|
||||
- [Format Date](/components/format-date)
|
||||
- [Format Number](/components/format-number)
|
||||
- [Include](/components/include)
|
||||
- [Mutation Observer](/components/mutation-observer)
|
||||
- [Popup](/components/popup)
|
||||
- [Relative Time](/components/relative-time)
|
||||
- [Resize Observer](/components/resize-observer)
|
||||
- [Visually Hidden](/components/visually-hidden)
|
||||
|
||||
- Design Tokens
|
||||
|
||||
- [Typography](/tokens/typography)
|
||||
- [Color](/tokens/color)
|
||||
- [Spacing](/tokens/spacing)
|
||||
- [Elevation](/tokens/elevation)
|
||||
- [Border Radius](/tokens/border-radius)
|
||||
- [Transition](/tokens/transition)
|
||||
- [Z-index](/tokens/z-index)
|
||||
- [More](/tokens/more)
|
||||
|
||||
- Tutorials
|
||||
|
||||
- [Integrating with Laravel](/tutorials/integrating-with-laravel)
|
||||
- [Integrating with NextJS](/tutorials/integrating-with-nextjs)
|
||||
- [Integrating with Rails](/tutorials/integrating-with-rails)
|
||||
35
docs/_utilities/active-links.cjs
Normal file
35
docs/_utilities/active-links.cjs
Normal file
@@ -0,0 +1,35 @@
|
||||
function normalizePathname(pathname) {
|
||||
// Remove /index.html
|
||||
if (pathname.endsWith('/index.html')) {
|
||||
pathname = pathname.replace(/\/index\.html/, '');
|
||||
}
|
||||
|
||||
// Remove trailing slashes
|
||||
return pathname.replace(/\/$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a class name to links that are currently active.
|
||||
*/
|
||||
module.exports = function (doc, options) {
|
||||
options = {
|
||||
className: 'active-link', // the class to add to active links
|
||||
pathname: undefined, // the current pathname to compare
|
||||
within: 'body', // element containing the target links
|
||||
...options
|
||||
};
|
||||
|
||||
const within = doc.querySelector(options.within);
|
||||
|
||||
if (!within) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
within.querySelectorAll('a').forEach(link => {
|
||||
if (normalizePathname(options.pathname) === normalizePathname(link.pathname)) {
|
||||
link.classList.add(options.className);
|
||||
}
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
64
docs/_utilities/anchor-headings.cjs
Normal file
64
docs/_utilities/anchor-headings.cjs
Normal file
@@ -0,0 +1,64 @@
|
||||
const { createSlug } = require('./strings.cjs');
|
||||
|
||||
/**
|
||||
* Turns headings into clickable, deep linkable anchors. The provided doc should be a document object provided by JSDOM.
|
||||
* The same document will be returned with the appropriate DOM manipulations.
|
||||
*/
|
||||
module.exports = function (doc, options) {
|
||||
options = {
|
||||
levels: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], // the headings to convert
|
||||
className: 'anchor-heading', // the class name to add
|
||||
within: 'body', // the element containing the target headings
|
||||
...options
|
||||
};
|
||||
|
||||
const within = doc.querySelector(options.within);
|
||||
|
||||
if (!within) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
within.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(heading => {
|
||||
const hasAnchor = heading.querySelector('a');
|
||||
const anchor = doc.createElement('a');
|
||||
let id = heading.textContent ?? '';
|
||||
let suffix = 0;
|
||||
|
||||
// Skip heading levels we don't care about
|
||||
if (!options.levels?.includes(heading.tagName.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert dots to underscores
|
||||
id = id.replace(/\./g, '_');
|
||||
|
||||
// Turn it into a slug
|
||||
id = createSlug(id);
|
||||
|
||||
// Make sure it starts with a letter
|
||||
if (!/^[a-z]/i.test(id)) {
|
||||
id = `id_${id}`;
|
||||
}
|
||||
|
||||
// Make sure the id is unique
|
||||
const originalId = id;
|
||||
while (doc.getElementById(id) !== null) {
|
||||
id = `${originalId}-${++suffix}`;
|
||||
}
|
||||
|
||||
if (hasAnchor || !id) return;
|
||||
|
||||
heading.setAttribute('id', id);
|
||||
anchor.setAttribute('href', `#${encodeURIComponent(id)}`);
|
||||
anchor.setAttribute('aria-label', `Direct link to "${heading.textContent}"`);
|
||||
|
||||
if (options.className) {
|
||||
heading.classList.add(options.className);
|
||||
}
|
||||
|
||||
// Append the anchor
|
||||
heading.append(anchor);
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
71
docs/_utilities/cem.cjs
Normal file
71
docs/_utilities/cem.cjs
Normal file
@@ -0,0 +1,71 @@
|
||||
const customElementsManifest = require('../../dist/custom-elements.json');
|
||||
|
||||
//
|
||||
// Export it here so we can import it elsewhere and use the same version
|
||||
//
|
||||
module.exports.customElementsManifest = customElementsManifest;
|
||||
|
||||
//
|
||||
// Gets all components from custom-elements.json and returns them in a more documentation-friendly format.
|
||||
//
|
||||
module.exports.getAllComponents = function () {
|
||||
const allComponents = [];
|
||||
|
||||
customElementsManifest.modules?.forEach(module => {
|
||||
module.declarations?.forEach(declaration => {
|
||||
if (declaration.customElement) {
|
||||
// Generate the dist path based on the src path and attach it to the component
|
||||
declaration.path = module.path.replace(/^src\//, 'dist/').replace(/\.ts$/, '.js');
|
||||
|
||||
// Remove members that are private or don't have a description
|
||||
const members = declaration.members?.filter(member => member.description && member.privacy !== 'private');
|
||||
const methods = members?.filter(prop => prop.kind === 'method' && prop.privacy !== 'private');
|
||||
const properties = members?.filter(prop => {
|
||||
// Look for a corresponding attribute
|
||||
const attribute = declaration.attributes?.find(attr => attr.fieldName === prop.name);
|
||||
if (attribute) {
|
||||
prop.attribute = attribute.name || attribute.fieldName;
|
||||
}
|
||||
|
||||
return prop.kind === 'field' && prop.privacy !== 'private';
|
||||
});
|
||||
allComponents.push({
|
||||
...declaration,
|
||||
methods,
|
||||
properties
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Build dependency graphs
|
||||
allComponents.forEach(component => {
|
||||
const dependencies = [];
|
||||
|
||||
// Recursively fetch sub-dependencies
|
||||
function getDependencies(tag) {
|
||||
const cmp = allComponents.find(c => c.tagName === tag);
|
||||
if (!cmp || !Array.isArray(component.dependencies)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cmp.dependencies?.forEach(dependentTag => {
|
||||
if (!dependencies.includes(dependentTag)) {
|
||||
dependencies.push(dependentTag);
|
||||
}
|
||||
getDependencies(dependentTag);
|
||||
});
|
||||
}
|
||||
|
||||
getDependencies(component.tagName);
|
||||
|
||||
component.dependencies = dependencies.sort();
|
||||
});
|
||||
|
||||
// Sort by name
|
||||
return allComponents.sort((a, b) => {
|
||||
if (a.name < b.name) return -1;
|
||||
if (a.name > b.name) return 1;
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
138
docs/_utilities/code-previews.cjs
Normal file
138
docs/_utilities/code-previews.cjs
Normal file
@@ -0,0 +1,138 @@
|
||||
let count = 1;
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns code fields with the :preview suffix into interactive code previews.
|
||||
*/
|
||||
module.exports = function (doc, options) {
|
||||
options = {
|
||||
within: 'body', // the element containing the code fields to convert
|
||||
...options
|
||||
};
|
||||
|
||||
const within = doc.querySelector(options.within);
|
||||
if (!within) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
within.querySelectorAll('[class*=":preview"]').forEach(code => {
|
||||
const pre = code.closest('pre');
|
||||
if (!pre) {
|
||||
return;
|
||||
}
|
||||
const adjacentPre = pre.nextElementSibling?.tagName.toLowerCase() === 'pre' ? pre.nextElementSibling : null;
|
||||
const reactCode = adjacentPre?.querySelector('code[class$="react"]');
|
||||
const sourceGroupId = `code-preview-source-group-${count}`;
|
||||
const isExpanded = code.getAttribute('class').includes(':expanded');
|
||||
const noCodePen = code.getAttribute('class').includes(':no-codepen');
|
||||
|
||||
count++;
|
||||
|
||||
const htmlButton = `
|
||||
<button type="button"
|
||||
title="Show HTML code"
|
||||
class="code-preview__button code-preview__button--html"
|
||||
>
|
||||
HTML
|
||||
</button>
|
||||
`;
|
||||
|
||||
const reactButton = `
|
||||
<button type="button" title="Show React code" class="code-preview__button code-preview__button--react">
|
||||
React
|
||||
</button>
|
||||
`;
|
||||
|
||||
const codePenButton = `
|
||||
<button type="button" class="code-preview__button code-preview__button--codepen" title="Edit on CodePen">
|
||||
<svg
|
||||
width="138"
|
||||
height="26"
|
||||
viewBox="0 0 138 26"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.3"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M80 6h-9v14h9 M114 6h-9 v14h9 M111 13h-6 M77 13h-6 M122 20V6l11 14V6 M22 16.7L33 24l11-7.3V9.3L33 2L22 9.3V16.7z M44 16.7L33 9.3l-11 7.4 M22 9.3l11 7.3 l11-7.3 M33 2v7.3 M33 16.7V24 M88 14h6c2.2 0 4-1.8 4-4s-1.8-4-4-4h-6v14 M15 8c-1.3-1.3-3-2-5-2c-4 0-7 3-7 7s3 7 7 7 c2 0 3.7-0.8 5-2 M64 13c0 4-3 7-7 7h-5V6h5C61 6 64 9 64 13z" />
|
||||
</svg>
|
||||
</button>
|
||||
`;
|
||||
|
||||
const codePreview = `
|
||||
<div class="code-preview ${isExpanded ? 'code-preview--expanded' : ''}">
|
||||
<div class="code-preview__preview">
|
||||
${code.textContent}
|
||||
<div class="code-preview__resizer">
|
||||
<sl-icon name="grip-vertical"></sl-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="code-preview__source-group" id="${sourceGroupId}">
|
||||
<div class="code-preview__source code-preview__source--html" ${reactCode ? 'data-flavor="html"' : ''}>
|
||||
<pre><code class="language-html">${escapeHtml(code.textContent)}</code></pre>
|
||||
</div>
|
||||
|
||||
${
|
||||
reactCode
|
||||
? `
|
||||
<div class="code-preview__source code-preview__source--react" data-flavor="react">
|
||||
<pre><code class="language-jsx">${escapeHtml(reactCode.textContent)}</code></pre>
|
||||
</div>
|
||||
`
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="code-preview__buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="code-preview__button code-preview__toggle"
|
||||
aria-expanded="${isExpanded ? 'true' : 'false'}"
|
||||
aria-controls="${sourceGroupId}"
|
||||
>
|
||||
Source
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
${reactCode ? ` ${htmlButton} ${reactButton} ` : ''}
|
||||
|
||||
${noCodePen ? '' : codePenButton}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
pre.insertAdjacentHTML('afterend', codePreview);
|
||||
pre.remove();
|
||||
|
||||
if (adjacentPre) {
|
||||
adjacentPre.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Wrap code preview scripts in anonymous functions so they don't run in the global scope
|
||||
doc.querySelectorAll('.code-preview__preview script').forEach(script => {
|
||||
if (script.type === 'module') {
|
||||
// Modules are already scoped
|
||||
script.textContent = script.innerHTML;
|
||||
} else {
|
||||
// Wrap non-modules in an anonymous function so they don't run in the global scope
|
||||
script.textContent = `(() => { ${script.innerHTML} })();`;
|
||||
}
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
26
docs/_utilities/copy-code-buttons.cjs
Normal file
26
docs/_utilities/copy-code-buttons.cjs
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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 = `
|
||||
<svg class="copy-code-button__copy-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-files" viewBox="0 0 16 16" part="svg">
|
||||
<path d="M13 0H6a2 2 0 0 0-2 2 2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2 2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm0 13V4a2 2 0 0 0-2-2H5a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zM3 4a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4z"></path>
|
||||
</svg>
|
||||
|
||||
<svg class="copy-code-button__copied-icon" style="display: none;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check-lg" viewBox="0 0 16 16" part="svg">
|
||||
<path d="M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425a.247.247 0 0 1 .02-.022Z"></path>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
pre.append(button);
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
41
docs/_utilities/external-links.cjs
Normal file
41
docs/_utilities/external-links.cjs
Normal file
@@ -0,0 +1,41 @@
|
||||
const { isExternalLink } = require('./strings.cjs');
|
||||
|
||||
/**
|
||||
* Transforms external links to make them safer and optionally add a target. The provided doc should be a document
|
||||
* object provided by JSDOM. The same document will be returned with the appropriate DOM manipulations.
|
||||
*/
|
||||
module.exports = function (doc, options) {
|
||||
options = {
|
||||
className: 'external-link', // the class name to add to links
|
||||
noopener: true, // sets rel="noopener"
|
||||
noreferrer: true, // sets rel="noreferrer"
|
||||
ignore: () => false, // callback function to filter links that should be ignored
|
||||
within: 'body', // element that contains the target links
|
||||
target: '', // sets the target attribute
|
||||
...options
|
||||
};
|
||||
|
||||
const within = doc.querySelector(options.within);
|
||||
|
||||
if (within) {
|
||||
within.querySelectorAll('a').forEach(link => {
|
||||
if (isExternalLink(link) && !options.ignore(link)) {
|
||||
link.classList.add(options.className);
|
||||
|
||||
const rel = [];
|
||||
if (options.noopener) rel.push('noopener');
|
||||
if (options.noreferrer) rel.push('noreferrer');
|
||||
|
||||
if (rel.length) {
|
||||
link.setAttribute('rel', rel.join(' '));
|
||||
}
|
||||
|
||||
if (options.target) {
|
||||
link.setAttribute('target', options.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return doc;
|
||||
};
|
||||
63
docs/_utilities/highlight-code.cjs
Normal file
63
docs/_utilities/highlight-code.cjs
Normal file
@@ -0,0 +1,63 @@
|
||||
const Prism = require('prismjs');
|
||||
const PrismLoader = require('prismjs/components/index.js');
|
||||
|
||||
PrismLoader('diff');
|
||||
PrismLoader.silent = true;
|
||||
|
||||
/** Highlights a code string. */
|
||||
function highlight(code, language) {
|
||||
const alias = language.replace(/^diff-/, '');
|
||||
const isDiff = /^diff-/i.test(language);
|
||||
|
||||
// Auto-load the target language
|
||||
if (!Prism.languages[alias]) {
|
||||
PrismLoader(alias);
|
||||
|
||||
if (!Prism.languages[alias]) {
|
||||
throw new Error(`Unsupported language for code highlighting: "${language}"`);
|
||||
}
|
||||
}
|
||||
|
||||
// Register diff-* languages to use the diff grammar
|
||||
if (isDiff) {
|
||||
Prism.languages[language] = Prism.languages.diff;
|
||||
}
|
||||
|
||||
return Prism.highlight(code, Prism.languages[language], language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights all code fields that have a language parameter. If the language has a colon in its name, the first chunk
|
||||
* will be the language used and additional chunks will be applied as classes to the `<pre>`. For example, a code field
|
||||
* tagged with "html:preview" will be rendered as `<pre class="language-html preview">`.
|
||||
*
|
||||
* The provided doc should be a document object provided by JSDOM. The same document will be returned with the
|
||||
* appropriate DOM manipulations.
|
||||
*/
|
||||
module.exports = function (doc) {
|
||||
doc.querySelectorAll('pre > code[class]').forEach(code => {
|
||||
// Look for class="language-*" and split colons into separate classes
|
||||
code.classList.forEach(className => {
|
||||
if (className.startsWith('language-')) {
|
||||
//
|
||||
// We use certain suffixes to indicate code previews, expanded states, etc. The class might look something like
|
||||
// this:
|
||||
//
|
||||
// class="language-html:preview:expanded"
|
||||
//
|
||||
// The language will always come first, so we need to drop the "language-" prefix and everything after the first
|
||||
// color to get the highlighter language.
|
||||
//
|
||||
const language = className.replace(/^language-/, '').split(':')[0];
|
||||
|
||||
try {
|
||||
code.innerHTML = highlight(code.textContent ?? '', language);
|
||||
} catch (err) {
|
||||
// Language not found, skip it
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
67
docs/_utilities/markdown.cjs
Normal file
67
docs/_utilities/markdown.cjs
Normal file
@@ -0,0 +1,67 @@
|
||||
const MarkdownIt = require('markdown-it');
|
||||
const markdownItContainer = require('markdown-it-container');
|
||||
const markdownItIns = require('markdown-it-ins');
|
||||
const markdownItKbd = require('markdown-it-kbd');
|
||||
const markdownItMark = require('markdown-it-mark');
|
||||
const markdownItReplaceIt = require('markdown-it-replace-it');
|
||||
|
||||
const markdown = MarkdownIt({
|
||||
html: true,
|
||||
xhtmlOut: false,
|
||||
breaks: false,
|
||||
langPrefix: 'language-',
|
||||
linkify: false,
|
||||
typographer: false
|
||||
});
|
||||
|
||||
// Third-party plugins
|
||||
markdown.use(markdownItContainer);
|
||||
markdown.use(markdownItIns);
|
||||
markdown.use(markdownItKbd);
|
||||
markdown.use(markdownItMark);
|
||||
markdown.use(markdownItReplaceIt);
|
||||
|
||||
// Callouts
|
||||
['tip', 'warning', 'danger'].forEach(type => {
|
||||
markdown.use(markdownItContainer, type, {
|
||||
render: function (tokens, idx) {
|
||||
if (tokens[idx].nesting === 1) {
|
||||
return `<div role="alert" class="callout callout--${type}">`;
|
||||
}
|
||||
return '</div>\n';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Asides
|
||||
markdown.use(markdownItContainer, 'aside', {
|
||||
render: function (tokens, idx) {
|
||||
if (tokens[idx].nesting === 1) {
|
||||
return `<aside>`;
|
||||
}
|
||||
return '</aside>\n';
|
||||
}
|
||||
});
|
||||
|
||||
// Details
|
||||
markdown.use(markdownItContainer, 'details', {
|
||||
validate: params => params.trim().match(/^details\s+(.*)$/),
|
||||
render: (tokens, idx) => {
|
||||
const m = tokens[idx].info.trim().match(/^details\s+(.*)$/);
|
||||
if (tokens[idx].nesting === 1) {
|
||||
return `<details>\n<summary><span>${markdown.utils.escapeHtml(m[1])}</span></summary>\n`;
|
||||
}
|
||||
return '</details>\n';
|
||||
}
|
||||
});
|
||||
|
||||
// Replace [#1234] with a link to GitHub issues
|
||||
markdownItReplaceIt.replacements.push({
|
||||
name: 'github-issues',
|
||||
re: /\[#([0-9]+)\]/gs,
|
||||
sub: '<a href="https://github.com/shoelace-style/shoelace/issues/$1">#$1</a>',
|
||||
html: true,
|
||||
default: true
|
||||
});
|
||||
|
||||
module.exports = markdown;
|
||||
26
docs/_utilities/prettier.cjs
Normal file
26
docs/_utilities/prettier.cjs
Normal file
@@ -0,0 +1,26 @@
|
||||
const { format } = require('prettier');
|
||||
|
||||
/** Formats markup using prettier. */
|
||||
module.exports = function (content, options) {
|
||||
options = {
|
||||
arrowParens: 'avoid',
|
||||
bracketSpacing: true,
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
insertPragma: false,
|
||||
bracketSameLine: false,
|
||||
jsxSingleQuote: false,
|
||||
parser: 'html',
|
||||
printWidth: 120,
|
||||
proseWrap: 'preserve',
|
||||
quoteProps: 'as-needed',
|
||||
requirePragma: false,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
tabWidth: 2,
|
||||
trailingComma: 'none',
|
||||
useTabs: false,
|
||||
...options
|
||||
};
|
||||
|
||||
return format(content, options);
|
||||
};
|
||||
19
docs/_utilities/replacer.cjs
Normal file
19
docs/_utilities/replacer.cjs
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @typedef {object} Replacement
|
||||
* @property {string | RegExp} pattern
|
||||
* @property {string} replacement
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Array<Replacement>} Replacements
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Document} content
|
||||
* @param {Replacements} replacements
|
||||
*/
|
||||
module.exports = function (content, replacements) {
|
||||
replacements.forEach(replacement => {
|
||||
content.body.innerHTML = content.body.innerHTML.replaceAll(replacement.pattern, replacement.replacement);
|
||||
});
|
||||
};
|
||||
21
docs/_utilities/scrolling-tables.cjs
Normal file
21
docs/_utilities/scrolling-tables.cjs
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Turns headings into clickable, deep linkable anchors. The provided doc should be a document object provided by JSDOM.
|
||||
* The same document will be returned with the appropriate DOM manipulations.
|
||||
*/
|
||||
module.exports = function (doc, options) {
|
||||
const tables = [...doc.querySelectorAll('table')];
|
||||
|
||||
options = {
|
||||
className: 'table-scroll', // the class name to add to the table's container
|
||||
...options
|
||||
};
|
||||
|
||||
tables.forEach(table => {
|
||||
const div = doc.createElement('div');
|
||||
div.classList.add(options.className);
|
||||
table.insertAdjacentElement('beforebegin', div);
|
||||
div.append(table);
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
16
docs/_utilities/strings.cjs
Normal file
16
docs/_utilities/strings.cjs
Normal file
@@ -0,0 +1,16 @@
|
||||
const slugify = require('slugify');
|
||||
|
||||
/** Creates a slug from an arbitrary string of text. */
|
||||
module.exports.createSlug = function (text) {
|
||||
return slugify(String(text), {
|
||||
remove: /[^\w|\s]/g,
|
||||
lower: true
|
||||
});
|
||||
};
|
||||
|
||||
/** Determines whether or not a link is external. */
|
||||
module.exports.isExternalLink = function (link) {
|
||||
// We use the "internal" hostname when initializing JSDOM so we know that those are local links
|
||||
if (!link.hostname || link.hostname === 'internal') return false;
|
||||
return true;
|
||||
};
|
||||
42
docs/_utilities/table-of-contents.cjs
Normal file
42
docs/_utilities/table-of-contents.cjs
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Generates an in-page table of contents based on headings.
|
||||
*/
|
||||
module.exports = function (doc, options) {
|
||||
options = {
|
||||
levels: ['h2'], // headings to include (they must have an id)
|
||||
container: 'nav', // the container to append links to
|
||||
listItem: true, // if true, links will be wrapped in <li>
|
||||
within: 'body', // the element containing the headings to summarize
|
||||
...options
|
||||
};
|
||||
|
||||
const container = doc.querySelector(options.container);
|
||||
const within = doc.querySelector(options.within);
|
||||
const headingSelector = options.levels.map(h => `${h}[id]`).join(', ');
|
||||
|
||||
if (!container || !within) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
within.querySelectorAll(headingSelector).forEach(heading => {
|
||||
const listItem = doc.createElement('li');
|
||||
const link = doc.createElement('a');
|
||||
const level = heading.tagName.slice(1);
|
||||
|
||||
link.href = `#${heading.id}`;
|
||||
link.textContent = heading.textContent;
|
||||
|
||||
if (options.listItem) {
|
||||
// List item + link
|
||||
listItem.setAttribute('data-level', level);
|
||||
listItem.append(link);
|
||||
container.append(listItem);
|
||||
} else {
|
||||
// Link only
|
||||
link.setAttribute('data-level', level);
|
||||
container.append(link);
|
||||
}
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
23
docs/_utilities/typography.cjs
Normal file
23
docs/_utilities/typography.cjs
Normal file
@@ -0,0 +1,23 @@
|
||||
const smartquotes = require('smartquotes');
|
||||
|
||||
smartquotes.replacements.push([/---/g, '\u2014']); // em dash
|
||||
smartquotes.replacements.push([/--/g, '\u2013']); // en dash
|
||||
smartquotes.replacements.push([/\.\.\./g, '\u2026']); // ellipsis
|
||||
smartquotes.replacements.push([/\(c\)/gi, '\u00A9']); // copyright
|
||||
smartquotes.replacements.push([/\(r\)/gi, '\u00AE']); // registered trademark
|
||||
smartquotes.replacements.push([/\?!/g, '\u2048']); // ?!
|
||||
smartquotes.replacements.push([/!!/g, '\u203C']); // !!
|
||||
smartquotes.replacements.push([/\?\?/g, '\u2047']); // ??
|
||||
smartquotes.replacements.push([/([0-9]\s?)-(\s?[0-9])/g, '$1\u2013$2']); // number ranges use en dash
|
||||
|
||||
/**
|
||||
* Improves typography by adding smart quotes and similar corrections within the specified element(s).
|
||||
*
|
||||
* The provided doc should be a document object provided by JSDOM. The same document will be returned with the
|
||||
* appropriate DOM manipulations.
|
||||
*/
|
||||
module.exports = function (doc, selector = 'body') {
|
||||
const elements = [...doc.querySelectorAll(selector)];
|
||||
elements.forEach(el => smartquotes.element(el));
|
||||
return doc;
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 1.0 MiB |
11
docs/assets/images/awesome.svg
Normal file
11
docs/assets/images/awesome.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="733" xmlns="http://www.w3.org/2000/svg" height="733">
|
||||
<circle cy="366.5" cx="366.5" r="366.5"/>
|
||||
<circle cy="366.5" cx="366.5" r="336.5" fill="#fede58"/>
|
||||
<path d="M325 665c-121-21-194-115-212-233v-8l-25-1-1-18h481c6 13 10 27 13 41 13 94-38 146-114 193-45 23-93 29-142 26z"/>
|
||||
<path d="M372 647c52-6 98-28 138-62 28-25 46-56 51-87 4-20 1-57-5-70l-423-1c-2 56 39 118 74 157 31 34 72 54 116 63 11 2 38 2 49 0z" fill="#871945"/>
|
||||
<path d="M76 342c-13-26-13-57-9-85 6-27 18-52 35-68 21-20 50-23 77-18 15 4 28 12 39 23 18 17 30 40 36 67 4 20 4 41 0 60l-6 21z"/>
|
||||
<path d="M234 323c5-6 6-40 2-58-3-16-4-16-10-10-14 14-38 14-52 0-15-18-12-41 6-55 3-3 5-5 5-6-1-4-22-8-34-7-42 4-57.6 40-66.2 77-3 17-1 53 4 59H234z" fill="#fff"/>
|
||||
<path d="M378 343c-2-3-6-20-7-29-5-28-1-57 11-83 15-30 41-52 72-60 29-7 57 0 82 15 26 17 45 49 50 82 2 12 2 33 0 45-1 10-5 26-8 30z"/>
|
||||
<path d="M565 324c4-5 5-34 4-50-2-14-6-24-8-24-1 0-3 2-6 5-17 17-47 13-58-9-7-16-4-31 8-43 4-4 7-8 7-9 0 0-4-2-8-3-51-17-105 20-115 80-3 15 0 43 3 53z" fill="#fff"/>
|
||||
<path d="M504 590s-46 40-105 53c-66 15-114-7-114-7s14-76 93-95c76-18 126 49 126 49z" fill="#f9bedd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
32
docs/assets/images/undraw-taken.svg
Normal file
32
docs/assets/images/undraw-taken.svg
Normal file
@@ -0,0 +1,32 @@
|
||||
<svg width="673" height="739" viewBox="0 0 673 739" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill-rule="nonzero" fill="none">
|
||||
<path d="M467 149.805c-46.62-7.44-99.71-11.41-155-11.41-50.6 0-99.35 3.32-142.98 9.58.01-.67.02-1.34.05-2.01C171.475 65.082 237.996.903 318.914 1.398c80.917.494 146.649 65.48 148.066 146.387.01.68.02 1.35.02 2.02Z" fill="#0BA5E9"/>
|
||||
<path d="M337.55 1.342a149.047 149.047 0 0 0-168.93 143.229c-.031.67-.04 1.34-.05 2.01 12.961-1.86 26.384-3.454 40.164-4.784 3.478-71.745 57.64-130.8 128.816-140.455Z" opacity=".1" fill="#FFF"/>
|
||||
<path d="M532.18 161.625a600.121 600.121 0 0 0-65.2-13.84 943.364 943.364 0 0 0-108.74-10.45 1133.608 1133.608 0 0 0-83.01-.34 973.29 973.29 0 0 0-106.16 8.97 624.292 624.292 0 0 0-77.25 15.66C32.61 177.995 0 199.935 0 223.395s32.61 45.4 91.82 61.77c41.64 11.52 92.98 19.37 148.92 22.97 23.09 1.5 46.96 2.26 71.26 2.26 24.38 0 48.33-.77 71.49-2.27 50.91-3.29 98.01-10.1 137.43-20 .21-.06.41-.11.62-.16 2.66-.66 5.28-1.35 7.87-2.04.93-.26 1.85-.51 2.77-.76a.978.978 0 0 1 .16-.05c.88-.24 1.75-.49 2.62-.73 1.74-.5 3.46-.99 5.15-1.5.08-.02.15-.04.22-.06 1.47-.44 2.91-.88 4.34-1.32 1.17-.37 2.33-.73 3.48-1.1.84-.27 1.67-.54 2.49-.81.6-.2 1.19-.39 1.77-.59.79-.26 1.58-.53 2.36-.8.33-.11.66-.22.98-.34.75-.25 1.48-.51 2.21-.77.79-.28 1.58-.57 2.36-.85.65-.23 1.3-.47 1.94-.71.54-.21 1.07-.41 1.61-.61 1.47-.55 2.91-1.12 4.33-1.68.71-.29 1.42-.57 2.12-.86.69-.28 1.39-.57 2.07-.86 1.12-.47 2.22-.94 3.3-1.41.52-.24 1.05-.47 1.56-.69.39-.18.77-.35 1.16-.53.28-.12.56-.25.83-.38 1.01-.46 2.01-.93 2.99-1.4 3.76-1.8 7.27-3.64 10.53-5.52 20.45-11.71 31.24-24.7 31.24-38.2 0-23.46-32.61-45.4-91.82-61.77Zm-.54 121.62c-41.69 11.53-93.17 19.38-149.26 22.95-22.81 1.45-46.39 2.2-70.38 2.2-23.91 0-47.41-.74-70.15-2.19-56.18-3.56-107.74-11.41-149.49-22.96C34.09 267.125 2 245.875 2 223.395c0-1.986.252-3.965.74-5.89 5.1-20.28 36.47-39.26 89.62-53.96a623.806 623.806 0 0 1 76.66-15.57 976.027 976.027 0 0 1 106.8-9c11.92-.39 23.98-.583 36.18-.58 15.41 0 30.65.31 45.63.91a941.367 941.367 0 0 1 109.37 10.5 598.858 598.858 0 0 1 64.64 13.74c53.14 14.7 84.5 33.67 89.61 53.94.496 1.93.75 3.916.75 5.91 0 22.48-32.09 43.73-90.36 59.85Z" fill="#3F3D56"/>
|
||||
<path d="M623.43 224.305c0 13.36-11.01 26-30.67 37.29-3.27 1.88-6.79 3.72-10.53 5.52-.98.47-1.98.94-2.99 1.4-.27.13-.55.26-.83.38-.39.18-.77.35-1.16.53-.51.22-1.04.45-1.56.69-1.08.47-2.18.94-3.3 1.41-.68.29-1.38.58-2.07.86-.7.29-1.41.57-2.12.86-1.42.56-2.86 1.13-4.33 1.68-.54.2-1.07.4-1.61.61-.64.24-1.29.48-1.94.71-.78.28-1.57.57-2.36.85-.73.26-1.46.52-2.21.77-.32.12-.65.23-.98.34-.78.27-1.57.54-2.36.8-.58.2-1.17.39-1.77.59-.82.27-1.65.54-2.49.81-1.15.37-2.31.73-3.48 1.1-1.43.44-2.87.88-4.34 1.32-.07.02-.14.04-.22.06-1.69.51-3.41 1-5.15 1.5-.87.24-1.74.49-2.62.73a.978.978 0 0 0-.16.05c-.92.25-1.84.5-2.77.76-2.58.68-5.21 1.37-7.87 2.04-.21.05-.41.1-.62.16-38.35 9.58-85.4 16.56-137.47 19.93-22.81 1.47-46.59 2.25-71.02 2.25-24.65 0-48.63-.79-71.62-2.29-137.24-8.95-239.38-43.03-239.38-83.71.01-2.475.388-4.936 1.12-7.3.06.17.12.33.19.5 14.27 37.48 115.54 67.77 246.94 75.16 20.13 1.14 40.98 1.73 62.32 1.73 21.43 0 42.36-.6 62.57-1.74 131.29-7.42 232.46-37.72 246.68-75.17.24-.6.45-1.2.63-1.8a25.304 25.304 0 0 1 1.55 8.62ZM91.67 213.54c-16.643 0-34.331-3.58-34.331-10.216 0-6.636 17.688-10.217 34.33-10.217 16.643 0 34.331 3.58 34.331 10.217 0 6.636-17.688 10.217-34.33 10.217Zm0-18.433c-19.054 0-32.331 4.33-32.331 8.217 0 3.886 13.277 8.217 32.33 8.217 19.053 0 32.331-4.33 32.331-8.217 0-3.886-13.278-8.217-32.33-8.217Z" fill="#3F3D56"/>
|
||||
<path d="M162.67 260.54c-16.643 0-34.331-3.58-34.331-10.216 0-6.636 17.688-10.217 34.33-10.217 16.643 0 34.331 3.58 34.331 10.217 0 6.636-17.688 10.217-34.33 10.217Zm0-18.433c-19.054 0-32.331 4.33-32.331 8.217 0 3.886 13.277 8.217 32.33 8.217 19.053 0 32.331-4.33 32.331-8.217 0-3.886-13.278-8.217-32.33-8.217ZM531.67 213.54c-16.643 0-34.331-3.58-34.331-10.216 0-6.636 17.688-10.217 34.33-10.217 16.643 0 34.331 3.58 34.331 10.217 0 6.636-17.688 10.217-34.33 10.217Zm0-18.433c-19.054 0-32.331 4.33-32.331 8.217 0 3.886 13.277 8.217 32.33 8.217 19.053 0 32.331-4.33 32.331-8.217 0-3.886-13.278-8.217-32.33-8.217ZM460.67 260.54c-16.643 0-34.331-3.58-34.331-10.216 0-6.636 17.688-10.217 34.33-10.217 16.643 0 34.331 3.58 34.331 10.217 0 6.636-17.688 10.217-34.33 10.217Zm0-18.433c-19.054 0-32.331 4.33-32.331 8.217 0 3.886 13.277 8.217 32.33 8.217 19.053 0 32.331-4.33 32.331-8.217 0-3.886-13.278-8.217-32.33-8.217ZM311.67 282.54c-16.643 0-34.331-3.58-34.331-10.216 0-6.636 17.688-10.217 34.33-10.217 16.643 0 34.331 3.58 34.331 10.217 0 6.636-17.688 10.217-34.33 10.217Zm0-18.433c-19.054 0-32.331 4.33-32.331 8.217 0 3.886 13.277 8.217 32.33 8.217 19.053 0 32.331-4.33 32.331-8.217 0-3.886-13.278-8.217-32.33-8.217Z" fill="#3F3D56"/>
|
||||
<circle fill="#2F2E41" cx="336.978" cy="450.705" r="42.012"/>
|
||||
<path fill="#2F2E41" d="m300.555 488.547 20.447-10.24 5.715 11.413-20.447 10.24z"/>
|
||||
<ellipse fill="#2F2E41" transform="rotate(-56.601 300.086 492.946)" cx="300.086" cy="492.946" rx="3.989" ry="10.636"/>
|
||||
<path fill="#2F2E41" d="m347.239 489.72 5.715-11.412 20.447 10.24-5.715 11.412z"/>
|
||||
<ellipse fill="#2F2E41" transform="rotate(-33.399 373.87 492.946)" cx="373.87" cy="492.946" rx="10.636" ry="3.989"/>
|
||||
<circle fill="#FFF" cx="334.037" cy="440.429" r="14.359"/>
|
||||
<ellipse fill="#3F3D56" transform="rotate(-45 334.135 434.282)" cx="334.135" cy="434.282" rx="4.766" ry="4.8"/>
|
||||
<path d="M370.12 405c.632-15.553-12.773-28.727-29.941-29.425-17.168-.697-31.597 11.346-32.229 26.9-.632 15.553 11.302 19.087 28.47 19.785 17.167.697 33.068-1.706 33.7-17.26Z" fill="#0BA5E9"/>
|
||||
<ellipse fill="#2F2E41" transform="rotate(-40.645 380.654 456.766)" cx="380.654" cy="456.766" rx="6.594" ry="21.006"/>
|
||||
<ellipse fill="#2F2E41" transform="rotate(-49.355 293.42 456.766)" cx="293.419" cy="456.766" rx="21.006" ry="6.594"/>
|
||||
<path d="M348.517 467.262a9.572 9.572 0 1 1-18.836 3.428l-.003-.018c-.942-5.202 3.08-7.043 8.282-7.985 5.203-.942 9.615-.628 10.557 4.575Z" fill="#FFF"/>
|
||||
<path d="M266 495.395a2 2 0 0 1-2-2v-118a2 2 0 0 1 4 0v118a2 2 0 0 1-2 2ZM236 601.395a2 2 0 0 1-2-2v-86a2 2 0 0 1 4 0v86a2 2 0 0 1-2 2ZM313 530.395a2 2 0 0 1-2-2v-118a2 2 0 0 1 4 0v118a2 2 0 0 1-2 2ZM284 615.395a2 2 0 0 1-2-2v-48a2 2 0 0 1 4 0v48a2 2 0 0 1-2 2ZM325 369.395a2 2 0 0 1-2-2v-48a2 2 0 0 1 4 0v48a2 2 0 0 1-2 2ZM225 390.395a2 2 0 0 1-2-2v-48a2 2 0 0 1 4 0v48a2 2 0 0 1-2 2ZM399 395.395a2 2 0 0 1-2-2v-48a2 2 0 0 1 4 0v48a2 2 0 0 1-2 2ZM395 545.395a2 2 0 0 1-2-2v-58a2 2 0 0 1 4 0v58a2 2 0 0 1-2 2ZM355 596.395a2 2 0 0 1-2-2v-86a2 2 0 0 1 4 0v86a2 2 0 0 1-2 2ZM363 449.395a2 2 0 0 1-2-2v-118a2 2 0 0 1 4 0v118a2 2 0 0 1-2 2Z" fill="#CCC"/>
|
||||
<ellipse fill="#2F2E41" transform="rotate(-39.938 594.37 683.981)" cx="594.369" cy="683.981" rx="6.76" ry="21.534"/>
|
||||
<circle fill="#2F2E41" transform="rotate(-71.565 548.562 676.503)" cx="548.562" cy="676.503" r="43.067"/>
|
||||
<path fill="#2F2E41" d="M553.707 710.303h13.084v23.442h-13.084zM527.54 710.303h13.084v23.442H527.54z"/>
|
||||
<ellipse fill="#2F2E41" cx="555.888" cy="734.017" rx="10.903" ry="4.089"/>
|
||||
<ellipse fill="#2F2E41" cx="529.72" cy="733.472" rx="10.903" ry="4.089"/>
|
||||
<path d="M535.04 622.366c3.845-15.487 20.82-24.6 37.914-20.356 17.094 4.245 27.834 20.24 23.989 35.727-3.846 15.487-16.604 15.537-33.698 11.293-17.094-4.245-32.051-11.177-28.206-26.664Z" fill="#0BA5E9"/>
|
||||
<ellipse fill="#2F2E41" transform="rotate(-64.626 500.054 656.52)" cx="500.054" cy="656.52" rx="6.76" ry="21.534"/>
|
||||
<circle fill="#FFF" cx="542.124" cy="667.416" r="14.359"/>
|
||||
<circle fill="#3F3D56" cx="536.222" cy="662.269" r="4.786"/>
|
||||
<circle fill="#FFF" cx="542" cy="697.395" r="6"/>
|
||||
<path d="M671.531 738.395h-236a1 1 0 1 1 0-2h236a1 1 0 0 1 0 2Z" fill="#3F3D56"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
@@ -1,374 +0,0 @@
|
||||
/* global Prism */
|
||||
|
||||
(() => {
|
||||
const reactVersion = '17.0.2';
|
||||
let flavor = getFlavor();
|
||||
let count = 1;
|
||||
|
||||
// Sync flavor UI on page load
|
||||
setFlavor(getFlavor());
|
||||
|
||||
if (!window.$docsify) {
|
||||
throw new Error('Docsify must be loaded before installing this plugin.');
|
||||
}
|
||||
|
||||
function convertModuleLinks(html) {
|
||||
const version = sessionStorage.getItem('sl-version');
|
||||
|
||||
html = html
|
||||
.replace(/@shoelace-style\/shoelace/g, `https://cdn.skypack.dev/@shoelace-style/shoelace@${version}`)
|
||||
.replace(/from 'react'/g, `from 'https://cdn.skypack.dev/react@${reactVersion}'`)
|
||||
.replace(/from "react"/g, `from "https://cdn.skypack.dev/react@${reactVersion}"`);
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function getAdjacentExample(name, pre) {
|
||||
let currentPre = pre.nextElementSibling;
|
||||
|
||||
while (currentPre?.tagName.toLowerCase() === 'pre') {
|
||||
if (currentPre?.getAttribute('data-lang').split(' ').includes(name)) {
|
||||
return currentPre;
|
||||
}
|
||||
|
||||
currentPre = currentPre.nextElementSibling;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function runScript(script) {
|
||||
const newScript = document.createElement('script');
|
||||
|
||||
if (script.type === 'module') {
|
||||
newScript.type = 'module';
|
||||
newScript.textContent = script.innerHTML;
|
||||
} else {
|
||||
newScript.appendChild(document.createTextNode(`(() => { ${script.innerHTML} })();`));
|
||||
}
|
||||
|
||||
script.parentNode.replaceChild(newScript, script);
|
||||
}
|
||||
|
||||
function getFlavor() {
|
||||
return localStorage.getItem('flavor') || 'html';
|
||||
}
|
||||
|
||||
function setFlavor(newFlavor) {
|
||||
flavor = ['html', 'react'].includes(newFlavor) ? newFlavor : 'html';
|
||||
localStorage.setItem('flavor', flavor);
|
||||
|
||||
// Set the flavor class on the body
|
||||
document.body.classList.toggle('flavor-html', flavor === 'html');
|
||||
document.body.classList.toggle('flavor-react', flavor === 'react');
|
||||
}
|
||||
|
||||
window.$docsify.plugins.push(hook => {
|
||||
// Convert code blocks to previews
|
||||
hook.afterEach((html, next) => {
|
||||
const domParser = new DOMParser();
|
||||
const doc = domParser.parseFromString(html, 'text/html');
|
||||
|
||||
const htmlButton = `
|
||||
<button
|
||||
type="button"
|
||||
title="Show HTML code"
|
||||
class="code-block__button code-block__button--html ${flavor === 'html' ? 'code-block__button--selected' : ''}"
|
||||
>
|
||||
HTML
|
||||
</button>
|
||||
`;
|
||||
|
||||
const reactButton = `
|
||||
<button
|
||||
type="button"
|
||||
title="Show React code"
|
||||
class="code-block__button code-block__button--react ${
|
||||
flavor === 'react' ? 'code-block__button--selected' : ''
|
||||
}"
|
||||
>
|
||||
React
|
||||
</button>
|
||||
`;
|
||||
|
||||
const codePenButton = `
|
||||
<button type="button" class="code-block__button code-block__button--codepen" title="Edit on CodePen">
|
||||
<svg
|
||||
width="138"
|
||||
height="26"
|
||||
viewBox="0 0 138 26"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.3"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M80 6h-9v14h9 M114 6h-9 v14h9 M111 13h-6 M77 13h-6 M122 20V6l11 14V6 M22 16.7L33 24l11-7.3V9.3L33 2L22 9.3V16.7z M44 16.7L33 9.3l-11 7.4 M22 9.3l11 7.3 l11-7.3 M33 2v7.3 M33 16.7V24 M88 14h6c2.2 0 4-1.8 4-4s-1.8-4-4-4h-6v14 M15 8c-1.3-1.3-3-2-5-2c-4 0-7 3-7 7s3 7 7 7 c2 0 3.7-0.8 5-2 M64 13c0 4-3 7-7 7h-5V6h5C61 6 64 9 64 13z" />
|
||||
</svg>
|
||||
</button>
|
||||
`;
|
||||
|
||||
[...doc.querySelectorAll('code[class^="lang-"]')].forEach(code => {
|
||||
if (code.classList.contains('preview')) {
|
||||
const isExpanded = code.classList.contains('expanded');
|
||||
const pre = code.closest('pre');
|
||||
const sourceGroupId = `code-block-source-group-${count}`;
|
||||
const toggleId = `code-block-toggle-${count}`;
|
||||
const reactPre = getAdjacentExample('react', pre);
|
||||
const hasReact = reactPre !== null;
|
||||
|
||||
pre.setAttribute('data-lang', pre.getAttribute('data-lang').replace(/ preview$/, ''));
|
||||
pre.setAttribute('aria-labelledby', toggleId);
|
||||
|
||||
const codeBlock = `
|
||||
<div class="code-block ${isExpanded ? 'code-block--expanded' : ''}">
|
||||
<div class="code-block__preview">
|
||||
${code.textContent}
|
||||
<div class="code-block__resizer">
|
||||
<sl-icon name="grip-vertical"></sl-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="code-block__source-group" id="${sourceGroupId}">
|
||||
<div class="code-block__source code-block__source--html" ${hasReact ? 'data-flavor="html"' : ''}>
|
||||
${pre.outerHTML}
|
||||
</div>
|
||||
|
||||
${
|
||||
hasReact
|
||||
? `
|
||||
<div class="code-block__source code-block__source--react" data-flavor="react">
|
||||
${reactPre.outerHTML}
|
||||
</div>
|
||||
`
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="code-block__buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="code-block__button code-block__toggle"
|
||||
aria-expanded="${isExpanded ? 'true' : 'false'}"
|
||||
aria-controls="${sourceGroupId}"
|
||||
>
|
||||
Source
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
${hasReact ? ` ${htmlButton} ${reactButton} ` : ''}
|
||||
|
||||
${!code.classList.contains('no-codepen') ? codePenButton : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
pre.replaceWith(domParser.parseFromString(codeBlock, 'text/html').body);
|
||||
reactPre?.remove();
|
||||
|
||||
count++;
|
||||
}
|
||||
});
|
||||
|
||||
// Force the highlighter to run again so JSX fields get highlighted properly
|
||||
requestAnimationFrame(() => Prism.highlightAll());
|
||||
|
||||
next(doc.body.innerHTML);
|
||||
});
|
||||
|
||||
// After the page is done loading, force scripts in previews to execute
|
||||
hook.doneEach(() => {
|
||||
[...document.querySelectorAll('.code-block__preview script')].map(script => runScript(script));
|
||||
});
|
||||
|
||||
// Horizontal resizing
|
||||
hook.doneEach(() => {
|
||||
[...document.querySelectorAll('.code-block__preview')].forEach(preview => {
|
||||
const resizer = preview.querySelector('.code-block__resizer');
|
||||
let startX;
|
||||
let startWidth;
|
||||
|
||||
function dragStart(event) {
|
||||
startX = event.changedTouches ? event.changedTouches[0].pageX : event.clientX;
|
||||
startWidth = parseInt(document.defaultView.getComputedStyle(preview).width, 10);
|
||||
preview.classList.add('code-block__preview--dragging');
|
||||
event.preventDefault();
|
||||
document.documentElement.addEventListener('mousemove', dragMove);
|
||||
document.documentElement.addEventListener('touchmove', dragMove);
|
||||
document.documentElement.addEventListener('mouseup', dragStop);
|
||||
document.documentElement.addEventListener('touchend', dragStop);
|
||||
}
|
||||
|
||||
function dragMove(event) {
|
||||
setWidth(startWidth + (event.changedTouches ? event.changedTouches[0].pageX : event.pageX) - startX);
|
||||
}
|
||||
|
||||
function dragStop() {
|
||||
preview.classList.remove('code-block__preview--dragging');
|
||||
document.documentElement.removeEventListener('mousemove', dragMove);
|
||||
document.documentElement.removeEventListener('touchmove', dragMove);
|
||||
document.documentElement.removeEventListener('mouseup', dragStop);
|
||||
document.documentElement.removeEventListener('touchend', dragStop);
|
||||
}
|
||||
|
||||
function setWidth(width) {
|
||||
preview.style.width = `${width}px`;
|
||||
}
|
||||
|
||||
resizer.addEventListener('mousedown', dragStart);
|
||||
resizer.addEventListener('touchstart', dragStart, { passive: true });
|
||||
}, false);
|
||||
});
|
||||
});
|
||||
|
||||
// Toggle source mode
|
||||
document.addEventListener('click', event => {
|
||||
const button = event.target.closest('.code-block__button');
|
||||
const codeBlock = button?.closest('.code-block');
|
||||
|
||||
if (button?.classList.contains('code-block__button--html')) {
|
||||
// Show HTML
|
||||
setFlavor('html');
|
||||
toggleSource(codeBlock, true);
|
||||
} else if (button?.classList.contains('code-block__button--react')) {
|
||||
// Show React
|
||||
setFlavor('react');
|
||||
toggleSource(codeBlock, true);
|
||||
} else if (button?.classList.contains('code-block__toggle')) {
|
||||
// Toggle source
|
||||
toggleSource(codeBlock);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update flavor buttons
|
||||
[...document.querySelectorAll('.code-block')].forEach(cb => {
|
||||
cb.querySelector('.code-block__button--html')?.classList.toggle(
|
||||
'code-block__button--selected',
|
||||
flavor === 'html'
|
||||
);
|
||||
cb.querySelector('.code-block__button--react')?.classList.toggle(
|
||||
'code-block__button--selected',
|
||||
flavor === 'react'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function toggleSource(codeBlock, force) {
|
||||
const toggle = codeBlock.querySelector('.code-block__toggle');
|
||||
|
||||
if (toggle) {
|
||||
codeBlock.classList.toggle('code-block--expanded', force === undefined ? undefined : force);
|
||||
event.target.setAttribute('aria-expanded', codeBlock.classList.contains('code-block--expanded'));
|
||||
}
|
||||
}
|
||||
|
||||
// Show pulse when copying
|
||||
document.addEventListener('click', event => {
|
||||
const button = event.target.closest('.docsify-copy-code-button');
|
||||
if (button) {
|
||||
button.classList.remove('copied');
|
||||
requestAnimationFrame(() => {
|
||||
button.addEventListener('animationend', () => button.classList.remove('copied'), { once: true });
|
||||
button.classList.add('copied');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Open in CodePen
|
||||
document.addEventListener('click', event => {
|
||||
const button = event.target.closest('button');
|
||||
const version = sessionStorage.getItem('sl-version');
|
||||
|
||||
if (button?.classList.contains('code-block__button--codepen')) {
|
||||
const codeBlock = button.closest('.code-block');
|
||||
const htmlExample = codeBlock.querySelector('.code-block__source--html > pre > code')?.textContent;
|
||||
const reactExample = codeBlock.querySelector('.code-block__source--react > pre > code')?.textContent;
|
||||
const isReact = flavor === 'react' && typeof reactExample === 'string';
|
||||
const theme = localStorage.getItem('theme');
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const isDark = theme === 'dark' || (theme === 'auto' && prefersDark);
|
||||
const editors = isReact ? '0010' : '1000';
|
||||
let htmlTemplate = '';
|
||||
let jsTemplate = '';
|
||||
let cssTemplate = '';
|
||||
|
||||
const form = document.createElement('form');
|
||||
form.action = 'https://codepen.io/pen/define';
|
||||
form.method = 'POST';
|
||||
form.target = '_blank';
|
||||
|
||||
// HTML templates
|
||||
if (!isReact) {
|
||||
htmlTemplate =
|
||||
`<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${version}/dist/shoelace.js"></script>\n` +
|
||||
`\n${htmlExample}`;
|
||||
jsTemplate = '';
|
||||
}
|
||||
|
||||
// React templates
|
||||
if (isReact) {
|
||||
htmlTemplate = '<div id="root"></div>';
|
||||
jsTemplate =
|
||||
`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@${version}/dist/utilities/base-path';\n` +
|
||||
`\n` +
|
||||
`// Set the base path for Shoelace assets\n` +
|
||||
`setBasePath('https://cdn.skypack.dev/@shoelace-style/shoelace@${version}/dist/')\n` +
|
||||
`\n${convertModuleLinks(reactExample)}\n` +
|
||||
`\n` +
|
||||
`ReactDOM.render(<App />, document.getElementById('root'));`;
|
||||
}
|
||||
|
||||
// CSS templates
|
||||
cssTemplate =
|
||||
`@import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${version}/dist/themes/${
|
||||
isDark ? 'dark' : 'light'
|
||||
}.css';\n` +
|
||||
'\n' +
|
||||
'body {\n' +
|
||||
' font: 16px sans-serif;\n' +
|
||||
' background-color: var(--sl-color-neutral-0);\n' +
|
||||
' color: var(--sl-color-neutral-900);\n' +
|
||||
' padding: 1rem;\n' +
|
||||
'}';
|
||||
|
||||
// Docs: https://blog.codepen.io/documentation/prefill/
|
||||
const data = {
|
||||
title: '',
|
||||
description: '',
|
||||
tags: ['shoelace', 'web components'],
|
||||
editors,
|
||||
head: `<meta name="viewport" content="width=device-width">`,
|
||||
html_classes: `sl-theme-${isDark ? 'dark' : 'light'}`,
|
||||
css_external: ``,
|
||||
js_external: ``,
|
||||
js_module: true,
|
||||
js_pre_processor: isReact ? 'babel' : 'none',
|
||||
html: htmlTemplate,
|
||||
css: cssTemplate,
|
||||
js: jsTemplate
|
||||
};
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'data';
|
||||
input.value = JSON.stringify(data);
|
||||
form.append(input);
|
||||
|
||||
document.body.append(form);
|
||||
form.submit();
|
||||
form.remove();
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -1,594 +0,0 @@
|
||||
(() => {
|
||||
const isDev = location.hostname === 'localhost';
|
||||
const isNext = location.hostname === 'next.shoelace.style';
|
||||
const customElements = fetch('/dist/custom-elements.json')
|
||||
.then(res => res.json())
|
||||
.catch(err => console.error(err));
|
||||
|
||||
function createPropsTable(props) {
|
||||
const table = document.createElement('table');
|
||||
table.classList.add('metadata-table');
|
||||
table.innerHTML = `
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Reflects</th>
|
||||
<th>Type</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${props
|
||||
.map(prop => {
|
||||
const hasAttribute = !!prop.attribute;
|
||||
const isAttributeDifferent = prop.attribute !== prop.name;
|
||||
let attributeInfo = '';
|
||||
|
||||
if (!hasAttribute) {
|
||||
attributeInfo = `<br><small>(property only)</small>`;
|
||||
} else if (isAttributeDifferent) {
|
||||
attributeInfo = `
|
||||
<br>
|
||||
<sl-tooltip content="This attribute is different from its property">
|
||||
<small>
|
||||
<code class="nowrap">
|
||||
${escapeHtml(prop.attribute)}
|
||||
</code>
|
||||
</small>
|
||||
</sl-tooltip>`;
|
||||
}
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>
|
||||
<code class="nowrap">${escapeHtml(prop.name)}</code>
|
||||
${attributeInfo}
|
||||
</td>
|
||||
<td>
|
||||
${escapeHtml(prop.description)}
|
||||
</td>
|
||||
<td style="text-align: center;">${
|
||||
prop.reflects ? '<sl-icon label="yes" name="check-lg"></sl-icon>' : ''
|
||||
}</td>
|
||||
<td>${prop.type?.text ? `<code>${escapeHtml(prop.type?.text || '')}</code>` : '-'}</td>
|
||||
<td>${prop.default ? `<code>${escapeHtml(prop.default)}</code>` : '-'}</td>
|
||||
</tr>
|
||||
`;
|
||||
})
|
||||
.join('')}
|
||||
|
||||
<tr>
|
||||
<td class="nowrap"><code>updateComplete</code></td>
|
||||
<td>
|
||||
A read-only promise that resolves when the component has
|
||||
<a href="/getting-started/usage?id=component-rendering-and-updating">finished updating</a>.
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
`;
|
||||
|
||||
return table.outerHTML;
|
||||
}
|
||||
|
||||
function createEventsTable(events) {
|
||||
const table = document.createElement('table');
|
||||
table.classList.add('metadata-table');
|
||||
table.innerHTML = `
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-flavor="html">Name</th>
|
||||
<th data-flavor="react">React Event</th>
|
||||
<th>Description</th>
|
||||
<th>Event Detail</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${events
|
||||
.map(
|
||||
event => `
|
||||
<tr>
|
||||
<td data-flavor="html"><code class="nowrap">${escapeHtml(event.name)}</code></td>
|
||||
<td data-flavor="react"><code class="nowrap">${escapeHtml(event.reactName)}</code></td>
|
||||
<td>${escapeHtml(event.description)}</td>
|
||||
<td>${event.type?.text ? `<code>${escapeHtml(event.type?.text)}` : '-'}</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join('')}
|
||||
</tbody>
|
||||
`;
|
||||
|
||||
return table.outerHTML;
|
||||
}
|
||||
|
||||
function createMethodsTable(methods) {
|
||||
const table = document.createElement('table');
|
||||
table.classList.add('metadata-table');
|
||||
table.innerHTML = `
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Arguments</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${methods
|
||||
.map(
|
||||
method => `
|
||||
<tr>
|
||||
<td class="nowrap"><code>${escapeHtml(method.name)}()</code></td>
|
||||
<td>${escapeHtml(method.description)}</td>
|
||||
<td>
|
||||
${
|
||||
method.parameters?.length
|
||||
? `
|
||||
<code>${escapeHtml(
|
||||
method.parameters.map(param => `${param.name}: ${param.type?.text || ''}`).join(', ')
|
||||
)}</code>
|
||||
`
|
||||
: '-'
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join('')}
|
||||
</tbody>
|
||||
`;
|
||||
|
||||
return table.outerHTML;
|
||||
}
|
||||
|
||||
function createSlotsTable(slots) {
|
||||
const table = document.createElement('table');
|
||||
table.classList.add('metadata-table');
|
||||
table.innerHTML = `
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${slots
|
||||
.map(
|
||||
slot => `
|
||||
<tr>
|
||||
<td class="nowrap">${slot.name ? `<code>${escapeHtml(slot.name)}</code>` : '(default)'}</td>
|
||||
<td>${escapeHtml(slot.description)}</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join('')}
|
||||
</tbody>
|
||||
`;
|
||||
|
||||
return table.outerHTML;
|
||||
}
|
||||
|
||||
function createCustomPropertiesTable(styles) {
|
||||
const table = document.createElement('table');
|
||||
table.classList.add('metadata-table');
|
||||
table.innerHTML = `
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${styles
|
||||
.map(
|
||||
style => `
|
||||
<tr>
|
||||
<td class="nowrap"><code>${escapeHtml(style.name)}</code></td>
|
||||
<td>${escapeHtml(style.description)}</td>
|
||||
<td>${style.default ? `<code>${escapeHtml(style.default)}</code>` : ''}</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join('')}
|
||||
</tbody>
|
||||
`;
|
||||
|
||||
return table.outerHTML;
|
||||
}
|
||||
|
||||
function createPartsTable(parts) {
|
||||
const table = document.createElement('table');
|
||||
table.classList.add('metadata-table');
|
||||
table.innerHTML = `
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${parts
|
||||
.map(
|
||||
part => `
|
||||
<tr>
|
||||
<td class="nowrap"><code>${escapeHtml(part.name)}</code></td>
|
||||
<td>${escapeHtml(part.description)}</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join('')}
|
||||
</tbody>
|
||||
`;
|
||||
|
||||
return table.outerHTML;
|
||||
}
|
||||
|
||||
function createAnimationsTable(animations) {
|
||||
const table = document.createElement('table');
|
||||
table.classList.add('metadata-table');
|
||||
table.innerHTML = `
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${animations
|
||||
.map(
|
||||
animation => `
|
||||
<tr>
|
||||
<td class="nowrap"><code>${escapeHtml(animation.name)}</code></td>
|
||||
<td>${escapeHtml(animation.description)}</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join('')}
|
||||
</tbody>
|
||||
`;
|
||||
|
||||
return table.outerHTML;
|
||||
}
|
||||
|
||||
function createDependenciesList(targetComponent, allComponents) {
|
||||
const ul = document.createElement('ul');
|
||||
const dependencies = [];
|
||||
|
||||
// Recursively fetch sub-dependencies
|
||||
function getDependencies(tag) {
|
||||
const component = allComponents.find(c => c.tagName === tag);
|
||||
if (!component || !Array.isArray(component.dependencies)) {
|
||||
return;
|
||||
}
|
||||
|
||||
component.dependencies?.forEach(dependentTag => {
|
||||
if (!dependencies.includes(dependentTag)) {
|
||||
dependencies.push(dependentTag);
|
||||
}
|
||||
getDependencies(dependentTag);
|
||||
});
|
||||
}
|
||||
|
||||
getDependencies(targetComponent);
|
||||
dependencies.sort().forEach(tag => {
|
||||
const li = document.createElement('li');
|
||||
li.innerHTML = `<code><${tag}></code>`;
|
||||
ul.appendChild(li);
|
||||
});
|
||||
|
||||
return ul.outerHTML;
|
||||
}
|
||||
|
||||
function escapeHtml(html) {
|
||||
if (!html) {
|
||||
return '';
|
||||
}
|
||||
return html
|
||||
.toString()
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" rel="noopener noreferrer" target="_blank">$1</a>')
|
||||
.replace(/`(.*?)`/g, '<code>$1</code>');
|
||||
}
|
||||
|
||||
function getAllComponents(metadata) {
|
||||
const allComponents = [];
|
||||
metadata.modules?.forEach(module => {
|
||||
module.declarations?.forEach(declaration => {
|
||||
if (declaration.customElement) {
|
||||
// Generate the dist path based on the src path and attach it to the component
|
||||
declaration.path = module.path.replace(/^src\//, 'dist/').replace(/\.ts$/, '.js');
|
||||
|
||||
allComponents.push(declaration);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return allComponents;
|
||||
}
|
||||
|
||||
function getComponent(metadata, tagName) {
|
||||
return getAllComponents(metadata).find(component => component.tagName === tagName);
|
||||
}
|
||||
|
||||
if (!window.$docsify) {
|
||||
throw new Error('Docsify must be loaded before installing this plugin.');
|
||||
}
|
||||
|
||||
window.$docsify.plugins.push(hook => {
|
||||
hook.mounted(async () => {
|
||||
const metadata = await customElements;
|
||||
const target = document.querySelector('.app-name');
|
||||
|
||||
// Add version
|
||||
const version = document.createElement('div');
|
||||
version.classList.add('sidebar-version');
|
||||
version.textContent = isDev ? 'Development' : isNext ? 'Next' : metadata.package.version;
|
||||
target.appendChild(version);
|
||||
|
||||
// Store version for reuse
|
||||
sessionStorage.setItem('sl-version', metadata.package.version);
|
||||
|
||||
// Add repo buttons
|
||||
const buttons = document.createElement('div');
|
||||
buttons.classList.add('sidebar-buttons');
|
||||
buttons.innerHTML = `
|
||||
<sl-button size="small" class="repo-button repo-button--sponsor" href="https://github.com/sponsors/claviska" target="_blank">
|
||||
<sl-icon slot="prefix" name="heart"></sl-icon> Sponsor
|
||||
</sl-button>
|
||||
<sl-button size="small" class="repo-button repo-button--github" href="https://github.com/shoelace-style/shoelace/stargazers" target="_blank">
|
||||
<sl-icon slot="prefix" name="github"></sl-icon> Star
|
||||
</sl-button>
|
||||
<sl-button size="small" class="repo-button repo-button--twitter" href="https://twitter.com/shoelace_style" target="_blank">
|
||||
<sl-icon slot="prefix" name="twitter"></sl-icon> Follow
|
||||
</sl-button>
|
||||
`;
|
||||
target.appendChild(buttons);
|
||||
});
|
||||
|
||||
hook.beforeEach(async (content, next) => {
|
||||
const metadata = await customElements;
|
||||
|
||||
// Replace %VERSION% placeholders
|
||||
content = content.replace(/%VERSION%/g, metadata.package.version);
|
||||
|
||||
// Handle [component-header] tags
|
||||
content = content.replace(/\[component-header:([a-z-]+)\]/g, (match, tag) => {
|
||||
const component = getComponent(metadata, tag);
|
||||
let result = '';
|
||||
|
||||
if (!component) {
|
||||
console.error(`Component not found in metadata: ${tag}`);
|
||||
return next(content);
|
||||
}
|
||||
|
||||
let badgeType = 'neutral';
|
||||
if (component.status === 'stable') {
|
||||
badgeType = 'primary';
|
||||
}
|
||||
if (component.status === 'experimental') {
|
||||
badgeType = 'warning';
|
||||
}
|
||||
if (component.status === 'planned') {
|
||||
badgeType = 'neutral';
|
||||
}
|
||||
if (component.status === 'deprecated') {
|
||||
badgeType = 'danger';
|
||||
}
|
||||
|
||||
result += `
|
||||
<div class="component-header">
|
||||
<div class="component-header__tag">
|
||||
<code><${component.tagName}> | ${component.title ?? component.name}</code>
|
||||
</div>
|
||||
|
||||
<div class="component-header__info">
|
||||
<sl-badge variant="neutral" pill>
|
||||
Since ${component.since || '?'}
|
||||
</sl-badge>
|
||||
|
||||
<sl-badge variant="${badgeType}" pill style="text-transform: capitalize;">
|
||||
${component.status}
|
||||
</sl-badge>
|
||||
</div>
|
||||
|
||||
<div class="component-header__summary">
|
||||
${component.summary ? `<p>${marked(component.summary)}</p>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return result.replace(/^ +| +$/gm, '');
|
||||
});
|
||||
|
||||
// Handle [component-metadata] tags
|
||||
content = content.replace(/\[component-metadata:([a-z-]+)\]/g, (match, tag) => {
|
||||
const component = getComponent(metadata, tag);
|
||||
let result = '';
|
||||
|
||||
if (!component) {
|
||||
console.error(`Component not found in metadata: ${tag}`);
|
||||
return next(content);
|
||||
}
|
||||
|
||||
// Remove members that are private or don't have a description
|
||||
const members = component.members?.filter(member => member.description && member.privacy !== 'private');
|
||||
const methods = members?.filter(prop => prop.kind === 'method' && prop.privacy !== 'private');
|
||||
const props = members?.filter(prop => {
|
||||
// Look for a corresponding attribute
|
||||
const attribute = component.attributes?.find(attr => attr.fieldName === prop.name);
|
||||
if (attribute) {
|
||||
prop.attribute = attribute.name || attribute.fieldName;
|
||||
}
|
||||
|
||||
return prop.kind === 'field' && prop.privacy !== 'private';
|
||||
});
|
||||
|
||||
if (component.path) {
|
||||
/* prettier-ignore */
|
||||
result += `
|
||||
## Importing
|
||||
|
||||
<sl-tab-group>
|
||||
<sl-tab slot="nav" panel="script">Script</sl-tab>
|
||||
<sl-tab slot="nav" panel="import">Import</sl-tab>
|
||||
<sl-tab slot="nav" panel="bundler">Bundler</sl-tab>
|
||||
<sl-tab slot="nav" panel="react">React</sl-tab>
|
||||
|
||||
<sl-tab-panel name="script">\n
|
||||
To import this component from [the CDN](https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace) using a script tag:
|
||||
|
||||
\`\`\`html
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${metadata.package.version}/dist/${component.path}"></script>
|
||||
\`\`\`
|
||||
</sl-tab-panel>
|
||||
|
||||
<sl-tab-panel name="import">\n
|
||||
To import this component from [the CDN](https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace) using a JavaScript import:
|
||||
|
||||
\`\`\`js
|
||||
import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${metadata.package.version}/dist/${component.path}';
|
||||
\`\`\`
|
||||
</sl-tab-panel>
|
||||
|
||||
<sl-tab-panel name="bundler">\n
|
||||
To import this component using [a bundler](/getting-started/installation#bundling):
|
||||
\`\`\`js
|
||||
import '@shoelace-style/shoelace/dist/${component.path}';
|
||||
\`\`\`
|
||||
</sl-tab-panel>
|
||||
|
||||
<sl-tab-panel name="react">\n
|
||||
To import this component as a [React component](/frameworks/react):
|
||||
\`\`\`js
|
||||
import { ${component.name} } from '@shoelace-style/shoelace/dist/react';
|
||||
\`\`\`
|
||||
</sl-tab-panel>
|
||||
</sl-tab-group>
|
||||
|
||||
<div class="sponsor-callout">
|
||||
<p>
|
||||
Shoelace is designed, developed, and maintained by <a href="https://twitter.com/claviska" target="_blank">Cory LaViska</a>.
|
||||
Please sponsor my open source work on GitHub. Your support will keep this project alive and growing!
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<sl-button class="repo-button repo-button--sponsor" href="https://github.com/sponsors/claviska" target="_blank">
|
||||
<sl-icon slot="prefix" name="heart"></sl-icon> Sponsor <span class="sponsor-callout__secondary-label">Development</span>
|
||||
</sl-button>
|
||||
|
||||
<sl-button class="repo-button repo-button--github" href="https://github.com/shoelace-style/shoelace/stargazers" target="_blank">
|
||||
<sl-icon slot="prefix" name="github"></sl-icon> Star <span class="sponsor-callout__secondary-label">on GitHub</span>
|
||||
</sl-button>
|
||||
|
||||
<sl-button class="repo-button repo-button--twitter" href="https://twitter.com/shoelace_style" target="_blank">
|
||||
<sl-icon slot="prefix" name="twitter"></sl-icon> Follow <span class="sponsor-callout__secondary-label">on Twitter</span>
|
||||
</sl-button>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (component.slots?.length) {
|
||||
result += `
|
||||
## Slots
|
||||
${createSlotsTable(component.slots)}
|
||||
|
||||
_Learn more about [using slots](/getting-started/usage#slots)._
|
||||
`;
|
||||
}
|
||||
|
||||
if (props?.length) {
|
||||
result += `
|
||||
## Attributes & Properties
|
||||
${createPropsTable(props)}
|
||||
|
||||
_Learn more about [attributes and properties](/getting-started/usage#properties)._
|
||||
`;
|
||||
}
|
||||
|
||||
if (component.events?.length) {
|
||||
result += `
|
||||
## Events
|
||||
${createEventsTable(component.events)}
|
||||
|
||||
_Learn more about [listening to events](/getting-started/usage#events)._
|
||||
`;
|
||||
}
|
||||
|
||||
if (methods?.length) {
|
||||
result += `
|
||||
## Methods
|
||||
|
||||
${createMethodsTable(methods)}
|
||||
|
||||
_Learn more about [calling methods](/getting-started/usage#methods)._
|
||||
`;
|
||||
}
|
||||
|
||||
if (component.cssProperties?.length) {
|
||||
result += `
|
||||
## CSS Custom Properties
|
||||
${createCustomPropertiesTable(component.cssProperties)}
|
||||
|
||||
_Learn more about [customizing CSS Custom Properties](/getting-started/customizing#custom-properties)._
|
||||
`;
|
||||
}
|
||||
|
||||
if (component.cssParts?.length) {
|
||||
result += `
|
||||
## CSS Parts
|
||||
${createPartsTable(component.cssParts)}
|
||||
|
||||
_Learn more about [customizing CSS Parts](/getting-started/customizing#component-parts)._
|
||||
`;
|
||||
}
|
||||
|
||||
if (component.animations?.length) {
|
||||
result += `
|
||||
## Animations
|
||||
${createAnimationsTable(component.animations)}
|
||||
|
||||
_Learn more about [customizing animations](/getting-started/customizing#animations)._
|
||||
`;
|
||||
}
|
||||
|
||||
if (component.dependencies?.length) {
|
||||
result += `
|
||||
## Dependencies
|
||||
|
||||
This component automatically imports the following dependencies.
|
||||
|
||||
${createDependenciesList(component.tagName, getAllComponents(metadata))}
|
||||
`;
|
||||
}
|
||||
|
||||
// Strip whitespace so markdown doesn't process things as code blocks
|
||||
return result.replace(/^ +| +$/gm, '');
|
||||
});
|
||||
|
||||
next(content);
|
||||
});
|
||||
|
||||
// Wrap tables so we can scroll them horizontally when needed
|
||||
hook.doneEach(() => {
|
||||
const content = document.querySelector('.content');
|
||||
const tables = [...content.querySelectorAll('table')];
|
||||
|
||||
tables.forEach(table => {
|
||||
table.outerHTML = `
|
||||
<div class="table-wrapper">
|
||||
${table.outerHTML}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
@@ -1,24 +0,0 @@
|
||||
(() => {
|
||||
if (!window.$docsify) {
|
||||
throw new Error('Docsify must be loaded before installing this plugin.');
|
||||
}
|
||||
|
||||
//
|
||||
// Docsify generates pages dynamically and asynchronously, so when a reload happens, the scroll position can't be
|
||||
// be restored immediately. This plugin waits until Docsify loads the page and then restores it.
|
||||
//
|
||||
window.$docsify.plugins.push(hook => {
|
||||
hook.ready(() => {
|
||||
// Restore
|
||||
const scrollTop = sessionStorage.getItem('bs-scroll');
|
||||
if (scrollTop) {
|
||||
document.documentElement.scrollTop = scrollTop;
|
||||
}
|
||||
|
||||
// Remember
|
||||
document.addEventListener('scroll', () => {
|
||||
sessionStorage.setItem('bs-scroll', document.documentElement.scrollTop);
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
1311
docs/assets/plugins/search/lunr.min.js
vendored
1311
docs/assets/plugins/search/lunr.min.js
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,192 +0,0 @@
|
||||
body.site-search-visible {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar .search-box {
|
||||
margin: 1.25rem 26px;
|
||||
}
|
||||
|
||||
.sidebar .search-box kbd {
|
||||
margin-top: 2px;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
/* Site search */
|
||||
.site-search {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.site-search[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.site-search__overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: var(--sl-overlay-background-color);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.site-search__panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 460px;
|
||||
max-height: calc(100vh - 20rem);
|
||||
background-color: var(--sl-panel-background-color);
|
||||
border-radius: var(--sl-border-radius-large);
|
||||
box-shadow: var(--sl-shadow-x-large);
|
||||
margin: 10rem auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.site-search__panel {
|
||||
max-width: 100%;
|
||||
max-height: calc(92vh - 120px); /* allow iOS browser chrome */
|
||||
margin: 4vh var(--sl-spacing-medium);
|
||||
}
|
||||
}
|
||||
|
||||
.site-search__input::part(base) {
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: var(--sl-border-radius-large);
|
||||
}
|
||||
|
||||
.site-search__input:focus-within::part(base) {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.site-search__input {
|
||||
--sl-input-height-large: 4rem;
|
||||
}
|
||||
|
||||
.site-search__body {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.site-search--has-results .site-search__body {
|
||||
border-top: solid 1px var(--sl-color-neutral-200);
|
||||
}
|
||||
|
||||
.site-search__results {
|
||||
display: none;
|
||||
line-height: var(--sl-line-height-dense);
|
||||
list-style: none;
|
||||
padding: var(--sl-spacing-x-small) 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.site-search--has-results .site-search__results {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.site-search__results a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
padding: var(--sl-spacing-x-small) var(--sl-spacing-large);
|
||||
}
|
||||
|
||||
.site-search__results li a:hover,
|
||||
.site-search__results li a:hover small {
|
||||
background-color: var(--sl-color-neutral-100);
|
||||
}
|
||||
|
||||
.site-search__results li[aria-selected='true'] a,
|
||||
.site-search__results li[aria-selected='true'] a small,
|
||||
.site-search__results li[aria-selected='true'] a sl-icon {
|
||||
outline: none;
|
||||
color: var(--sl-color-neutral-0);
|
||||
background-color: var(--sl-color-primary-600);
|
||||
}
|
||||
|
||||
.sl-theme-dark .site-search__results li[aria-selected='true'] a,
|
||||
.sl-theme-dark .site-search__results li[aria-selected='true'] a small,
|
||||
.sl-theme-dark .site-search__results li[aria-selected='true'] a sl-icon {
|
||||
background-color: var(--sl-color-primary-400);
|
||||
color: var(--sl-color-neutral-1000);
|
||||
}
|
||||
|
||||
.site-search__results h3 {
|
||||
font-weight: var(--sl-font-weight-semibold);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.site-search__results small {
|
||||
display: block;
|
||||
color: var(--sl-color-neutral-600);
|
||||
}
|
||||
|
||||
.site-search__result {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.site-search__result a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sl-spacing-medium);
|
||||
}
|
||||
|
||||
.site-search__result-icon {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
color: var(--sl-color-neutral-400);
|
||||
font-size: var(--sl-font-size-x-large);
|
||||
}
|
||||
|
||||
.site-search__result-description {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.site-search__empty {
|
||||
display: none;
|
||||
border-top: solid 1px var(--sl-color-neutral-200);
|
||||
text-align: center;
|
||||
padding: var(--sl-spacing-x-large);
|
||||
}
|
||||
|
||||
.site-search--no-results .site-search__empty {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.site-search__footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: var(--sl-spacing-large);
|
||||
border-top: solid 1px var(--sl-color-neutral-200);
|
||||
border-bottom-left-radius: inherit;
|
||||
border-bottom-right-radius: inherit;
|
||||
padding: var(--sl-spacing-medium);
|
||||
}
|
||||
|
||||
.site-search__footer small {
|
||||
color: var(--sl-color-neutral-700);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.site-search__footer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Forced colors mode */
|
||||
@media (forced-colors: active) {
|
||||
.site-search__panel {
|
||||
border: solid 1px var(--sl-color-neutral-0);
|
||||
}
|
||||
|
||||
.site-search__results li[aria-selected='true'] a {
|
||||
outline: dashed 1px SelectedItem;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
@@ -1,320 +0,0 @@
|
||||
/* global lunr */
|
||||
(() => {
|
||||
if (!window.$docsify) {
|
||||
throw new Error('Docsify must be loaded before installing this plugin.');
|
||||
}
|
||||
|
||||
window.$docsify.plugins.push(hook => {
|
||||
// Append the search box to the sidebar
|
||||
hook.mounted(() => {
|
||||
const appName = document.querySelector('.sidebar .app-name');
|
||||
const searchBox = document.createElement('div');
|
||||
searchBox.classList.add('search-box');
|
||||
searchBox.innerHTML = `
|
||||
<sl-input type="search" placeholder="Search" pill>
|
||||
<sl-icon slot="prefix" name="search"></sl-icon>
|
||||
<kbd slot="suffix" title="Press / to search">/</kbd>
|
||||
</sl-input>
|
||||
`;
|
||||
const searchBoxInput = searchBox.querySelector('sl-input');
|
||||
|
||||
appName.insertAdjacentElement('afterend', searchBox);
|
||||
|
||||
// Show the search panel when the search is clicked
|
||||
searchBoxInput.addEventListener('mousedown', event => {
|
||||
event.preventDefault();
|
||||
show();
|
||||
});
|
||||
|
||||
// Show the search panel when a key is pressed
|
||||
searchBoxInput.addEventListener('keydown', event => {
|
||||
if (event.key === 'Tab') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass the character that was typed through to the search input
|
||||
if (event.key.length === 1) {
|
||||
event.preventDefault();
|
||||
input.value = event.key;
|
||||
show();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Append the search panel to the body
|
||||
const siteSearch = document.createElement('div');
|
||||
siteSearch.classList.add('site-search');
|
||||
siteSearch.hidden = true;
|
||||
siteSearch.innerHTML = `
|
||||
<div class="site-search__overlay"></div>
|
||||
<div
|
||||
id="site-search-panel"
|
||||
class="site-search__panel"
|
||||
role="combobox"
|
||||
aria-expanded="false"
|
||||
aria-owns="site-search-results"
|
||||
aria-activedescendant=""
|
||||
>
|
||||
<header class="site-search__header">
|
||||
<sl-input
|
||||
class="site-search__input"
|
||||
type="search"
|
||||
placeholder="Search this site"
|
||||
aria-autocomplete="list"
|
||||
aria-controls="site-search-results"
|
||||
size="large"
|
||||
clearable
|
||||
>
|
||||
<sl-icon slot="prefix" name="search"></sl-icon>
|
||||
</sl-input>
|
||||
</header>
|
||||
<div class="site-search__body">
|
||||
<ul
|
||||
id="site-search-results"
|
||||
class="site-search__results"
|
||||
role="listbox"
|
||||
aria-labelledby="site-search-panel"
|
||||
>
|
||||
</ul>
|
||||
<div class="site-search__empty">No results found.</div>
|
||||
</div>
|
||||
<footer class="site-search__footer">
|
||||
<small><kbd>↑</kbd> <kbd>↓</kbd> navigate</small>
|
||||
<small><kbd>↲</kbd> select</small>
|
||||
<small><kbd>esc</kbd> close</small>
|
||||
</footer>
|
||||
</div>
|
||||
`;
|
||||
document.body.append(siteSearch);
|
||||
|
||||
const overlay = siteSearch.querySelector('.site-search__overlay');
|
||||
const panel = siteSearch.querySelector('.site-search__panel');
|
||||
const input = siteSearch.querySelector('.site-search__input');
|
||||
const results = siteSearch.querySelector('.site-search__results');
|
||||
const animationDuration = 150;
|
||||
const searchDebounce = 200;
|
||||
let isShowing = false;
|
||||
let searchTimeout;
|
||||
let searchIndex;
|
||||
let map;
|
||||
|
||||
// Load search data
|
||||
fetch('../../../search.json')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
searchIndex = lunr.Index.load(data.searchIndex);
|
||||
map = data.map;
|
||||
});
|
||||
|
||||
async function show() {
|
||||
isShowing = true;
|
||||
document.body.classList.add('site-search-visible');
|
||||
siteSearch.hidden = false;
|
||||
requestAnimationFrame(() => input.focus());
|
||||
updateResults();
|
||||
|
||||
await Promise.all([
|
||||
panel.animate(
|
||||
[
|
||||
{ opacity: 0, transform: 'scale(.9)' },
|
||||
{ opacity: 1, transform: 'scale(1)' }
|
||||
],
|
||||
{ duration: animationDuration }
|
||||
).finished,
|
||||
overlay.animate([{ opacity: 0 }, { opacity: 1 }], { duration: animationDuration }).finished
|
||||
]);
|
||||
|
||||
document.addEventListener('mousedown', handleDocumentMouseDown);
|
||||
document.addEventListener('keydown', handleDocumentKeyDown);
|
||||
document.addEventListener('focusin', handleDocumentFocusIn);
|
||||
}
|
||||
|
||||
async function hide() {
|
||||
isShowing = false;
|
||||
document.body.classList.remove('site-search-visible');
|
||||
|
||||
await Promise.all([
|
||||
panel.animate(
|
||||
[
|
||||
{ opacity: 1, transform: 'scale(1)' },
|
||||
{ opacity: 0, transform: 'scale(.9)' }
|
||||
],
|
||||
{ duration: animationDuration }
|
||||
).finished,
|
||||
overlay.animate([{ opacity: 1 }, { opacity: 0 }], { duration: animationDuration }).finished
|
||||
]);
|
||||
|
||||
siteSearch.hidden = true;
|
||||
input.value = '';
|
||||
updateResults();
|
||||
|
||||
document.removeEventListener('mousedown', handleDocumentMouseDown);
|
||||
document.removeEventListener('keydown', handleDocumentKeyDown);
|
||||
document.removeEventListener('focusin', handleDocumentFocusIn);
|
||||
}
|
||||
|
||||
function handleInput() {
|
||||
// Debounce search queries
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => updateResults(input.value), searchDebounce);
|
||||
}
|
||||
|
||||
function handleDocumentFocusIn(event) {
|
||||
// Close when focus leaves the panel
|
||||
if (event.target.closest('.site-search__panel') !== panel) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
function handleDocumentMouseDown(event) {
|
||||
// Close when clicking outside of the panel
|
||||
if (event.target.closest('.site-search__overlay') === overlay) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
function handleDocumentKeyDown(event) {
|
||||
// Close when pressing escape
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle keyboard selections
|
||||
if (['ArrowDown', 'ArrowUp', 'Home', 'End', 'Enter'].includes(event.key)) {
|
||||
event.preventDefault();
|
||||
|
||||
const currentEl = results.querySelector('[aria-selected="true"]');
|
||||
const items = [...results.querySelectorAll('li')];
|
||||
const index = items.indexOf(currentEl);
|
||||
let nextEl;
|
||||
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowUp':
|
||||
nextEl = items[Math.max(0, index - 1)];
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
nextEl = items[Math.min(items.length - 1, index + 1)];
|
||||
break;
|
||||
case 'Home':
|
||||
nextEl = items[0];
|
||||
break;
|
||||
case 'End':
|
||||
nextEl = items[items.length - 1];
|
||||
break;
|
||||
case 'Enter':
|
||||
currentEl?.querySelector('a')?.click();
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the selected item
|
||||
items.forEach(item => {
|
||||
if (item === nextEl) {
|
||||
const a = nextEl.querySelector('a');
|
||||
panel.setAttribute('aria-activedescendant', a.id);
|
||||
|
||||
item.setAttribute('aria-selected', 'true');
|
||||
nextEl.scrollIntoView({ block: 'nearest' });
|
||||
} else {
|
||||
item.setAttribute('aria-selected', 'false');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updateResults(query = '') {
|
||||
try {
|
||||
await searchIndex;
|
||||
|
||||
const hasQuery = query.length > 0;
|
||||
const searchTokens = query
|
||||
.split(' ')
|
||||
.map((term, index, arr) => `${term}${index === arr.length - 1 ? `* ${term}~1` : '~1'}`)
|
||||
.join(' ');
|
||||
const matches = hasQuery ? searchIndex.search(`${query} ${searchTokens}`) : [];
|
||||
const hasResults = hasQuery && matches.length > 0;
|
||||
siteSearch.classList.toggle('site-search--has-results', hasQuery && hasResults);
|
||||
siteSearch.classList.toggle('site-search--no-results', hasQuery && !hasResults);
|
||||
panel.setAttribute('aria-expanded', hasQuery && hasResults ? 'true' : 'false');
|
||||
|
||||
results.innerHTML = '';
|
||||
|
||||
matches.forEach((match, index) => {
|
||||
const page = map[match.ref];
|
||||
const li = document.createElement('li');
|
||||
const a = document.createElement('a');
|
||||
let icon = 'file-text';
|
||||
|
||||
a.setAttribute('role', 'option');
|
||||
a.setAttribute('id', `search-result-item-${match.ref}`);
|
||||
|
||||
if (page.url.includes('getting-started/')) {
|
||||
icon = 'lightbulb';
|
||||
}
|
||||
if (page.url.includes('resources/')) {
|
||||
icon = 'book';
|
||||
}
|
||||
if (page.url.includes('components/')) {
|
||||
icon = 'puzzle';
|
||||
}
|
||||
if (page.url.includes('tokens/')) {
|
||||
icon = 'palette2';
|
||||
}
|
||||
if (page.url.includes('utilities/')) {
|
||||
icon = 'wrench';
|
||||
}
|
||||
if (page.url.includes('tutorials/')) {
|
||||
icon = 'joystick';
|
||||
}
|
||||
|
||||
a.href = window.$docsify.routerMode === 'hash' ? `/#/${page.url}` : `/${page.url}`;
|
||||
a.innerHTML = `
|
||||
<div class="site-search__result-icon">
|
||||
<sl-icon name="${icon}" aria-hidden="true"></sl-icon>
|
||||
</div>
|
||||
<div class="site-search__result__details">
|
||||
<h3>${page.title}</h3>
|
||||
<small>${page.url}</small>
|
||||
</div>
|
||||
`;
|
||||
|
||||
li.classList.add('site-search__result');
|
||||
li.setAttribute('aria-selected', index === 0 ? 'true' : 'false');
|
||||
li.appendChild(a);
|
||||
results.appendChild(li);
|
||||
});
|
||||
} catch {
|
||||
// Ignore query errors as the user types
|
||||
}
|
||||
}
|
||||
|
||||
// Show the search panel slash is pressed outside of a form element
|
||||
document.addEventListener('keydown', event => {
|
||||
const isSlash = event.key === '/';
|
||||
const isCtrlK = (event.metaKey || event.ctrlKey) && event.key === 'k';
|
||||
|
||||
if (
|
||||
!isShowing &&
|
||||
(isSlash || isCtrlK) &&
|
||||
!event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))
|
||||
) {
|
||||
event.preventDefault();
|
||||
show();
|
||||
}
|
||||
});
|
||||
|
||||
input.addEventListener('sl-input', handleInput);
|
||||
|
||||
// Close when a result is selected
|
||||
results.addEventListener('click', event => {
|
||||
if (event.target.closest('a')) {
|
||||
hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
@@ -1,29 +0,0 @@
|
||||
.theme-picker {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
.theme-picker:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.theme-picker sl-menu-label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.theme-picker sl-menu-label kbd {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.theme-picker {
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
}
|
||||
|
||||
.theme-picker sl-menu-label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
(() => {
|
||||
if (!window.$docsify) {
|
||||
throw new Error('Docsify must be loaded before installing this plugin.');
|
||||
}
|
||||
|
||||
window.$docsify.plugins.push(hook => {
|
||||
hook.mounted(() => {
|
||||
function getTheme() {
|
||||
return localStorage.getItem('theme') || 'auto';
|
||||
}
|
||||
|
||||
function isDark() {
|
||||
if (theme === 'auto') {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
return theme === 'dark';
|
||||
}
|
||||
|
||||
function setTheme(newTheme) {
|
||||
const noTransitions = Object.assign(document.createElement('style'), {
|
||||
textContent: '* { transition: none !important; }'
|
||||
});
|
||||
|
||||
theme = newTheme;
|
||||
localStorage.setItem('theme', theme);
|
||||
|
||||
// Update the UI
|
||||
[...menu.querySelectorAll('sl-menu-item')].map(item => (item.checked = item.getAttribute('value') === theme));
|
||||
menuIcon.name = isDark() ? 'moon' : 'sun';
|
||||
|
||||
// Toggle the dark mode class without transitions
|
||||
document.body.appendChild(noTransitions);
|
||||
requestAnimationFrame(() => {
|
||||
document.documentElement.classList.toggle('sl-theme-dark', isDark());
|
||||
requestAnimationFrame(() => document.body.removeChild(noTransitions));
|
||||
});
|
||||
}
|
||||
|
||||
let theme = getTheme();
|
||||
|
||||
// Generate the theme picker dropdown
|
||||
const dropdown = document.createElement('sl-dropdown');
|
||||
dropdown.classList.add('theme-picker');
|
||||
dropdown.innerHTML = `
|
||||
<sl-button size="small" pill slot="trigger" caret>
|
||||
<sl-icon name="sun" label="Select Theme"></sl-icon>
|
||||
</sl-button>
|
||||
<sl-menu>
|
||||
<sl-menu-label>Toggle <kbd>\\</kbd></sl-menu-label>
|
||||
<sl-menu-item type="checkbox" value="light">Light</sl-menu-item>
|
||||
<sl-menu-item type="checkbox" value="dark">Dark</sl-menu-item>
|
||||
<sl-divider></sl-divider>
|
||||
<sl-menu-item type="checkbox" value="auto">Auto</sl-menu-item>
|
||||
</sl-menu>
|
||||
`;
|
||||
document.querySelector('.sidebar-toggle').insertAdjacentElement('afterend', dropdown);
|
||||
|
||||
// Listen for selections
|
||||
const menu = dropdown.querySelector('sl-menu');
|
||||
const menuIcon = dropdown.querySelector('sl-icon');
|
||||
menu.addEventListener('sl-select', event => setTheme(event.detail.item.value));
|
||||
|
||||
// Update the theme when the preference changes
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => setTheme(theme));
|
||||
|
||||
// Toggle themes when pressing backslash
|
||||
document.addEventListener('keydown', event => {
|
||||
if (
|
||||
event.key === '\\' &&
|
||||
!event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))
|
||||
) {
|
||||
event.preventDefault();
|
||||
|
||||
setTheme(isDark() ? 'light' : 'dark');
|
||||
}
|
||||
});
|
||||
|
||||
// Set the initial theme and sync the UI
|
||||
setTheme(theme);
|
||||
});
|
||||
});
|
||||
})();
|
||||
249
docs/assets/scripts/code-previews.js
Normal file
249
docs/assets/scripts/code-previews.js
Normal file
@@ -0,0 +1,249 @@
|
||||
(() => {
|
||||
function convertModuleLinks(html) {
|
||||
html = html
|
||||
.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;
|
||||
}
|
||||
|
||||
function getAdjacentExample(name, pre) {
|
||||
let currentPre = pre.nextElementSibling;
|
||||
|
||||
while (currentPre?.tagName.toLowerCase() === 'pre') {
|
||||
if (currentPre?.getAttribute('data-lang').split(' ').includes(name)) {
|
||||
return currentPre;
|
||||
}
|
||||
|
||||
currentPre = currentPre.nextElementSibling;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function runScript(script) {
|
||||
const newScript = document.createElement('script');
|
||||
|
||||
if (script.type === 'module') {
|
||||
newScript.type = 'module';
|
||||
newScript.textContent = script.innerHTML;
|
||||
} else {
|
||||
newScript.appendChild(document.createTextNode(`(() => { ${script.innerHTML} })();`));
|
||||
}
|
||||
|
||||
script.parentNode.replaceChild(newScript, script);
|
||||
}
|
||||
|
||||
function getFlavor() {
|
||||
return sessionStorage.getItem('flavor') || 'html';
|
||||
}
|
||||
|
||||
function setFlavor(newFlavor) {
|
||||
flavor = ['html', 'react'].includes(newFlavor) ? newFlavor : 'html';
|
||||
sessionStorage.setItem('flavor', flavor);
|
||||
|
||||
// Set the flavor class on the body
|
||||
document.documentElement.classList.toggle('flavor-html', flavor === 'html');
|
||||
document.documentElement.classList.toggle('flavor-react', flavor === 'react');
|
||||
}
|
||||
|
||||
function syncFlavor() {
|
||||
setFlavor(getFlavor());
|
||||
|
||||
document.querySelectorAll('.code-preview__button--html').forEach(preview => {
|
||||
if (flavor === 'html') {
|
||||
preview.classList.add('code-preview__button--selected');
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('.code-preview__button--react').forEach(preview => {
|
||||
if (flavor === 'react') {
|
||||
preview.classList.add('code-preview__button--selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const shoelaceVersion = document.documentElement.getAttribute('data-shoelace-version');
|
||||
const reactVersion = '18.2.0';
|
||||
const cdndir = 'cdn';
|
||||
const npmdir = 'dist';
|
||||
let flavor = getFlavor();
|
||||
let count = 1;
|
||||
|
||||
// We need the version to open
|
||||
if (!shoelaceVersion) {
|
||||
throw new Error('The data-shoelace-version attribute is missing from <html>.');
|
||||
}
|
||||
|
||||
// Sync flavor UI on page load
|
||||
syncFlavor();
|
||||
|
||||
//
|
||||
// Resizing previews
|
||||
//
|
||||
document.addEventListener('mousedown', handleResizerDrag);
|
||||
document.addEventListener('touchstart', handleResizerDrag, { passive: true });
|
||||
|
||||
function handleResizerDrag(event) {
|
||||
const resizer = event.target.closest('.code-preview__resizer');
|
||||
const preview = event.target.closest('.code-preview__preview');
|
||||
|
||||
if (!resizer || !preview) return;
|
||||
|
||||
let startX = event.changedTouches ? event.changedTouches[0].pageX : event.clientX;
|
||||
let startWidth = parseInt(document.defaultView.getComputedStyle(preview).width, 10);
|
||||
|
||||
event.preventDefault();
|
||||
preview.classList.add('code-preview__preview--dragging');
|
||||
document.documentElement.addEventListener('mousemove', dragMove);
|
||||
document.documentElement.addEventListener('touchmove', dragMove);
|
||||
document.documentElement.addEventListener('mouseup', dragStop);
|
||||
document.documentElement.addEventListener('touchend', dragStop);
|
||||
|
||||
function dragMove(event) {
|
||||
const width = startWidth + (event.changedTouches ? event.changedTouches[0].pageX : event.pageX) - startX;
|
||||
preview.style.width = `${width}px`;
|
||||
}
|
||||
|
||||
function dragStop() {
|
||||
preview.classList.remove('code-preview__preview--dragging');
|
||||
document.documentElement.removeEventListener('mousemove', dragMove);
|
||||
document.documentElement.removeEventListener('touchmove', dragMove);
|
||||
document.documentElement.removeEventListener('mouseup', dragStop);
|
||||
document.documentElement.removeEventListener('touchend', dragStop);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Toggle source mode
|
||||
//
|
||||
document.addEventListener('click', event => {
|
||||
const button = event.target.closest('.code-preview__button');
|
||||
const codeBlock = button?.closest('.code-preview');
|
||||
|
||||
if (button?.classList.contains('code-preview__button--html')) {
|
||||
// Show HTML
|
||||
setFlavor('html');
|
||||
toggleSource(codeBlock, true);
|
||||
} else if (button?.classList.contains('code-preview__button--react')) {
|
||||
// Show React
|
||||
setFlavor('react');
|
||||
toggleSource(codeBlock, true);
|
||||
} else if (button?.classList.contains('code-preview__toggle')) {
|
||||
// Toggle source
|
||||
toggleSource(codeBlock);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update flavor buttons
|
||||
[...document.querySelectorAll('.code-preview')].forEach(cb => {
|
||||
cb.querySelector('.code-preview__button--html')?.classList.toggle(
|
||||
'code-preview__button--selected',
|
||||
flavor === 'html'
|
||||
);
|
||||
cb.querySelector('.code-preview__button--react')?.classList.toggle(
|
||||
'code-preview__button--selected',
|
||||
flavor === 'react'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function toggleSource(codeBlock, force) {
|
||||
codeBlock.classList.toggle('code-preview--expanded', force);
|
||||
event.target.setAttribute('aria-expanded', codeBlock.classList.contains('code-preview--expanded'));
|
||||
}
|
||||
|
||||
//
|
||||
// Open in CodePen
|
||||
//
|
||||
document.addEventListener('click', event => {
|
||||
const button = event.target.closest('button');
|
||||
|
||||
if (button?.classList.contains('code-preview__button--codepen')) {
|
||||
const codeBlock = button.closest('.code-preview');
|
||||
const htmlExample = codeBlock.querySelector('.code-preview__source--html > pre > code')?.textContent;
|
||||
const reactExample = codeBlock.querySelector('.code-preview__source--react > pre > code')?.textContent;
|
||||
const isReact = flavor === 'react' && typeof reactExample === 'string';
|
||||
const theme = document.documentElement.classList.contains('sl-theme-dark') ? 'dark' : 'light';
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const isDark = theme === 'dark' || (theme === 'auto' && prefersDark);
|
||||
const editors = isReact ? '0010' : '1000';
|
||||
let htmlTemplate = '';
|
||||
let jsTemplate = '';
|
||||
let cssTemplate = '';
|
||||
|
||||
const form = document.createElement('form');
|
||||
form.action = 'https://codepen.io/pen/define';
|
||||
form.method = 'POST';
|
||||
form.target = '_blank';
|
||||
|
||||
// HTML templates
|
||||
if (!isReact) {
|
||||
htmlTemplate =
|
||||
`<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/shoelace.js"></script>\n` +
|
||||
`\n${htmlExample}`;
|
||||
jsTemplate = '';
|
||||
}
|
||||
|
||||
// React templates
|
||||
if (isReact) {
|
||||
htmlTemplate = '<div id="root"></div>';
|
||||
jsTemplate =
|
||||
`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://cdn.skypack.dev/@shoelace-style/shoelace@${shoelaceVersion}/${npmdir}/')\n` +
|
||||
`\n${convertModuleLinks(reactExample)}\n` +
|
||||
`\n` +
|
||||
`ReactDOM.render(<App />, document.getElementById('root'));`;
|
||||
}
|
||||
|
||||
// CSS templates
|
||||
cssTemplate =
|
||||
`@import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/themes/${
|
||||
isDark ? 'dark' : 'light'
|
||||
}.css';\n` +
|
||||
'\n' +
|
||||
'body {\n' +
|
||||
' font: 16px sans-serif;\n' +
|
||||
' background-color: var(--sl-color-neutral-0);\n' +
|
||||
' color: var(--sl-color-neutral-900);\n' +
|
||||
' padding: 1rem;\n' +
|
||||
'}';
|
||||
|
||||
// Docs: https://blog.codepen.io/documentation/prefill/
|
||||
const data = {
|
||||
title: '',
|
||||
description: '',
|
||||
tags: ['shoelace', 'web components'],
|
||||
editors,
|
||||
head: `<meta name="viewport" content="width=device-width">`,
|
||||
html_classes: `sl-theme-${isDark ? 'dark' : 'light'}`,
|
||||
css_external: ``,
|
||||
js_external: ``,
|
||||
js_module: true,
|
||||
js_pre_processor: isReact ? 'babel' : 'none',
|
||||
html: htmlTemplate,
|
||||
css: cssTemplate,
|
||||
js: jsTemplate
|
||||
};
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'data';
|
||||
input.value = JSON.stringify(data);
|
||||
form.append(input);
|
||||
|
||||
document.documentElement.append(form);
|
||||
form.submit();
|
||||
form.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Set the initial flavor
|
||||
window.addEventListener('turbo:load', syncFlavor);
|
||||
})();
|
||||
298
docs/assets/scripts/docs.js
Normal file
298
docs/assets/scripts/docs.js
Normal file
@@ -0,0 +1,298 @@
|
||||
//
|
||||
// Sidebar
|
||||
//
|
||||
// When the sidebar is hidden, we apply the inert attribute to prevent focus from reaching it. Due to the many states
|
||||
// the sidebar can have (e.g. static, hidden, expanded), we test for visibility by checking to see if it's placed
|
||||
// offscreen or not. Then, on resize/transition we make sure to update the attribute accordingly.
|
||||
//
|
||||
(() => {
|
||||
function getSidebar() {
|
||||
return document.getElementById('sidebar');
|
||||
}
|
||||
|
||||
function isSidebarOpen() {
|
||||
return document.documentElement.classList.contains('sidebar-open');
|
||||
}
|
||||
|
||||
function isSidebarVisible() {
|
||||
return getSidebar().getBoundingClientRect().x >= 0;
|
||||
}
|
||||
|
||||
function toggleSidebar(force) {
|
||||
const isOpen = typeof force === 'boolean' ? force : !isSidebarOpen();
|
||||
return document.documentElement.classList.toggle('sidebar-open', isOpen);
|
||||
}
|
||||
|
||||
function updateInert() {
|
||||
getSidebar().inert = !isSidebarVisible();
|
||||
}
|
||||
|
||||
// Toggle the menu
|
||||
document.addEventListener('click', event => {
|
||||
const menuToggle = event.target.closest('#menu-toggle');
|
||||
if (!menuToggle) return;
|
||||
toggleSidebar();
|
||||
});
|
||||
|
||||
// Update the sidebar's inert state when the window resizes and when the sidebar transitions
|
||||
window.addEventListener('resize', () => toggleSidebar(false));
|
||||
|
||||
document.addEventListener('transitionend', event => {
|
||||
const sidebar = event.target.closest('#sidebar');
|
||||
if (!sidebar) return;
|
||||
updateInert();
|
||||
});
|
||||
|
||||
// Close when a menu item is selected on mobile
|
||||
document.addEventListener('click', event => {
|
||||
const sidebar = event.target.closest('#sidebar');
|
||||
const link = event.target.closest('a');
|
||||
if (!sidebar || !link) return;
|
||||
|
||||
if (isSidebarOpen()) {
|
||||
toggleSidebar();
|
||||
}
|
||||
});
|
||||
|
||||
// Close when open and escape is pressed
|
||||
document.addEventListener('keydown', event => {
|
||||
if (event.key === 'Escape' && isSidebarOpen()) {
|
||||
event.stopImmediatePropagation();
|
||||
toggleSidebar();
|
||||
}
|
||||
});
|
||||
|
||||
// Close when clicking outside of the sidebar
|
||||
document.addEventListener('mousedown', event => {
|
||||
if (isSidebarOpen() & !event.target?.closest('#sidebar, #menu-toggle')) {
|
||||
event.stopImmediatePropagation();
|
||||
toggleSidebar();
|
||||
}
|
||||
});
|
||||
|
||||
updateInert();
|
||||
})();
|
||||
|
||||
//
|
||||
// Theme selector
|
||||
//
|
||||
(() => {
|
||||
function getTheme() {
|
||||
return localStorage.getItem('theme') || 'auto';
|
||||
}
|
||||
|
||||
function isDark() {
|
||||
if (theme === 'auto') {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
return theme === 'dark';
|
||||
}
|
||||
|
||||
function setTheme(newTheme) {
|
||||
theme = newTheme;
|
||||
localStorage.setItem('theme', theme);
|
||||
|
||||
// Update the UI
|
||||
updateSelection();
|
||||
|
||||
// Toggle the dark mode class
|
||||
document.documentElement.classList.toggle('sl-theme-dark', isDark());
|
||||
}
|
||||
|
||||
function updateSelection() {
|
||||
const menu = document.querySelector('#theme-selector sl-menu');
|
||||
if (!menu) return;
|
||||
[...menu.querySelectorAll('sl-menu-item')].map(item => (item.checked = item.getAttribute('value') === theme));
|
||||
}
|
||||
|
||||
let theme = getTheme();
|
||||
|
||||
// Selection is not preserved when changing page, so update when opening dropdown
|
||||
document.addEventListener('sl-show', event => {
|
||||
const themeSelector = event.target.closest('#theme-selector');
|
||||
if (!themeSelector) return;
|
||||
updateSelection();
|
||||
});
|
||||
|
||||
// Listen for selections
|
||||
document.addEventListener('sl-select', event => {
|
||||
const menu = event.target.closest('#theme-selector sl-menu');
|
||||
if (!menu) return;
|
||||
setTheme(event.detail.item.value);
|
||||
});
|
||||
|
||||
// Update the theme when the preference changes
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => setTheme(theme));
|
||||
|
||||
// Toggle with backslash
|
||||
document.addEventListener('keydown', event => {
|
||||
if (
|
||||
event.key === '\\' &&
|
||||
!event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))
|
||||
) {
|
||||
event.preventDefault();
|
||||
setTheme(isDark() ? 'light' : 'dark');
|
||||
}
|
||||
});
|
||||
|
||||
// Set the initial theme and sync the UI
|
||||
setTheme(theme);
|
||||
})();
|
||||
|
||||
//
|
||||
// Open details when printing
|
||||
//
|
||||
(() => {
|
||||
const detailsOpenOnPrint = new Set();
|
||||
|
||||
window.addEventListener('beforeprint', () => {
|
||||
detailsOpenOnPrint.clear();
|
||||
document.querySelectorAll('details').forEach(details => {
|
||||
if (details.open) {
|
||||
detailsOpenOnPrint.add(details);
|
||||
}
|
||||
details.open = true;
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('afterprint', () => {
|
||||
document.querySelectorAll('details').forEach(details => {
|
||||
details.open = detailsOpenOnPrint.has(details);
|
||||
});
|
||||
detailsOpenOnPrint.clear();
|
||||
});
|
||||
})();
|
||||
|
||||
//
|
||||
// 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
|
||||
//
|
||||
(() => {
|
||||
document.addEventListener('click', event => {
|
||||
const link = event.target.closest('a');
|
||||
const id = (link?.hash ?? '').substr(1);
|
||||
const isFragment = link?.hasAttribute('href') && link?.getAttribute('href').startsWith('#');
|
||||
|
||||
if (!link || !isFragment || link.getAttribute('data-smooth-link') === 'false') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Scroll to the top
|
||||
if (link.hash === '') {
|
||||
event.preventDefault();
|
||||
window.scroll({ top: 0, behavior: 'smooth' });
|
||||
history.pushState(undefined, undefined, location.pathname);
|
||||
}
|
||||
|
||||
// Scroll to an id
|
||||
if (id) {
|
||||
const target = document.getElementById(id);
|
||||
|
||||
if (target) {
|
||||
event.preventDefault();
|
||||
window.scroll({ top: target.offsetTop, behavior: 'smooth' });
|
||||
history.pushState(undefined, undefined, `#${id}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
//
|
||||
// Table of Contents scrollspy
|
||||
//
|
||||
(() => {
|
||||
// This will be stale if its not a function.
|
||||
const getLinks = () => [...document.querySelectorAll('.content__toc a')];
|
||||
const linkTargets = new WeakMap();
|
||||
const visibleTargets = new WeakSet();
|
||||
const observer = new IntersectionObserver(handleIntersect, { rootMargin: '0px 0px' });
|
||||
let debounce;
|
||||
|
||||
function handleIntersect(entries) {
|
||||
entries.forEach(entry => {
|
||||
// Remember which targets are visible
|
||||
if (entry.isIntersecting) {
|
||||
visibleTargets.add(entry.target);
|
||||
} else {
|
||||
visibleTargets.delete(entry.target);
|
||||
}
|
||||
});
|
||||
|
||||
updateActiveLinks();
|
||||
}
|
||||
|
||||
function updateActiveLinks() {
|
||||
const links = getLinks();
|
||||
// Find the first visible target and activate the respective link
|
||||
links.find(link => {
|
||||
const target = linkTargets.get(link);
|
||||
|
||||
if (target && visibleTargets.has(target)) {
|
||||
links.forEach(el => el.classList.toggle('active', el === link));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// Observe link targets
|
||||
function observeLinks() {
|
||||
getLinks().forEach(link => {
|
||||
const hash = link.hash.slice(1);
|
||||
const target = hash ? document.querySelector(`.content__body #${hash}`) : null;
|
||||
|
||||
if (target) {
|
||||
linkTargets.set(link, target);
|
||||
observer.observe(target);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
observeLinks();
|
||||
|
||||
document.addEventListener('turbo:load', updateActiveLinks);
|
||||
document.addEventListener('turbo:load', observeLinks);
|
||||
})();
|
||||
|
||||
//
|
||||
// Show custom versions in the sidebar
|
||||
//
|
||||
(() => {
|
||||
function updateVersion() {
|
||||
const el = document.querySelector('.sidebar-version');
|
||||
if (!el) return;
|
||||
|
||||
if (location.hostname === 'next.shoelace.style') el.textContent = 'Next';
|
||||
if (location.hostname === 'localhost') el.textContent = 'Development';
|
||||
}
|
||||
|
||||
updateVersion();
|
||||
|
||||
document.addEventListener('turbo:load', updateVersion);
|
||||
})();
|
||||
369
docs/assets/scripts/search.js
Normal file
369
docs/assets/scripts/search.js
Normal file
@@ -0,0 +1,369 @@
|
||||
(() => {
|
||||
// Append the search dialog to the body
|
||||
const siteSearch = document.createElement('div');
|
||||
const scrollbarWidth = Math.abs(window.innerWidth - document.documentElement.clientWidth);
|
||||
|
||||
siteSearch.classList.add('search');
|
||||
siteSearch.innerHTML = `
|
||||
<div class="search__overlay"></div>
|
||||
<dialog id="search-dialog" class="search__dialog">
|
||||
<div class="search__content">
|
||||
<div class="search__header">
|
||||
<div id="search-combobox" class="search__input-wrapper">
|
||||
<sl-icon name="search"></sl-icon>
|
||||
<input
|
||||
id="search-input"
|
||||
class="search__input"
|
||||
type="search"
|
||||
placeholder="Search"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
enterkeyhint="go"
|
||||
spellcheck="false"
|
||||
maxlength="100"
|
||||
role="combobox"
|
||||
aria-autocomplete="list"
|
||||
aria-expanded="true"
|
||||
aria-controls="search-listbox"
|
||||
aria-haspopup="listbox"
|
||||
aria-activedescendant
|
||||
>
|
||||
<button type="button" class="search__clear-button" aria-label="Clear entry" tabindex="-1" hidden>
|
||||
<sl-icon name="x-circle-fill"></sl-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search__body">
|
||||
<ul
|
||||
id="search-listbox"
|
||||
class="search__results"
|
||||
role="listbox"
|
||||
aria-label="Search results"
|
||||
></ul>
|
||||
<div class="search__empty">No matching pages</div>
|
||||
</div>
|
||||
<footer class="search__footer">
|
||||
<small><kbd>↑</kbd> <kbd>↓</kbd> Navigate</small>
|
||||
<small><kbd>↲</kbd> Select</small>
|
||||
<small><kbd>Esc</kbd> Close</small>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
`;
|
||||
|
||||
const overlay = siteSearch.querySelector('.search__overlay');
|
||||
const dialog = siteSearch.querySelector('.search__dialog');
|
||||
const input = siteSearch.querySelector('.search__input');
|
||||
const clearButton = siteSearch.querySelector('.search__clear-button');
|
||||
const results = siteSearch.querySelector('.search__results');
|
||||
const version = document.documentElement.getAttribute('data-shoelace-version');
|
||||
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;
|
||||
|
||||
// Cleanup older search indices (everything before this version)
|
||||
try {
|
||||
const items = { ...localStorage };
|
||||
|
||||
Object.keys(items).forEach(k => {
|
||||
if (key > k) {
|
||||
localStorage.removeItem(k);
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
// Look for a cached index
|
||||
try {
|
||||
if (cache) {
|
||||
const data = JSON.parse(cache);
|
||||
|
||||
searchIndex = window.lunr.Index.load(data.searchIndex);
|
||||
map = data.map;
|
||||
|
||||
return resolve();
|
||||
}
|
||||
} catch {
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
// Wait until idle to fetch the index
|
||||
wait(() => {
|
||||
fetch('/assets/search.json')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (!window.lunr) {
|
||||
console.error('The Lunr search client has not yet been loaded.');
|
||||
}
|
||||
|
||||
searchIndex = window.lunr.Index.load(data.searchIndex);
|
||||
map = data.map;
|
||||
|
||||
// Cache the search index for this version
|
||||
if (version) {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(data));
|
||||
} catch (err) {
|
||||
console.warn(`Unable to cache the search index: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function show() {
|
||||
isShowing = true;
|
||||
document.body.append(siteSearch);
|
||||
document.body.classList.add('search-visible');
|
||||
document.body.style.setProperty('--docs-search-scroll-lock-size', `${scrollbarWidth}px`);
|
||||
clearButton.hidden = true;
|
||||
requestAnimationFrame(() => input.focus());
|
||||
updateResults();
|
||||
|
||||
dialog.showModal();
|
||||
|
||||
await Promise.all([
|
||||
dialog.animate(
|
||||
[
|
||||
{ opacity: 0, transform: 'scale(.9)', transformOrigin: 'top' },
|
||||
{ opacity: 1, transform: 'scale(1)', transformOrigin: 'top' }
|
||||
],
|
||||
{ duration: animationDuration }
|
||||
).finished,
|
||||
overlay.animate([{ opacity: 0 }, { opacity: 1 }], { duration: animationDuration }).finished
|
||||
]);
|
||||
|
||||
dialog.addEventListener('mousedown', handleMouseDown);
|
||||
dialog.addEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
|
||||
async function hide() {
|
||||
isShowing = false;
|
||||
|
||||
await Promise.all([
|
||||
dialog.animate(
|
||||
[
|
||||
{ opacity: 1, transform: 'scale(1)', transformOrigin: 'top' },
|
||||
{ opacity: 0, transform: 'scale(.9)', transformOrigin: 'top' }
|
||||
],
|
||||
{ duration: animationDuration }
|
||||
).finished,
|
||||
overlay.animate([{ opacity: 1 }, { opacity: 0 }], { duration: animationDuration }).finished
|
||||
]);
|
||||
|
||||
dialog.close();
|
||||
|
||||
input.blur(); // otherwise Safari will scroll to the bottom of the page on close
|
||||
input.value = '';
|
||||
document.body.classList.remove('search-visible');
|
||||
document.body.style.removeProperty('--docs-search-scroll-lock-size');
|
||||
siteSearch.remove();
|
||||
updateResults();
|
||||
|
||||
dialog.removeEventListener('mousedown', handleMouseDown);
|
||||
dialog.removeEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
|
||||
function handleInput() {
|
||||
clearButton.hidden = input.value === '';
|
||||
|
||||
// Debounce search queries
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => updateResults(input.value), searchDebounce);
|
||||
}
|
||||
|
||||
function handleClear() {
|
||||
clearButton.hidden = true;
|
||||
input.value = '';
|
||||
input.focus();
|
||||
updateResults();
|
||||
}
|
||||
|
||||
function handleMouseDown(event) {
|
||||
if (!event.target.closest('.search__content')) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(event) {
|
||||
// Close when pressing escape
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault(); // prevent <dialog> from closing immediately so it can animate
|
||||
event.stopImmediatePropagation();
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle keyboard selections
|
||||
if (['ArrowDown', 'ArrowUp', 'Home', 'End', 'Enter'].includes(event.key)) {
|
||||
event.preventDefault();
|
||||
|
||||
const currentEl = results.querySelector('[data-selected="true"]');
|
||||
const items = [...results.querySelectorAll('li')];
|
||||
const index = items.indexOf(currentEl);
|
||||
let nextEl;
|
||||
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowUp':
|
||||
nextEl = items[Math.max(0, index - 1)];
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
nextEl = items[Math.min(items.length - 1, index + 1)];
|
||||
break;
|
||||
case 'Home':
|
||||
nextEl = items[0];
|
||||
break;
|
||||
case 'End':
|
||||
nextEl = items[items.length - 1];
|
||||
break;
|
||||
case 'Enter':
|
||||
currentEl?.querySelector('a')?.click();
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the selected item
|
||||
items.forEach(item => {
|
||||
if (item === nextEl) {
|
||||
input.setAttribute('aria-activedescendant', item.id);
|
||||
item.setAttribute('data-selected', 'true');
|
||||
nextEl.scrollIntoView({ block: 'nearest' });
|
||||
} else {
|
||||
item.setAttribute('data-selected', 'false');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updateResults(query = '') {
|
||||
try {
|
||||
await loadSearchIndex;
|
||||
|
||||
const hasQuery = query.length > 0;
|
||||
const searchTerms = query
|
||||
.split(' ')
|
||||
.map((term, index, arr) => {
|
||||
// Search API: https://lunrjs.com/guides/searching.html
|
||||
if (index === arr.length - 1) {
|
||||
// The last term is not mandatory and 1x fuzzy. We also duplicate it with a wildcard to match partial words
|
||||
// as the user types.
|
||||
return `${term}~1 ${term}*`;
|
||||
} else {
|
||||
// All other terms are mandatory and 1x fuzzy
|
||||
return `+${term}~1`;
|
||||
}
|
||||
})
|
||||
.join(' ');
|
||||
const matches = hasQuery ? searchIndex.search(searchTerms) : [];
|
||||
const hasResults = hasQuery && matches.length > 0;
|
||||
|
||||
siteSearch.classList.toggle('search--has-results', hasQuery && hasResults);
|
||||
siteSearch.classList.toggle('search--no-results', hasQuery && !hasResults);
|
||||
|
||||
input.setAttribute('aria-activedescendant', '');
|
||||
results.innerHTML = '';
|
||||
|
||||
matches.forEach((match, index) => {
|
||||
const page = map[match.ref];
|
||||
const li = document.createElement('li');
|
||||
const a = document.createElement('a');
|
||||
const displayTitle = page.title ?? '';
|
||||
const displayDescription = page.description ?? '';
|
||||
const displayUrl = page.url.replace(/^\//, '');
|
||||
let icon = 'file-text';
|
||||
|
||||
a.setAttribute('role', 'option');
|
||||
a.setAttribute('id', `search-result-item-${match.ref}`);
|
||||
|
||||
if (page.url.includes('getting-started/')) {
|
||||
icon = 'lightbulb';
|
||||
}
|
||||
if (page.url.includes('resources/')) {
|
||||
icon = 'book';
|
||||
}
|
||||
if (page.url.includes('components/')) {
|
||||
icon = 'puzzle';
|
||||
}
|
||||
if (page.url.includes('tokens/')) {
|
||||
icon = 'palette2';
|
||||
}
|
||||
if (page.url.includes('utilities/')) {
|
||||
icon = 'wrench';
|
||||
}
|
||||
if (page.url.includes('tutorials/')) {
|
||||
icon = 'joystick';
|
||||
}
|
||||
|
||||
li.classList.add('search__result');
|
||||
li.setAttribute('role', 'option');
|
||||
li.setAttribute('id', `search-result-item-${match.ref}`);
|
||||
li.setAttribute('data-selected', index === 0 ? 'true' : 'false');
|
||||
|
||||
a.href = page.url;
|
||||
a.innerHTML = `
|
||||
<div class="search__result-icon" aria-hidden="true">
|
||||
<sl-icon name="${icon}"></sl-icon>
|
||||
</div>
|
||||
<div class="search__result__details">
|
||||
<div class="search__result-title"></div>
|
||||
<div class="search__result-description"></div>
|
||||
<div class="search__result-url"></div>
|
||||
</div>
|
||||
`;
|
||||
a.querySelector('.search__result-title').textContent = displayTitle;
|
||||
a.querySelector('.search__result-description').textContent = displayDescription;
|
||||
a.querySelector('.search__result-url').textContent = displayUrl;
|
||||
|
||||
li.appendChild(a);
|
||||
results.appendChild(li);
|
||||
});
|
||||
} catch {
|
||||
// Ignore query errors as the user types
|
||||
}
|
||||
}
|
||||
|
||||
// Show the search dialog when clicking on data-plugin="search"
|
||||
document.addEventListener('click', event => {
|
||||
const searchButton = event.target.closest('[data-plugin="search"]');
|
||||
if (searchButton) {
|
||||
show();
|
||||
}
|
||||
});
|
||||
|
||||
// Show the search dialog when slash (or CMD+K) is pressed and focus is not inside a form element
|
||||
document.addEventListener('keydown', event => {
|
||||
if (
|
||||
!isShowing &&
|
||||
(event.key === '/' || (event.key === 'k' && (event.metaKey || event.ctrlKey))) &&
|
||||
!event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))
|
||||
) {
|
||||
event.preventDefault();
|
||||
show();
|
||||
}
|
||||
});
|
||||
|
||||
input.addEventListener('input', handleInput);
|
||||
clearButton.addEventListener('click', handleClear);
|
||||
|
||||
// Close when a result is selected
|
||||
results.addEventListener('click', event => {
|
||||
if (event.target.closest('a')) {
|
||||
hide();
|
||||
}
|
||||
});
|
||||
})();
|
||||
29
docs/assets/scripts/turbo.js
Normal file
29
docs/assets/scripts/turbo.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as Turbo from 'https://cdn.jsdelivr.net/npm/@hotwired/turbo@7.3.0/+esm';
|
||||
|
||||
(() => {
|
||||
if (!window.scrollPositions) {
|
||||
window.scrollPositions = {};
|
||||
}
|
||||
|
||||
function preserveScroll() {
|
||||
document.querySelectorAll('[data-preserve-scroll').forEach(element => {
|
||||
scrollPositions[element.id] = element.scrollTop;
|
||||
});
|
||||
}
|
||||
|
||||
function restoreScroll(event) {
|
||||
document.querySelectorAll('[data-preserve-scroll').forEach(element => {
|
||||
element.scrollTop = scrollPositions[element.id];
|
||||
});
|
||||
|
||||
if (event.detail && event.detail.newBody) {
|
||||
event.detail.newBody.querySelectorAll('[data-preserve-scroll').forEach(element => {
|
||||
element.scrollTop = scrollPositions[element.id];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('turbo:before-cache', preserveScroll);
|
||||
window.addEventListener('turbo:before-render', restoreScroll);
|
||||
window.addEventListener('turbo:render', restoreScroll);
|
||||
})();
|
||||
@@ -1,11 +1,12 @@
|
||||
.code-block {
|
||||
/* Interactive code blocks */
|
||||
.code-preview {
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
background-color: var(--sl-color-neutral-50);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.code-block__preview {
|
||||
.code-preview__preview {
|
||||
position: relative;
|
||||
border: solid 1px var(--sl-color-neutral-200);
|
||||
border-bottom: none;
|
||||
@@ -18,7 +19,7 @@
|
||||
}
|
||||
|
||||
/* Block the preview while dragging to prevent iframes from intercepting drag events */
|
||||
.code-block__preview--dragging:after {
|
||||
.code-preview__preview--dragging:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -29,7 +30,7 @@
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.code-block__resizer {
|
||||
.code-preview__resizer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -44,35 +45,34 @@
|
||||
border-left: solid 1px var(--sl-color-neutral-200);
|
||||
border-top-right-radius: 3px;
|
||||
cursor: ew-resize;
|
||||
transition: 250ms background-color;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.code-block__preview {
|
||||
.code-preview__preview {
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.code-block__resizer {
|
||||
.code-preview__resizer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.code-block__source {
|
||||
.code-preview__source {
|
||||
border: solid 1px var(--sl-color-neutral-200);
|
||||
border-bottom: none;
|
||||
border-radius: 0 !important;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.code-block--expanded .code-block__source {
|
||||
.code-preview--expanded .code-preview__source {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.code-block__source pre {
|
||||
.code-preview__source pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.code-block__buttons {
|
||||
.code-preview__buttons {
|
||||
position: relative;
|
||||
border: solid 1px var(--sl-color-neutral-200);
|
||||
border-bottom-left-radius: 3px;
|
||||
@@ -80,7 +80,7 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.code-block__button {
|
||||
.code-preview__button {
|
||||
flex: 0 0 auto;
|
||||
height: 2.5rem;
|
||||
min-width: 2.5rem;
|
||||
@@ -96,39 +96,39 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.code-block__button:not(:last-of-type) {
|
||||
.code-preview__button:not(:last-of-type) {
|
||||
border-right: solid 1px var(--sl-color-neutral-200);
|
||||
}
|
||||
|
||||
.code-block__button--html,
|
||||
.code-block__button--react {
|
||||
.code-preview__button--html,
|
||||
.code-preview__button--react {
|
||||
width: 70px;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.code-block__button--selected {
|
||||
.code-preview__button--selected {
|
||||
font-weight: 700;
|
||||
color: var(--sl-color-primary-600);
|
||||
}
|
||||
|
||||
.code-block__button--codepen {
|
||||
.code-preview__button--codepen {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
width: 6rem;
|
||||
}
|
||||
|
||||
.code-block__button:first-of-type {
|
||||
.code-preview__button:first-of-type {
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
|
||||
.code-block__button:last-of-type {
|
||||
.code-preview__button:last-of-type {
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
.code-block__button:hover,
|
||||
.code-block__button:active {
|
||||
.code-preview__button:hover,
|
||||
.code-preview__button:active {
|
||||
box-shadow: 0 0 0 1px var(--sl-color-primary-400);
|
||||
border-right-color: transparent;
|
||||
background-color: var(--sl-color-primary-50);
|
||||
@@ -136,13 +136,13 @@
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.code-block__button:focus-visible {
|
||||
.code-preview__button:focus-visible {
|
||||
outline: none;
|
||||
outline: var(--sl-focus-ring);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.code-block__toggle {
|
||||
.code-preview__toggle {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
@@ -151,75 +151,23 @@
|
||||
width: 100%;
|
||||
color: var(--sl-color-neutral-600);
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.code-block__toggle svg {
|
||||
.code-preview__toggle svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
.code-block--expanded .code-block__toggle svg {
|
||||
.code-preview--expanded .code-preview__toggle svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* Copy button styles */
|
||||
.markdown-section .docsify-copy-code-button {
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
background-color: var(--sl-color-neutral-500);
|
||||
border-radius: var(--sl-border-radius-medium);
|
||||
font-family: var(--sl-font-sans);
|
||||
font-size: var(--sl-font-size-x-small);
|
||||
font-weight: var(--sl-font-weight-semibold);
|
||||
text-transform: uppercase;
|
||||
padding: 8px;
|
||||
user-select: none;
|
||||
transition: 0.1s all;
|
||||
}
|
||||
|
||||
.markdown-section .docsify-copy-code-button.copied {
|
||||
animation: pulse 0.75s;
|
||||
--pulse-color: var(--sl-color-neutral-600);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 var(--pulse-color);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 0.5rem transparent;
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.markdown-section .docsify-copy-code-button .label {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.markdown-section .docsify-copy-code-button .success,
|
||||
.markdown-section .docsify-copy-code-button .error {
|
||||
/* We can apply data-flavor="html|react" to any element on the page to toggle it when the flavor changes */
|
||||
.flavor-html [data-flavor]:not([data-flavor='html']) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.markdown-section .docsify-copy-code-button:focus-visible {
|
||||
outline: var(--sl-focus-ring);
|
||||
outline-offset: var(--sl-focus-ring-offset);
|
||||
}
|
||||
|
||||
.markdown-section .docsify-copy-code-button:active {
|
||||
background-color: var(--sl-color-neutral-600);
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
/* We can apply data-flavor="html|react" to any element on the page to toggle it when the user's flavor changes */
|
||||
body.flavor-html [data-flavor]:not([data-flavor='html']) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.flavor-react [data-flavor]:not([data-flavor='react']) {
|
||||
.flavor-react [data-flavor]:not([data-flavor='react']) {
|
||||
display: none;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
347
docs/assets/styles/search.css
Normal file
347
docs/assets/styles/search.css
Normal file
@@ -0,0 +1,347 @@
|
||||
/* Search plugin */
|
||||
:root,
|
||||
:root.sl-theme-dark {
|
||||
--docs-search-box-background: var(--sl-color-neutral-0);
|
||||
--docs-search-box-border-width: 1px;
|
||||
--docs-search-box-border-color: var(--sl-color-neutral-300);
|
||||
--docs-search-box-color: var(--sl-color-neutral-600);
|
||||
--docs-search-dialog-background: var(--sl-color-neutral-0);
|
||||
--docs-search-border-width: var(--docs-border-width);
|
||||
--docs-search-border-color: var(--docs-border-color);
|
||||
--docs-search-text-color: var(--sl-color-neutral-900);
|
||||
--docs-search-text-color-muted: var(--sl-color-neutral-500);
|
||||
--docs-search-font-weight-normal: var(--sl-font-weight-normal);
|
||||
--docs-search-font-weight-semibold: var(--sl-font-weight-semibold);
|
||||
--docs-search-border-radius: calc(2 * var(--docs-border-radius));
|
||||
--docs-search-accent-color: var(--sl-color-primary-600);
|
||||
--docs-search-icon-color: var(--sl-color-neutral-500);
|
||||
--docs-search-icon-color-active: var(--sl-color-neutral-600);
|
||||
--docs-search-shadow: var(--docs-shadow-x-large);
|
||||
--docs-search-result-background-hover: var(--sl-color-neutral-100);
|
||||
--docs-search-result-color-hover: var(--sl-color-neutral-900);
|
||||
--docs-search-result-background-active: var(--sl-color-primary-600);
|
||||
--docs-search-result-color-active: var(--sl-color-neutral-0);
|
||||
--docs-search-focus-ring: var(--sl-focus-ring);
|
||||
--docs-search-overlay-background: rgb(0 0 0 / 0.33);
|
||||
}
|
||||
|
||||
:root.sl-theme-dark {
|
||||
--docs-search-overlay-background: rgb(71 71 71 / 0.33);
|
||||
}
|
||||
|
||||
body.search-visible {
|
||||
padding-right: var(--docs-search-scroll-lock-size) !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* Search box */
|
||||
.search-box {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-radius: 9999px;
|
||||
background: var(--docs-search-box-background);
|
||||
border: solid var(--docs-search-box-border-width) var(--docs-search-box-border-color);
|
||||
font: inherit;
|
||||
color: var(--docs-search-box-color);
|
||||
padding: 0.75rem 1rem;
|
||||
margin: var(--sl-spacing-large) 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-box span {
|
||||
flex: 1 1 auto;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
text-align: left;
|
||||
line-height: 1;
|
||||
margin: 0 0.75rem;
|
||||
}
|
||||
|
||||
.search-box:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search-box:focus-visible {
|
||||
outline: var(--docs-search-focus-ring);
|
||||
}
|
||||
|
||||
/* Site search */
|
||||
.search {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.search[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search__overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: var(--docs-search-overlay-background);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.search__dialog {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: none;
|
||||
max-height: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search__dialog:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search__dialog::backdrop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Fixes an iOS Safari 16.4 bug that draws the parent element's border radius incorrectly when showing/hiding results */
|
||||
.search__header {
|
||||
background-color: var(--docs-search-dialog-background);
|
||||
border-radius: var(--docs-search-border-radius);
|
||||
}
|
||||
|
||||
.search--has-results .search__header {
|
||||
border-top-left-radius: var(--docs-search-border-radius);
|
||||
border-top-right-radius: var(--docs-search-border-radius);
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.search__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
max-height: calc(100vh - 20rem);
|
||||
background-color: var(--docs-search-dialog-background);
|
||||
border-radius: var(--docs-search-border-radius);
|
||||
box-shadow: var(--docs-search-shadow);
|
||||
padding: 0;
|
||||
margin: 10rem auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.search__content {
|
||||
max-width: calc(100% - 2rem);
|
||||
max-height: calc(90svh);
|
||||
margin: 4vh 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.search__input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search__input-wrapper sl-icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
flex: 0 0 auto;
|
||||
color: var(--docs-search-icon-color);
|
||||
margin: 0 1.5rem;
|
||||
}
|
||||
|
||||
.search__clear-button {
|
||||
display: flex;
|
||||
background: none;
|
||||
border: none;
|
||||
font: inherit;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search__clear-button[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search__clear-button:active sl-icon {
|
||||
color: var(--docs-search-icon-color-active);
|
||||
}
|
||||
|
||||
.search__input {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
border: none;
|
||||
font: inherit;
|
||||
font-size: 1.5rem;
|
||||
font-weight: var(--docs-search-font-weight-normal);
|
||||
color: var(--docs-search-text-color);
|
||||
background: transparent;
|
||||
padding: 1rem 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search__input::placeholder {
|
||||
color: var(--docs-search-text-color-muted);
|
||||
}
|
||||
|
||||
.search__input::-webkit-search-decoration,
|
||||
.search__input::-webkit-search-cancel-button,
|
||||
.search__input::-webkit-search-results-button,
|
||||
.search__input::-webkit-search-results-decoration {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search__input:focus,
|
||||
.search__input:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search__body {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.search--has-results .search__body {
|
||||
border-top: solid var(--docs-search-border-width) var(--docs-search-border-color);
|
||||
}
|
||||
|
||||
.search__results {
|
||||
display: none;
|
||||
line-height: 1.2;
|
||||
list-style: none;
|
||||
padding: 0.5rem 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search--has-results .search__results {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.search__results a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
padding: 0.5rem 1.5rem;
|
||||
}
|
||||
|
||||
.search__results a:focus-visible {
|
||||
outline: var(--docs-search-focus-ring);
|
||||
}
|
||||
|
||||
.search__results li a:hover,
|
||||
.search__results li a:hover small {
|
||||
background-color: var(--docs-search-result-background-hover);
|
||||
color: var(--docs-search-result-color-hover);
|
||||
}
|
||||
|
||||
.search__results li[data-selected='true'] a,
|
||||
.search__results li[data-selected='true'] a * {
|
||||
outline: none;
|
||||
background-color: var(--docs-search-result-background-active);
|
||||
color: var(--docs-search-result-color-active);
|
||||
}
|
||||
|
||||
.search__results h3 {
|
||||
font-weight: var(--docs-search-font-weight-semibold);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search__results small {
|
||||
display: block;
|
||||
color: var(--docs-search-text-color-muted);
|
||||
}
|
||||
|
||||
.search__result {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search__result a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.search__result-icon {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
color: var(--docs-search-text-color-muted);
|
||||
}
|
||||
|
||||
.search__result-icon sl-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.search__result__details {
|
||||
width: calc(100% - 3rem);
|
||||
}
|
||||
|
||||
.search__result-title,
|
||||
.search__result-description,
|
||||
.search__result-url {
|
||||
max-width: 400px;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.search__result-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: var(--docs-search-font-weight-semibold);
|
||||
color: var(--docs-search-accent-color);
|
||||
}
|
||||
|
||||
.search__result-description {
|
||||
font-size: 0.875rem;
|
||||
color: var(--docs-search-text-color);
|
||||
}
|
||||
|
||||
.search__result-url {
|
||||
font-size: 0.875rem;
|
||||
color: var(--docs-search-text-color-muted);
|
||||
}
|
||||
|
||||
.search__empty {
|
||||
display: none;
|
||||
border-top: solid var(--docs-search-border-width) var(--docs-search-border-color);
|
||||
text-align: center;
|
||||
color: var(--docs-search-text-color-muted);
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.search--no-results .search__empty {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.search__footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
border-top: solid var(--docs-search-border-width) var(--docs-search-border-color);
|
||||
border-bottom-left-radius: inherit;
|
||||
border-bottom-right-radius: inherit;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.search__footer small {
|
||||
color: var(--docs-search-text-color-muted);
|
||||
}
|
||||
|
||||
.search__footer small kbd:last-of-type {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.search__footer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
236
docs/eleventy.config.cjs
Normal file
236
docs/eleventy.config.cjs
Normal file
@@ -0,0 +1,236 @@
|
||||
/* eslint-disable no-invalid-this */
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const lunr = require('lunr');
|
||||
const { capitalCase } = require('change-case');
|
||||
const { JSDOM } = require('jsdom');
|
||||
const { customElementsManifest, getAllComponents } = require('./_utilities/cem.cjs');
|
||||
const shoelaceFlavoredMarkdown = require('./_utilities/markdown.cjs');
|
||||
const activeLinks = require('./_utilities/active-links.cjs');
|
||||
const anchorHeadings = require('./_utilities/anchor-headings.cjs');
|
||||
const codePreviews = require('./_utilities/code-previews.cjs');
|
||||
const copyCodeButtons = require('./_utilities/copy-code-buttons.cjs');
|
||||
const externalLinks = require('./_utilities/external-links.cjs');
|
||||
const highlightCodeBlocks = require('./_utilities/highlight-code.cjs');
|
||||
const tableOfContents = require('./_utilities/table-of-contents.cjs');
|
||||
const prettier = require('./_utilities/prettier.cjs');
|
||||
const scrollingTables = require('./_utilities/scrolling-tables.cjs');
|
||||
const typography = require('./_utilities/typography.cjs');
|
||||
const replacer = require('./_utilities/replacer.cjs');
|
||||
|
||||
const assetsDir = 'assets';
|
||||
const cdndir = 'cdn';
|
||||
const npmdir = 'dist';
|
||||
const allComponents = getAllComponents();
|
||||
let hasBuiltSearchIndex = false;
|
||||
|
||||
module.exports = function (eleventyConfig) {
|
||||
//
|
||||
// Global data
|
||||
//
|
||||
eleventyConfig.addGlobalData('baseUrl', 'https://shoelace.style/'); // the production URL
|
||||
eleventyConfig.addGlobalData('layout', 'default'); // make 'default' the default layout
|
||||
eleventyConfig.addGlobalData('toc', true); // enable the table of contents
|
||||
eleventyConfig.addGlobalData('meta', {
|
||||
title: 'Shoelace',
|
||||
description: 'A forward-thinking library of web components.',
|
||||
image: 'images/og-image.png',
|
||||
version: customElementsManifest.package.version,
|
||||
components: allComponents,
|
||||
cdndir,
|
||||
npmdir
|
||||
});
|
||||
|
||||
//
|
||||
// Layout aliases
|
||||
//
|
||||
eleventyConfig.addLayoutAlias('default', 'default.njk');
|
||||
|
||||
//
|
||||
// Copy assets
|
||||
//
|
||||
eleventyConfig.addPassthroughCopy(assetsDir);
|
||||
eleventyConfig.setServerPassthroughCopyBehavior('passthrough'); // emulates passthrough copy during --serve
|
||||
|
||||
//
|
||||
// Functions
|
||||
//
|
||||
|
||||
// Generates a URL relative to the site's root
|
||||
eleventyConfig.addNunjucksGlobal('rootUrl', (value = '', absolute = false) => {
|
||||
value = path.join('/', value);
|
||||
return absolute ? new URL(value, eleventyConfig.globalData.baseUrl).toString() : value;
|
||||
});
|
||||
|
||||
// Generates a URL relative to the site's asset directory
|
||||
eleventyConfig.addNunjucksGlobal('assetUrl', (value = '', absolute = false) => {
|
||||
value = path.join(`/${assetsDir}`, value);
|
||||
return absolute ? new URL(value, eleventyConfig.globalData.baseUrl).toString() : value;
|
||||
});
|
||||
|
||||
// Fetches a specific component's metadata
|
||||
eleventyConfig.addNunjucksGlobal('getComponent', tagName => {
|
||||
const component = allComponents.find(c => c.tagName === tagName);
|
||||
if (!component) {
|
||||
throw new Error(
|
||||
`Unable to find a component called "${tagName}". Make sure the file name is the same as the component's tag ` +
|
||||
`name (minus the sl- prefix).`
|
||||
);
|
||||
}
|
||||
return component;
|
||||
});
|
||||
|
||||
//
|
||||
// Custom markdown syntaxes
|
||||
//
|
||||
eleventyConfig.setLibrary('md', shoelaceFlavoredMarkdown);
|
||||
|
||||
//
|
||||
// Filters
|
||||
//
|
||||
eleventyConfig.addFilter('markdown', content => {
|
||||
return shoelaceFlavoredMarkdown.render(content);
|
||||
});
|
||||
|
||||
eleventyConfig.addFilter('markdownInline', content => {
|
||||
return shoelaceFlavoredMarkdown.renderInline(content);
|
||||
});
|
||||
|
||||
eleventyConfig.addFilter('classNameToComponentName', className => {
|
||||
let name = capitalCase(className.replace(/^Sl/, ''));
|
||||
if (name === 'Qr Code') name = 'QR Code'; // manual override
|
||||
return name;
|
||||
});
|
||||
|
||||
eleventyConfig.addFilter('removeSlPrefix', tagName => {
|
||||
return tagName.replace(/^sl-/, '');
|
||||
});
|
||||
|
||||
//
|
||||
// Transforms
|
||||
//
|
||||
eleventyConfig.addTransform('html-transform', function (content) {
|
||||
// Parse the template and get a Document object
|
||||
const doc = new JSDOM(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.
|
||||
url: `https://internal/`
|
||||
}).window.document;
|
||||
|
||||
// DOM transforms
|
||||
activeLinks(doc, { pathname: this.page.url });
|
||||
anchorHeadings(doc, {
|
||||
within: '#content .content__body',
|
||||
levels: ['h2', 'h3', 'h4', 'h5']
|
||||
});
|
||||
tableOfContents(doc, {
|
||||
levels: ['h2', 'h3'],
|
||||
container: '#content .content__toc > ul',
|
||||
within: '#content .content__body'
|
||||
});
|
||||
codePreviews(doc);
|
||||
externalLinks(doc, { target: '_blank' });
|
||||
highlightCodeBlocks(doc);
|
||||
scrollingTables(doc);
|
||||
copyCodeButtons(doc); // must be after codePreviews + highlightCodeBlocks
|
||||
typography(doc, '#content');
|
||||
replacer(doc, [
|
||||
{ pattern: '%VERSION%', replacement: customElementsManifest.package.version },
|
||||
{ pattern: '%CDNDIR%', replacement: cdndir },
|
||||
{ pattern: '%NPMDIR%', replacement: npmdir }
|
||||
]);
|
||||
|
||||
// Serialize the Document object to an HTML string and prepend the doctype
|
||||
content = `<!DOCTYPE html>\n${doc.documentElement.outerHTML}`;
|
||||
|
||||
// String transforms
|
||||
content = prettier(content);
|
||||
|
||||
return content;
|
||||
});
|
||||
|
||||
//
|
||||
// Build a search index
|
||||
//
|
||||
eleventyConfig.on('eleventy.after', ({ results }) => {
|
||||
// We only want to build the search index on the first run so all pages get indexed.
|
||||
if (hasBuiltSearchIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
const map = {};
|
||||
const searchIndexFilename = path.join(eleventyConfig.dir.output, assetsDir, 'search.json');
|
||||
const lunrInput = path.resolve('../node_modules/lunr/lunr.min.js');
|
||||
const lunrOutput = path.join(eleventyConfig.dir.output, assetsDir, 'scripts/lunr.js');
|
||||
const searchIndex = lunr(function () {
|
||||
// The search index uses these field names extensively, so shortening them can save some serious bytes. The
|
||||
// initial index file went from 468 KB => 401 KB by using single-character names!
|
||||
this.ref('id'); // id
|
||||
this.field('t', { boost: 50 }); // title
|
||||
this.field('h', { boost: 25 }); // headings
|
||||
this.field('c'); // content
|
||||
|
||||
results.forEach((result, index) => {
|
||||
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.
|
||||
url: `https://internal/`
|
||||
}).window.document;
|
||||
const content = doc.querySelector('#content');
|
||||
|
||||
// Get title and headings
|
||||
const title = (doc.querySelector('title')?.textContent || path.basename(result.outputPath)).trim();
|
||||
const headings = [...content.querySelectorAll('h1, h2, h3, h4')]
|
||||
.map(heading => heading.textContent)
|
||||
.join(' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
|
||||
// Remove code blocks and whitespace from content
|
||||
[...content.querySelectorAll('code[class|=language]')].forEach(code => code.remove());
|
||||
const textContent = content.textContent.replace(/\s+/g, ' ').trim();
|
||||
|
||||
// Update the index and map
|
||||
this.add({ id: index, t: title, h: headings, c: textContent });
|
||||
map[index] = { title, url };
|
||||
});
|
||||
});
|
||||
|
||||
// Copy the Lunr search client and write the index
|
||||
fs.mkdirSync(path.dirname(lunrOutput), { recursive: true });
|
||||
fs.copyFileSync(lunrInput, lunrOutput);
|
||||
fs.writeFileSync(searchIndexFilename, JSON.stringify({ searchIndex, map }), 'utf-8');
|
||||
|
||||
hasBuiltSearchIndex = true;
|
||||
});
|
||||
|
||||
//
|
||||
// Send a signal to stdout that let's the build know we've reached this point
|
||||
//
|
||||
eleventyConfig.on('eleventy.after', () => {
|
||||
console.log('[eleventy.after]');
|
||||
});
|
||||
|
||||
//
|
||||
// Dev server options (see https://www.11ty.dev/docs/dev-server/#options)
|
||||
//
|
||||
eleventyConfig.setServerOptions({
|
||||
domDiff: false, // disable dom diffing so custom elements don't break on reload,
|
||||
port: 4000, // if port 4000 is taken, 11ty will use the next one available
|
||||
watch: ['cdn/**/*'] // additional files to watch that will trigger server updates (array of paths or globs)
|
||||
});
|
||||
|
||||
//
|
||||
// 11ty config
|
||||
//
|
||||
return {
|
||||
dir: {
|
||||
input: 'pages',
|
||||
output: '../_site',
|
||||
includes: '../_includes' // resolved relative to the input dir
|
||||
},
|
||||
markdownTemplateEngine: 'njk', // use Nunjucks instead of Liquid for markdown files
|
||||
templateEngineOverride: ['njk'] // just Nunjucks and then markdown
|
||||
};
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
# Angular
|
||||
|
||||
Angular [plays nice](https://custom-elements-everywhere.com/#angular) with custom elements, so you can use Shoelace in your Angular apps with ease.
|
||||
|
||||
## Installation
|
||||
|
||||
To add Shoelace to your Angular app, install the package from npm.
|
||||
|
||||
```bash
|
||||
npm install @shoelace-style/shoelace
|
||||
```
|
||||
|
||||
Next, [include a theme](/getting-started/themes) and set the [base path](/getting-started/installation#setting-the-base-path) for icons and other assets. In this example, we'll import the light theme and use the CDN as a base path.
|
||||
|
||||
```jsx
|
||||
import '@shoelace-style/shoelace/dist/themes/light.css';
|
||||
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path';
|
||||
|
||||
setBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/');
|
||||
```
|
||||
|
||||
?> If you'd rather not use the CDN for assets, you can create a build task that copies `node_modules/@shoelace-style/shoelace/dist/assets` into a public folder in your app. Then you can point the base path to that folder instead.
|
||||
|
||||
## Configuration
|
||||
|
||||
Then make sure to apply the custom elements schema as shown below.
|
||||
|
||||
```js
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [BrowserModule],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
Now you can start using Shoelace components in your app!
|
||||
|
||||
?> Are you using Shoelace with Angular? [Help us improve this page!](https://github.com/shoelace-style/shoelace/blob/next/docs/frameworks/angular.md)
|
||||
@@ -1,144 +0,0 @@
|
||||
# Installation
|
||||
|
||||
You can use Shoelace via CDN or by installing it locally. You can also [cherry pick](#cherry-picking) individual components for faster load times.
|
||||
|
||||
If you're using a framework, make sure to check out the pages for [React](/frameworks/react), [Vue](/frameworks/vue), and [Angular](/frameworks/angular).
|
||||
|
||||
## CDN Installation (Easiest)
|
||||
|
||||
The easiest way to install Shoelace is with the CDN. Just add the following tags to your page to get all components and the default light theme.
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/themes/light.css" />
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/shoelace.js"></script>
|
||||
```
|
||||
|
||||
?> If you're only using a handful of components, it will be more efficient to [cherry pick](#cherry-picking) the ones you need.
|
||||
|
||||
### Dark Theme
|
||||
|
||||
If you prefer to use the [dark theme](/getting-started/themes#dark-theme) instead, use this code and add `<html class="sl-theme-dark">` to the page.
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/themes/dark.css" />
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/shoelace.js"></script>
|
||||
```
|
||||
|
||||
### Light & Dark Theme
|
||||
|
||||
If you want to load the light or dark theme based on the user's `prefers-color-scheme` setting, use this. The `media` attributes ensure that only the user's preferred theme stylesheet loads and the `onload` attribute sets the appropriate [theme class](/getting-started/themes) on the `<html>` element.
|
||||
|
||||
```html
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="(prefers-color-scheme:light)"
|
||||
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/themes/light.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="(prefers-color-scheme:dark)"
|
||||
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/themes/dark.css"
|
||||
onload="document.documentElement.classList.add('sl-theme-dark');"
|
||||
/>
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/shoelace.js"></script>
|
||||
```
|
||||
|
||||
Now you can [start using Shoelace!](/getting-started/usage)
|
||||
|
||||
## Local Installation
|
||||
|
||||
If you don't want to use the CDN, you can install Shoelace locally with the following command.
|
||||
|
||||
```bash
|
||||
npm install @shoelace-style/shoelace
|
||||
```
|
||||
|
||||
It's up to you to make the source files available to your app. One way to do this is to create a route in your app called `/shoelace` that serves static files from `node_modules/@shoelace-style/shoelace`.
|
||||
|
||||
Once you've done that, add the following tags to your page. Make sure to update `href` and `src` so they point to the route you created.
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="/shoelace/dist/themes/light.css" />
|
||||
<script type="module" src="/shoelace/dist/shoelace.js"></script>
|
||||
```
|
||||
|
||||
Alternatively, [you can use a bundler](#bundling).
|
||||
|
||||
?> For clarity, the docs will usually show imports from `@shoelace-style/shoelace`. If you're not using a module resolver or bundler, you'll need to adjust these paths to point to the folder Shoelace is in.
|
||||
|
||||
## Setting the Base Path
|
||||
|
||||
Some components rely on assets (icons, images, etc.) and Shoelace needs to know where they're located. For convenience, Shoelace will try to auto-detect the correct location based on the script you've loaded it from. This assumes assets are colocated with `shoelace.js` and will "just work" for most users.
|
||||
|
||||
However, if you're [cherry picking](#cherry-picking) or [bundling](#bundling) Shoelace, you'll need to set the base path. You can do this one of two ways.
|
||||
|
||||
```html
|
||||
<!-- Option 1: the data-shoelace attribute -->
|
||||
<script src="bundle.js" data-shoelace="/path/to/shoelace/dist"></script>
|
||||
|
||||
<!-- Option 2: the setBasePath() method -->
|
||||
<script src="bundle.js"></script>
|
||||
<script type="module">
|
||||
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js';
|
||||
setBasePath('/path/to/shoelace/dist');
|
||||
</script>
|
||||
```
|
||||
|
||||
?> The library also exports a `getBasePath()` method you can use to reference assets.
|
||||
|
||||
## Cherry Picking
|
||||
|
||||
The previous approach is the _easiest_ way to load Shoelace, but easy isn't always efficient. You'll incur the full size of the library even if you only use a handful of components. This is convenient for prototyping or if you're using most of the components, but it may result in longer load times in production. To improve this, you can cherry pick the components you need.
|
||||
|
||||
Cherry picking can be done from your local install or [directly from the CDN](https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/). This will limit the number of files the browser has to download and reduce the amount of bytes being transferred. The disadvantage is that you need to load components manually.
|
||||
|
||||
Here's an example that loads only the button component. Again, if you're not using a module resolver, you'll need to adjust the path to point to the folder Shoelace is in.
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="/path/to/shoelace/dist/themes/light.css" />
|
||||
|
||||
<script type="module" data-shoelace="/path/to/shoelace/dist">
|
||||
import '@shoelace-style/shoelace/dist/components/button/button.js';
|
||||
|
||||
// <sl-button> is ready to use!
|
||||
</script>
|
||||
```
|
||||
|
||||
You can copy and paste the code to import a component from the "Importing" section of the component's documentation. Note that some components have dependencies that are automatically imported when you cherry pick. If a component has dependencies, they will be listed in the "Dependencies" section of its docs.
|
||||
|
||||
!> Never cherry pick components or utilities from `shoelace.js` as this will cause the browser to load the entire library. Instead, cherry pick from specific modules as shown above.
|
||||
|
||||
!> You will see files named `chunk.[hash].js` in the `chunks` directory. Never import these files directly, as they are generated and change from version to version.
|
||||
|
||||
## Bundling
|
||||
|
||||
Shoelace is distributed as a collection of standard ES modules that [all modern browsers can understand](https://caniuse.com/es6-module). However, importing a lot of modules can result in a lot of HTTP requests and potentially longer load times. Using a CDN can alleviate this, but some users may wish to further optimize their imports with a bundler.
|
||||
|
||||
To use Shoelace with a bundler, first install Shoelace along with your bundler of choice.
|
||||
|
||||
```bash
|
||||
npm install @shoelace-style/shoelace
|
||||
```
|
||||
|
||||
Now it's time to configure your bundler. Configurations vary for each tool, but here are some examples to help you get started.
|
||||
|
||||
- [Example webpack config](https://github.com/shoelace-style/webpack-example/blob/master/webpack.config.js)
|
||||
- [Example Rollup config](https://github.com/shoelace-style/rollup-example/blob/master/rollup.config.js)
|
||||
|
||||
Once your bundler is configured, you'll be able to import Shoelace components and utilities.
|
||||
|
||||
```js
|
||||
import '@shoelace-style/shoelace/dist/themes/light.css';
|
||||
import '@shoelace-style/shoelace/dist/components/button/button.js';
|
||||
import '@shoelace-style/shoelace/dist/components/icon/icon.js';
|
||||
import '@shoelace-style/shoelace/dist/components/input/input.js';
|
||||
import '@shoelace-style/shoelace/dist/components/rating/rating.js';
|
||||
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js';
|
||||
|
||||
// Set the base path to the folder you copied Shoelace's assets to
|
||||
setBasePath('/path/to/shoelace/dist');
|
||||
|
||||
// <sl-button>, <sl-icon>, <sl-input>, and <sl-rating> are ready to use!
|
||||
```
|
||||
|
||||
!> 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.
|
||||
@@ -1,96 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Shoelace: A forward-thinking library of web components.</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Shoelace provides a collection of professionally designed, every day UI components built on a framework-agnostic technology."
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="custom-elements-manifest" content="/dist/custom-elements.json" />
|
||||
<meta property="og:title" content="Shoelace" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Shoelace provides a collection of professionally designed, every day UI components built on a framework-agnostic technology."
|
||||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://shoelace.style/" />
|
||||
<meta property="og:image" content="https://shoelace.style/assets/images/og-image.png" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:site" content="@shoelace_style" />
|
||||
<meta name="twitter:creator" content="@claviska" />
|
||||
<meta name="twitter:title" content="Shoelace" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Shoelace provides a collection of professionally designed, every day UI components built on a framework-agnostic technology."
|
||||
/>
|
||||
<meta name="twitter:image" content="https://shoelace.style/assets/images/twitter-card.png" />
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify@4/themes/pure.css" />
|
||||
<link rel="stylesheet" href="/assets/styles/docs.css" />
|
||||
<link rel="stylesheet" href="/assets/plugins/code-block/code-block.css" />
|
||||
<link rel="stylesheet" href="/assets/plugins/search/search.css" />
|
||||
<link rel="stylesheet" href="/assets/plugins/theme-picker/theme-picker.css" />
|
||||
<link rel="icon" href="/assets/images/logo.svg" type="image/x-icon" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/touch-icon.png" />
|
||||
|
||||
<!-- Import Shoelace -->
|
||||
<link rel="stylesheet" href="/dist/themes/light.css" />
|
||||
<link rel="stylesheet" href="/dist/themes/dark.css" />
|
||||
<script type="module" src="/dist/shoelace.js"></script>
|
||||
</head>
|
||||
<body data-shoelace="/dist">
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
// Set the initial theme to prevent flashing
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const theme = localStorage.getItem('theme') || 'auto';
|
||||
document.documentElement.classList.toggle('sl-theme-dark', theme === 'dark' || (theme === 'auto' && prefersDark));
|
||||
</script>
|
||||
<script>
|
||||
window.$docsify = {
|
||||
alias: {
|
||||
'/.*/_sidebar.md': '/_sidebar.md'
|
||||
},
|
||||
auto2top: true,
|
||||
copyCode: {
|
||||
buttonText: 'Copy',
|
||||
errorText: 'Failed to copy',
|
||||
successText: 'Copied'
|
||||
},
|
||||
coverpage: false,
|
||||
executeScript: true,
|
||||
homepage: '/getting-started/overview.md',
|
||||
loadSidebar: true,
|
||||
logo: '/assets/images/wordmark.svg',
|
||||
maxLevel: 3,
|
||||
subMaxLevel: 2,
|
||||
name: 'Shoelace',
|
||||
nameLink: '/',
|
||||
notFoundPage: '404.md',
|
||||
pagination: {
|
||||
previousText: 'Previous',
|
||||
nextText: 'Next',
|
||||
crossChapter: true,
|
||||
crossChapterText: false
|
||||
},
|
||||
routerMode: 'history',
|
||||
themeColor: 'var(--sl-color-primary-500)'
|
||||
};
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify@4/lib/docsify.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify-copy-code@2"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify-pagination@2/dist/docsify-pagination.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.25.0/components/prism-bash.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.25.0/components/prism-jsx.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.25.0/components/prism-tsx.min.js"></script>
|
||||
<script src="/assets/plugins/code-block/code-block.js"></script>
|
||||
<script src="/assets/plugins/metadata/metadata.js"></script>
|
||||
<script src="/assets/plugins/scroll-position/scroll-position.js"></script>
|
||||
<script src="/assets/plugins/search/lunr.min.js"></script>
|
||||
<script src="/assets/plugins/search/search.js"></script>
|
||||
<script src="/assets/plugins/theme-picker/theme-picker.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
19
docs/pages/404.md
Normal file
19
docs/pages/404.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
meta:
|
||||
title: Page Not Found
|
||||
description: "The page you were looking for couldn't be found."
|
||||
permalink: 404.html
|
||||
toc: false
|
||||
---
|
||||
|
||||
<div style="text-align: center;">
|
||||
|
||||
# Page Not Found
|
||||
|
||||

|
||||
|
||||
The page you were looking for couldn't be found.
|
||||
|
||||
Press [[/]] to search, or [head back to the homepage](/).
|
||||
|
||||
</div>
|
||||
@@ -1,15 +1,18 @@
|
||||
# Alert
|
||||
---
|
||||
meta:
|
||||
title: Alert
|
||||
description: Alerts are used to display important messages inline or as toast notifications.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-alert]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-alert open>
|
||||
<sl-icon slot="icon" name="info-circle"></sl-icon>
|
||||
This is a standard alert. You can customize its content and even the icon.
|
||||
</sl-alert>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlAlert, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -20,7 +23,9 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
?> Alerts will not be visible if the `open` attribute is not present.
|
||||
:::tip
|
||||
Alerts will not be visible if the `open` attribute is not present.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -28,7 +33,7 @@ const App = () => (
|
||||
|
||||
Set the `variant` attribute to change the alert's variant.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-alert variant="primary" open>
|
||||
<sl-icon slot="icon" name="info-circle"></sl-icon>
|
||||
<strong>This is super informative</strong><br />
|
||||
@@ -68,7 +73,7 @@ Set the `variant` attribute to change the alert's variant.
|
||||
</sl-alert>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlAlert, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -123,7 +128,7 @@ const App = () => (
|
||||
|
||||
Add the `closable` attribute to show a close button that will hide the alert.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-alert variant="primary" open closable class="alert-closable">
|
||||
<sl-icon slot="icon" name="info-circle"></sl-icon>
|
||||
You can close this alert any time!
|
||||
@@ -137,7 +142,7 @@ Add the `closable` attribute to show a close button that will hide the alert.
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlAlert, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -162,11 +167,11 @@ const App = () => {
|
||||
|
||||
Icons are optional. Simply omit the `icon` slot if you don't want them.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-alert variant="primary" open> Nothing fancy here, just a simple alert. </sl-alert>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlAlert } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -180,7 +185,7 @@ const App = () => (
|
||||
|
||||
Set the `duration` attribute to automatically hide an alert after a period of time. This is useful for alerts that don't require acknowledgement.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="alert-duration">
|
||||
<sl-button variant="primary">Show Alert</sl-button>
|
||||
|
||||
@@ -205,7 +210,7 @@ Set the `duration` attribute to automatically hide an alert after a period of ti
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlAlert, SlButton, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -243,7 +248,7 @@ To display an alert as a toast notification, or "toast", create the alert and ca
|
||||
|
||||
You should always use the `closable` attribute so users can dismiss the notification. It's also common to set a reasonable `duration` when the notification doesn't require acknowledgement.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="alert-toast">
|
||||
<sl-button variant="primary">Primary</sl-button>
|
||||
<sl-button variant="success">Success</sl-button>
|
||||
@@ -294,7 +299,7 @@ You should always use the `closable` attribute so users can dismiss the notifica
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useRef } from 'react';
|
||||
import { SlAlert, SlButton, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -374,7 +379,7 @@ const App = () => {
|
||||
|
||||
For convenience, you can create a utility that emits toast notifications with a function call rather than composing them in your HTML. To do this, generate the alert with JavaScript, append it to the body, and call the `toast()` method as shown in the example below.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="alert-toast-wrapper">
|
||||
<sl-button variant="primary">Create Toast</sl-button>
|
||||
</div>
|
||||
@@ -426,6 +431,6 @@ By default, the toast stack is positioned at the top-right of the viewport. You
|
||||
}
|
||||
```
|
||||
|
||||
?> By design, it is not possible to show toasts in more than one stack simultaneously. Such behavior is confusing and makes for a poor user experience.
|
||||
|
||||
[component-metadata:sl-alert]
|
||||
:::tip
|
||||
By design, it is not possible to show toasts in more than one stack simultaneously. Such behavior is confusing and makes for a poor user experience.
|
||||
:::
|
||||
@@ -1,15 +1,18 @@
|
||||
# Animated Image
|
||||
---
|
||||
meta:
|
||||
title: Animated Image
|
||||
description: A component for displaying animated GIFs and WEBPs that play and pause on interaction.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-animated-image]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-animated-image
|
||||
src="https://shoelace.style/assets/images/walk.gif"
|
||||
alt="Animation of untied shoes walking on pavement"
|
||||
></sl-animated-image>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlAnimatedImage } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -20,7 +23,9 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
?> This component uses `<canvas>` to draw freeze frames, so images are subject to [cross-origin restrictions](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image).
|
||||
:::tip
|
||||
This component uses `<canvas>` to draw freeze frames, so images are subject to [cross-origin restrictions](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image).
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -28,14 +33,14 @@ const App = () => (
|
||||
|
||||
Both GIF and WEBP images are supported.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-animated-image
|
||||
src="https://shoelace.style/assets/images/tie.webp"
|
||||
alt="Animation of a shoe being tied"
|
||||
></sl-animated-image>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlAnimatedImage } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -47,7 +52,7 @@ const App = () => (
|
||||
|
||||
To set a custom size, apply a width and/or height to the host element.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-animated-image
|
||||
src="https://shoelace.style/assets/images/walk.gif"
|
||||
alt="Animation of untied shoes walking on pavement"
|
||||
@@ -56,7 +61,9 @@ To set a custom size, apply a width and/or height to the host element.
|
||||
</sl-animated-image>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlAnimatedImage } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -68,11 +75,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Customizing the Control Box
|
||||
|
||||
You can change the appearance and location of the control box by targeting the `control-box` part in your styles.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-animated-image
|
||||
src="https://shoelace.style/assets/images/walk.gif"
|
||||
alt="Animation of untied shoes walking on pavement"
|
||||
@@ -92,7 +101,7 @@ You can change the appearance and location of the control box by targeting the `
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlAnimatedImage } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -119,5 +128,3 @@ const App = () => (
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-animated-image]
|
||||
@@ -1,10 +1,13 @@
|
||||
# Animation
|
||||
|
||||
[component-header:sl-animation]
|
||||
---
|
||||
meta:
|
||||
title: Animation
|
||||
description: Animate elements declaratively with nearly 100 baked-in presets, or roll your own with custom keyframes.
|
||||
layout: component
|
||||
---
|
||||
|
||||
To animate an element, wrap it in `<sl-animation>` and set an animation `name`. The animation will not start until you add the `play` attribute. Refer to the [properties table](#properties) for a list of all animation options.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="animation-overview">
|
||||
<sl-animation name="bounce" duration="2000" play><div class="box"></div></sl-animation>
|
||||
<sl-animation name="jello" duration="2000" play><div class="box"></div></sl-animation>
|
||||
@@ -23,7 +26,7 @@ To animate an element, wrap it in `<sl-animation>` and set an animation `name`.
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlAnimation } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -58,7 +61,9 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
?> The animation will only be applied to the first child element found in `<sl-animation>`.
|
||||
:::tip
|
||||
The animation will only be applied to the first child element found in `<sl-animation>`.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -66,7 +71,7 @@ const App = () => (
|
||||
|
||||
This example demonstrates all of the baked-in animations and easings. Animations are based on those found in the popular [Animate.css](https://animate.style/) library.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="animation-sandbox">
|
||||
<sl-animation name="bounce" easing="ease-in-out" duration="2000" play>
|
||||
<div class="box"></div>
|
||||
@@ -133,7 +138,7 @@ This example demonstrates all of the baked-in animations and easings. Animations
|
||||
|
||||
Use an [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) to control the animation when an element enters or exits the viewport. For example, scroll the box below in and out of your screen. The animation stops when the box exits the viewport and restarts each time it enters the viewport.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="animation-scroll">
|
||||
<sl-animation name="jackInTheBox" duration="2000" iterations="1"><div class="box"></div></sl-animation>
|
||||
</div>
|
||||
@@ -166,7 +171,7 @@ Use an [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { SlAnimation } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -220,7 +225,7 @@ const App = () => {
|
||||
|
||||
Supply your own [keyframe formats](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Keyframe_Formats) to build custom animations.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="animation-keyframes">
|
||||
<sl-animation easing="ease-in-out" duration="2000" play>
|
||||
<div class="box"></div>
|
||||
@@ -256,7 +261,7 @@ Supply your own [keyframe formats](https://developer.mozilla.org/en-US/docs/Web/
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlAnimation } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -304,7 +309,7 @@ const App = () => (
|
||||
|
||||
Animations won't play until you apply the `play` attribute. You can omit it initially, then apply it on demand such as after a user interaction. In this example, the button will animate once every time the button is clicked.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="animation-form">
|
||||
<sl-animation name="rubberBand" duration="1000" iterations="1">
|
||||
<sl-button variant="primary">Click me</sl-button>
|
||||
@@ -322,7 +327,7 @@ Animations won't play until you apply the `play` attribute. You can omit it init
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlAnimation, SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -340,5 +345,3 @@ const App = () => {
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
[component-metadata:sl-animation]
|
||||
@@ -1,14 +1,17 @@
|
||||
# Avatar
|
||||
|
||||
[component-header:sl-avatar]
|
||||
---
|
||||
meta:
|
||||
title: Avatar
|
||||
description: Avatars are used to represent a person or object.
|
||||
layout: component
|
||||
---
|
||||
|
||||
By default, a generic icon will be shown. You can personalize avatars by adding custom icons, initials, and images. You should always provide a `label` for assistive devices.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-avatar label="User avatar"></sl-avatar>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlAvatar } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlAvatar label="User avatar" />;
|
||||
@@ -21,7 +24,7 @@ const App = () => <SlAvatar label="User avatar" />;
|
||||
To use an image for the avatar, set the `image` and `label` attributes. This will take priority and be shown over initials and icons.
|
||||
Avatar images can be lazily loaded by setting the `loading` attribute to `lazy`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-avatar
|
||||
image="https://images.unsplash.com/photo-1529778873920-4da4926a72c2?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=80"
|
||||
label="Avatar of a gray tabby kitten looking down"
|
||||
@@ -33,7 +36,7 @@ Avatar images can be lazily loaded by setting the `loading` attribute to `lazy`.
|
||||
></sl-avatar>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlAvatar } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -53,11 +56,11 @@ const App = () => (
|
||||
|
||||
When you don't have an image to use, you can set the `initials` attribute to show something more personalized than an icon.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-avatar initials="SL" label="Avatar with initials: SL"></sl-avatar>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlAvatar } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlAvatar initials="SL" label="Avatar with initials: SL" />;
|
||||
@@ -67,7 +70,7 @@ const App = () => <SlAvatar initials="SL" label="Avatar with initials: SL" />;
|
||||
|
||||
When no image or initials are set, an icon will be shown. The default avatar shows a generic "user" icon, but you can customize this with the `icon` slot.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-avatar label="Avatar with an image icon">
|
||||
<sl-icon slot="icon" name="image"></sl-icon>
|
||||
</sl-avatar>
|
||||
@@ -81,7 +84,7 @@ When no image or initials are set, an icon will be shown. The default avatar sho
|
||||
</sl-avatar>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlAvatar, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -105,13 +108,13 @@ const App = () => (
|
||||
|
||||
Avatars can be shaped using the `shape` attribute.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-avatar shape="square" label="Square avatar"></sl-avatar>
|
||||
<sl-avatar shape="rounded" label="Rounded avatar"></sl-avatar>
|
||||
<sl-avatar shape="circle" label="Circle avatar"></sl-avatar>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlAvatar, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -127,7 +130,7 @@ const App = () => (
|
||||
|
||||
You can group avatars with a few lines of CSS.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="avatar-group">
|
||||
<sl-avatar
|
||||
image="https://images.unsplash.com/photo-1490150028299-bf57d78394e0?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&q=80&crop=right"
|
||||
@@ -161,7 +164,7 @@ You can group avatars with a few lines of CSS.
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlAvatar, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -202,5 +205,3 @@ const App = () => (
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-avatar]
|
||||
@@ -1,12 +1,15 @@
|
||||
# Badge
|
||||
---
|
||||
meta:
|
||||
title: Badge
|
||||
description: Badges are used to draw attention and display statuses or counts.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-badge]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-badge>Badge</sl-badge>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlBadge } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlBadge>Badge</SlBadge>;
|
||||
@@ -18,7 +21,7 @@ const App = () => <SlBadge>Badge</SlBadge>;
|
||||
|
||||
Set the `variant` attribute to change the badge's variant.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-badge variant="primary">Primary</sl-badge>
|
||||
<sl-badge variant="success">Success</sl-badge>
|
||||
<sl-badge variant="neutral">Neutral</sl-badge>
|
||||
@@ -26,7 +29,7 @@ Set the `variant` attribute to change the badge's variant.
|
||||
<sl-badge variant="danger">Danger</sl-badge>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlBadge } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -44,7 +47,7 @@ const App = () => (
|
||||
|
||||
Use the `pill` attribute to give badges rounded edges.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-badge variant="primary" pill>Primary</sl-badge>
|
||||
<sl-badge variant="success" pill>Success</sl-badge>
|
||||
<sl-badge variant="neutral" pill>Neutral</sl-badge>
|
||||
@@ -52,7 +55,7 @@ Use the `pill` attribute to give badges rounded edges.
|
||||
<sl-badge variant="danger" pill>Danger</sl-badge>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlBadge } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -80,7 +83,7 @@ const App = () => (
|
||||
|
||||
Use the `pulse` attribute to draw attention to the badge with a subtle animation.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="badge-pulse">
|
||||
<sl-badge variant="primary" pill pulse>1</sl-badge>
|
||||
<sl-badge variant="success" pill pulse>1</sl-badge>
|
||||
@@ -96,7 +99,7 @@ Use the `pulse` attribute to draw attention to the badge with a subtle animation
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlBadge } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -134,7 +137,7 @@ const App = () => (
|
||||
|
||||
One of the most common use cases for badges is attaching them to buttons. To make this easier, badges will be automatically positioned at the top-right when they're a child of a button.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button>
|
||||
Requests
|
||||
<sl-badge pill>30</sl-badge>
|
||||
@@ -151,7 +154,9 @@ One of the most common use cases for badges is attaching them to buttons. To mak
|
||||
</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlBadge, SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -178,11 +183,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### With Menu Items
|
||||
|
||||
When including badges in menu items, use the `suffix` slot to make sure they're aligned correctly.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-menu style="max-width: 240px;">
|
||||
<sl-menu-label>Messages</sl-menu-label>
|
||||
<sl-menu-item>Comments <sl-badge slot="suffix" variant="neutral" pill>4</sl-badge></sl-menu-item>
|
||||
@@ -190,7 +197,9 @@ When including badges in menu items, use the `suffix` slot to make sure they're
|
||||
</sl-menu>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlBadge, SlButton, SlMenu, SlMenuItem, SlMenuLabel } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -218,4 +227,4 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-badge]
|
||||
{% endraw %}
|
||||
@@ -1,8 +1,11 @@
|
||||
# Breadcrumb Item
|
||||
---
|
||||
meta:
|
||||
title: Breadcrumb Item
|
||||
description: Breadcrumb Items are used inside breadcrumbs to represent different links.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-breadcrumb-item]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item>
|
||||
<sl-icon slot="prefix" name="house"></sl-icon>
|
||||
@@ -13,7 +16,7 @@
|
||||
</sl-breadcrumb>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlBreadcrumb, SlBreadcrumbItem, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -28,6 +31,6 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
?> Additional demonstrations can be found in the [breadcrumb examples](/components/breadcrumb).
|
||||
|
||||
[component-metadata:sl-breadcrumb-item]
|
||||
:::tip
|
||||
Additional demonstrations can be found in the [breadcrumb examples](/components/breadcrumb).
|
||||
:::
|
||||
@@ -1,10 +1,13 @@
|
||||
# Breadcrumb
|
||||
|
||||
[component-header:sl-breadcrumb]
|
||||
---
|
||||
meta:
|
||||
title: Breadcrumb
|
||||
description: Breadcrumbs provide a group of links so users can easily navigate a website's hierarchy.
|
||||
layout: component
|
||||
---
|
||||
|
||||
Breadcrumbs are usually placed before a page's main content with the current page shown last to indicate the user's position in the navigation.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item>Catalog</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>Clothing</sl-breadcrumb-item>
|
||||
@@ -13,7 +16,7 @@ Breadcrumbs are usually placed before a page's main content with the current pag
|
||||
</sl-breadcrumb>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlBreadcrumb, SlBreadcrumbItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -34,7 +37,7 @@ By default, breadcrumb items are rendered as buttons so you can use them to navi
|
||||
|
||||
For websites, you'll probably want to use links instead. You can make any breadcrumb item a link by applying an `href` attribute to it. Now, when the user activates it, they'll be taken to the corresponding page — no event listeners required.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item href="https://example.com/home">Homepage</sl-breadcrumb-item>
|
||||
|
||||
@@ -46,7 +49,7 @@ For websites, you'll probably want to use links instead. You can make any breadc
|
||||
</sl-breadcrumb>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlBreadcrumb, SlBreadcrumbItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -66,7 +69,7 @@ const App = () => (
|
||||
|
||||
Use the `separator` slot to change the separator that goes between breadcrumb items. Icons work well, but you can also use text or an image.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-breadcrumb>
|
||||
<sl-icon name="dot" slot="separator"></sl-icon>
|
||||
<sl-breadcrumb-item>First</sl-breadcrumb-item>
|
||||
@@ -93,7 +96,7 @@ Use the `separator` slot to change the separator that goes between breadcrumb it
|
||||
</sl-breadcrumb>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import '@shoelace-style/shoelace/dist/components/icon/icon.js';
|
||||
import { SlBreadcrumb, SlBreadcrumbItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -131,7 +134,7 @@ const App = () => (
|
||||
|
||||
Use the `prefix` slot to add content before any breadcrumb item.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item>
|
||||
<sl-icon slot="prefix" name="house"></sl-icon>
|
||||
@@ -142,7 +145,7 @@ Use the `prefix` slot to add content before any breadcrumb item.
|
||||
</sl-breadcrumb>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlBreadcrumb, SlBreadcrumbItem, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -161,7 +164,7 @@ const App = () => (
|
||||
|
||||
Use the `suffix` slot to add content after any breadcrumb item.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item>Documents</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>Policies</sl-breadcrumb-item>
|
||||
@@ -172,7 +175,7 @@ Use the `suffix` slot to add content after any breadcrumb item.
|
||||
</sl-breadcrumb>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlBreadcrumb, SlBreadcrumbItem, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -191,7 +194,7 @@ const App = () => (
|
||||
|
||||
Dropdown menus can be placed in a prefix or suffix slot to provide additional options.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item>Homepage</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>Our Services</sl-breadcrumb-item>
|
||||
@@ -212,7 +215,7 @@ Dropdown menus can be placed in a prefix or suffix slot to provide additional op
|
||||
</sl-breadcrumb>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import {
|
||||
SlBreadcrumb,
|
||||
SlBreadcrumbItem,
|
||||
@@ -246,5 +249,3 @@ const App = () => (
|
||||
</SlBreadcrumb>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-breadcrumb]
|
||||
@@ -1,8 +1,11 @@
|
||||
# Button Group
|
||||
---
|
||||
meta:
|
||||
title: Button Group
|
||||
description: Button groups can be used to group related buttons into sections.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-button-group]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button-group label="Alignment">
|
||||
<sl-button>Left</sl-button>
|
||||
<sl-button>Center</sl-button>
|
||||
@@ -10,7 +13,7 @@
|
||||
</sl-button-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlButtonGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -28,7 +31,7 @@ const App = () => (
|
||||
|
||||
All button sizes are supported, but avoid mixing sizes within the same button group.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button-group label="Alignment">
|
||||
<sl-button size="small">Left</sl-button>
|
||||
<sl-button size="small">Center</sl-button>
|
||||
@@ -52,7 +55,7 @@ All button sizes are supported, but avoid mixing sizes within the same button gr
|
||||
</sl-button-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlButtonGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -88,7 +91,7 @@ const App = () => (
|
||||
|
||||
Theme buttons are supported through the button's `variant` attribute.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button-group label="Alignment">
|
||||
<sl-button variant="primary">Left</sl-button>
|
||||
<sl-button variant="primary">Center</sl-button>
|
||||
@@ -128,7 +131,7 @@ Theme buttons are supported through the button's `variant` attribute.
|
||||
</sl-button-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlButtonGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -182,7 +185,7 @@ const App = () => (
|
||||
|
||||
Pill buttons are supported through the button's `pill` attribute.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button-group label="Alignment">
|
||||
<sl-button size="small" pill>Left</sl-button>
|
||||
<sl-button size="small" pill>Center</sl-button>
|
||||
@@ -206,7 +209,7 @@ Pill buttons are supported through the button's `pill` attribute.
|
||||
</sl-button-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlButtonGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -260,7 +263,7 @@ const App = () => (
|
||||
|
||||
Dropdowns can be placed inside button groups as long as the trigger is an `<sl-button>` element.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button-group label="Example Button Group">
|
||||
<sl-button>Button</sl-button>
|
||||
<sl-button>Button</sl-button>
|
||||
@@ -275,7 +278,7 @@ Dropdowns can be placed inside button groups as long as the trigger is an `<sl-b
|
||||
</sl-button-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlButtonGroup, SlDropdown, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -300,7 +303,7 @@ const App = () => (
|
||||
|
||||
Create a split button using a button and a dropdown. Use a [visually hidden](/components/visually-hidden) label to ensure the dropdown is accessible to users with assistive devices.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button-group label="Example Button Group">
|
||||
<sl-button variant="primary">Save</sl-button>
|
||||
<sl-dropdown placement="bottom-end">
|
||||
@@ -316,7 +319,7 @@ Create a split button using a button and a dropdown. Use a [visually hidden](/co
|
||||
</sl-button-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlButtonGroup, SlDropdown, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -338,7 +341,7 @@ const App = () => (
|
||||
|
||||
Buttons can be wrapped in tooltips to provide more detail when the user interacts with them.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button-group label="Alignment">
|
||||
<sl-tooltip content="I'm on the left">
|
||||
<sl-button>Left</sl-button>
|
||||
@@ -354,7 +357,7 @@ Buttons can be wrapped in tooltips to provide more detail when the user interact
|
||||
</sl-button-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlButtonGroup, SlTooltip } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -380,7 +383,7 @@ const App = () => (
|
||||
|
||||
Create interactive toolbars with button groups.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="button-group-toolbar">
|
||||
<sl-button-group label="History">
|
||||
<sl-tooltip content="Undo">
|
||||
@@ -423,7 +426,7 @@ Create interactive toolbars with button groups.
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlButtonGroup, SlIcon, SlTooltip } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -489,5 +492,3 @@ const App = () => (
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-button-group]
|
||||
@@ -1,12 +1,15 @@
|
||||
# Button
|
||||
---
|
||||
meta:
|
||||
title: Button
|
||||
description: Buttons represent actions that are available to the user.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-button]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button>Button</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlButton>Button</SlButton>;
|
||||
@@ -18,7 +21,7 @@ const App = () => <SlButton>Button</SlButton>;
|
||||
|
||||
Use the `variant` attribute to set the button's variant.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button variant="default">Default</sl-button>
|
||||
<sl-button variant="primary">Primary</sl-button>
|
||||
<sl-button variant="success">Success</sl-button>
|
||||
@@ -27,7 +30,7 @@ Use the `variant` attribute to set the button's variant.
|
||||
<sl-button variant="danger">Danger</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -46,13 +49,13 @@ const App = () => (
|
||||
|
||||
Use the `size` attribute to change a button's size.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button size="small">Small</sl-button>
|
||||
<sl-button size="medium">Medium</sl-button>
|
||||
<sl-button size="large">Large</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -68,7 +71,7 @@ const App = () => (
|
||||
|
||||
Use the `outline` attribute to draw outlined buttons with transparent backgrounds.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button variant="default" outline>Default</sl-button>
|
||||
<sl-button variant="primary" outline>Primary</sl-button>
|
||||
<sl-button variant="success" outline>Success</sl-button>
|
||||
@@ -77,7 +80,7 @@ Use the `outline` attribute to draw outlined buttons with transparent background
|
||||
<sl-button variant="danger" outline>Danger</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -108,13 +111,13 @@ const App = () => (
|
||||
|
||||
Use the `pill` attribute to give buttons rounded edges.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button size="small" pill>Small</sl-button>
|
||||
<sl-button size="medium" pill>Medium</sl-button>
|
||||
<sl-button size="large" pill>Large</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -136,7 +139,7 @@ const App = () => (
|
||||
|
||||
Use the `circle` attribute to create circular icon buttons. When this attribute is set, the button expects a single `<sl-icon>` in the default slot.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button variant="default" size="small" circle>
|
||||
<sl-icon name="gear" label="Settings"></sl-icon>
|
||||
</sl-button>
|
||||
@@ -150,7 +153,7 @@ Use the `circle` attribute to create circular icon buttons. When this attribute
|
||||
</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -172,13 +175,13 @@ const App = () => (
|
||||
|
||||
Use the `text` variant to create text buttons that share the same size as regular buttons but don't have backgrounds or borders.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button variant="text" size="small">Text</sl-button>
|
||||
<sl-button variant="text" size="medium">Text</sl-button>
|
||||
<sl-button variant="text" size="large">Text</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -198,16 +201,16 @@ const App = () => (
|
||||
|
||||
### Link Buttons
|
||||
|
||||
It's often helpful to have a button that works like a link. This is possible by setting the `href` attribute, which will make the component render an `<a>` under the hood. This gives you all the default link behavior the browser provides (e.g. <kbd>CMD/CTRL/SHIFT + CLICK</kbd>) and exposes the `target` and `download` attributes.
|
||||
It's often helpful to have a button that works like a link. This is possible by setting the `href` attribute, which will make the component render an `<a>` under the hood. This gives you all the default link behavior the browser provides (e.g. [[CMD/CTRL/SHIFT]] + [[CLICK]]) and exposes the `target` and `download` attributes.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button href="https://example.com/">Link</sl-button>
|
||||
<sl-button href="https://example.com/" target="_blank">New Window</sl-button>
|
||||
<sl-button href="/assets/images/wordmark.svg" download="shoelace.svg">Download</sl-button>
|
||||
<sl-button href="https://example.com/" disabled>Disabled</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -226,19 +229,23 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
?> When a `target` is set, the link will receive `rel="noreferrer noopener"` for [security reasons](https://mathiasbynens.github.io/rel-noopener/).
|
||||
:::tip
|
||||
When a `target` is set, the link will receive `rel="noreferrer noopener"` for [security reasons](https://mathiasbynens.github.io/rel-noopener/).
|
||||
:::
|
||||
|
||||
### Setting a Custom Width
|
||||
|
||||
As expected, buttons can be given a custom width by setting the `width` attribute. This is useful for making buttons span the full width of their container on smaller screens.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button variant="default" size="small" style="width: 100%; margin-bottom: 1rem;">Small</sl-button>
|
||||
<sl-button variant="default" size="medium" style="width: 100%; margin-bottom: 1rem;">Medium</sl-button>
|
||||
<sl-button variant="default" size="large" style="width: 100%;">Large</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -256,11 +263,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Prefix and Suffix Icons
|
||||
|
||||
Use the `prefix` and `suffix` slots to add icons.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button variant="default" size="small">
|
||||
<sl-icon slot="prefix" name="gear"></sl-icon>
|
||||
Settings
|
||||
@@ -314,7 +323,7 @@ Use the `prefix` and `suffix` slots to add icons.
|
||||
</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -380,13 +389,13 @@ const App = () => (
|
||||
|
||||
Use the `caret` attribute to add a dropdown indicator when a button will trigger a dropdown, menu, or popover.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button size="small" caret>Small</sl-button>
|
||||
<sl-button size="medium" caret>Medium</sl-button>
|
||||
<sl-button size="large" caret>Large</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -408,7 +417,7 @@ const App = () => (
|
||||
|
||||
Use the `loading` attribute to make a button busy. The width will remain the same as before, preventing adjacent elements from moving around. Clicks will be suppressed until the loading state is removed.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button variant="default" loading>Default</sl-button>
|
||||
<sl-button variant="primary" loading>Primary</sl-button>
|
||||
<sl-button variant="success" loading>Success</sl-button>
|
||||
@@ -417,7 +426,7 @@ Use the `loading` attribute to make a button busy. The width will remain the sam
|
||||
<sl-button variant="danger" loading>Danger</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -448,7 +457,7 @@ const App = () => (
|
||||
|
||||
Use the `disabled` attribute to disable a button. Clicks will be suppressed until the disabled state is removed.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button variant="default" disabled>Default</sl-button>
|
||||
<sl-button variant="primary" disabled>Primary</sl-button>
|
||||
<sl-button variant="success" disabled>Success</sl-button>
|
||||
@@ -457,7 +466,7 @@ Use the `disabled` attribute to disable a button. Clicks will be suppressed unti
|
||||
<sl-button variant="danger" disabled>Danger</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -493,7 +502,7 @@ const App = () => (
|
||||
|
||||
This example demonstrates how to style buttons using a custom class. This is the recommended approach if you need to add additional variations. To customize an existing variation, modify the selector to target the button's `variant` attribute instead of a class (e.g. `sl-button[variant="primary"]`).
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button class="pink">Pink Button</sl-button>
|
||||
|
||||
<style>
|
||||
@@ -532,5 +541,3 @@ This example demonstrates how to style buttons using a custom class. This is the
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
[component-metadata:sl-button]
|
||||
@@ -1,8 +1,11 @@
|
||||
# Card
|
||||
---
|
||||
meta:
|
||||
title: Card
|
||||
description: Cards can be used to group related subjects in a container.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-card]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-card class="card-overview">
|
||||
<img
|
||||
slot="image"
|
||||
@@ -37,7 +40,7 @@
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlCard, SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -50,8 +53,8 @@ const css = `
|
||||
}
|
||||
|
||||
.card-overview [slot="footer"] {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
@@ -84,11 +87,11 @@ const App = () => (
|
||||
|
||||
## Examples
|
||||
|
||||
## Basic Card
|
||||
### Basic Card
|
||||
|
||||
Basic cards aren't very exciting, but they can display any content you want them to.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-card class="card-basic">
|
||||
This is just a basic card. No image, no header, and no footer. Just your content.
|
||||
</sl-card>
|
||||
@@ -100,7 +103,7 @@ Basic cards aren't very exciting, but they can display any content you want them
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlCard } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -120,11 +123,11 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
## Card with Header
|
||||
### Card with Header
|
||||
|
||||
Headers can be used to display titles and more.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-card class="card-header">
|
||||
<div slot="header">
|
||||
Header Title
|
||||
@@ -155,7 +158,7 @@ Headers can be used to display titles and more.
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlCard, SlIconButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -164,8 +167,8 @@ const css = `
|
||||
}
|
||||
|
||||
.card-header [slot="header"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@@ -193,11 +196,11 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
## Card with Footer
|
||||
### Card with Footer
|
||||
|
||||
Footers can be used to display actions, summaries, or other relevant content.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-card class="card-footer">
|
||||
This card has a footer. You can put all sorts of things in it!
|
||||
|
||||
@@ -220,7 +223,7 @@ Footers can be used to display actions, summaries, or other relevant content.
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlCard, SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -229,8 +232,8 @@ const css = `
|
||||
}
|
||||
|
||||
.card-footer [slot="footer"] {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
@@ -252,11 +255,11 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
## Images
|
||||
### Images
|
||||
|
||||
Cards accept an `image` slot. The image is displayed atop the card and stretches to fit.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-card class="card-image">
|
||||
<img
|
||||
slot="image"
|
||||
@@ -273,7 +276,7 @@ Cards accept an `image` slot. The image is displayed atop the card and stretches
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlCard } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -297,5 +300,3 @@ const App = () => (
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-card]
|
||||
@@ -1,8 +1,11 @@
|
||||
# Carousel Item
|
||||
---
|
||||
meta:
|
||||
title: Carousel Item
|
||||
description: A carousel item represent a slide within a carousel.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-carousel-item]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-carousel pagination>
|
||||
<sl-carousel-item>
|
||||
<img
|
||||
@@ -37,7 +40,7 @@
|
||||
</sl-carousel>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -76,6 +79,6 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
?> Additional demonstrations can be found in the [carousel examples](/components/carousel).
|
||||
|
||||
[component-metadata:sl-carousel-item]
|
||||
:::tip
|
||||
Additional demonstrations can be found in the [carousel examples](/components/carousel).
|
||||
:::
|
||||
@@ -1,8 +1,11 @@
|
||||
# Carousel
|
||||
---
|
||||
meta:
|
||||
title: Carousel
|
||||
description: Carousels display an arbitrary number of content slides along a horizontal or vertical axis.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-carousel]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-carousel pagination navigation mouse-dragging loop>
|
||||
<sl-carousel-item>
|
||||
<img
|
||||
@@ -37,7 +40,7 @@
|
||||
</sl-carousel>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -84,7 +87,7 @@ const App = () => (
|
||||
|
||||
Use the `pagination` attribute to show the total number of slides and the current slide as a set of interactive dots.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-carousel pagination>
|
||||
<sl-carousel-item>
|
||||
<img
|
||||
@@ -119,7 +122,7 @@ Use the `pagination` attribute to show the total number of slides and the curren
|
||||
</sl-carousel>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -162,7 +165,7 @@ const App = () => (
|
||||
|
||||
Use the `navigation` attribute to show previous and next buttons.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-carousel navigation>
|
||||
<sl-carousel-item>
|
||||
<img
|
||||
@@ -197,7 +200,7 @@ Use the `navigation` attribute to show previous and next buttons.
|
||||
</sl-carousel>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -240,7 +243,7 @@ const App = () => (
|
||||
|
||||
By default, the carousel will not advanced beyond the first and last slides. You can change this behavior and force the carousel to "wrap" with the `loop` attribute.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-carousel loop navigation pagination>
|
||||
<sl-carousel-item>
|
||||
<img
|
||||
@@ -275,7 +278,7 @@ By default, the carousel will not advanced beyond the first and last slides. You
|
||||
</sl-carousel>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -318,7 +321,7 @@ const App = () => (
|
||||
|
||||
The carousel will automatically advance when the `autoplay` attribute is used. To change how long a slide is shown before advancing, set `autoplay-interval` to the desired number of milliseconds. For best results, use the `loop` attribute when autoplay is enabled. Note that autoplay will pause while the user interacts with the carousel.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-carousel autoplay loop pagination>
|
||||
<sl-carousel-item>
|
||||
<img
|
||||
@@ -353,7 +356,7 @@ The carousel will automatically advance when the `autoplay` attribute is used. T
|
||||
</sl-carousel>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -398,7 +401,7 @@ The carousel uses [scroll snap](https://developer.mozilla.org/en-US/docs/Web/CSS
|
||||
|
||||
This example is best demonstrated using a mouse. Try clicking and dragging the slide to move it. Then toggle the switch and try again.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="mouse-dragging">
|
||||
<sl-carousel pagination>
|
||||
<sl-carousel-item>
|
||||
@@ -449,7 +452,7 @@ This example is best demonstrated using a mouse. Try clicking and dragging the s
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlCarousel, SlCarouselItem, SlDivider, SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -503,9 +506,9 @@ const App = () => {
|
||||
|
||||
### Multiple Slides Per View
|
||||
|
||||
The `slides-per-view` attribute makes it possible to display multiple slides at a time. You can also use the `slides-per-move` attribute to advance more than once slide at a time, if desired.
|
||||
The `slides-per-page` attribute makes it possible to display multiple slides at a time. You can also use the `slides-per-move` attribute to advance more than once slide at a time, if desired.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-carousel navigation pagination slides-per-page="2" slides-per-move="2">
|
||||
<sl-carousel-item style="background: var(--sl-color-red-200);">Slide 1</sl-carousel-item>
|
||||
<sl-carousel-item style="background: var(--sl-color-orange-200);">Slide 2</sl-carousel-item>
|
||||
@@ -516,7 +519,9 @@ The `slides-per-view` attribute makes it possible to display multiple slides at
|
||||
</sl-carousel>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -531,11 +536,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Adding and Removing Slides
|
||||
|
||||
The content of the carousel can be changed by appending or removing carousel items. The carousel will update itself automatically.
|
||||
The content of the carousel can be changed by adding or removing carousel items. The carousel will update itself automatically.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-carousel class="dynamic-carousel" pagination navigation>
|
||||
<sl-carousel-item style="background: var(--sl-color-red-200)">Slide 1</sl-carousel-item>
|
||||
<sl-carousel-item style="background: var(--sl-color-orange-200)">Slide 2</sl-carousel-item>
|
||||
@@ -582,12 +589,19 @@ The content of the carousel can be changed by appending or removing carousel ite
|
||||
slide.innerText = `Slide ${dynamicCarousel.children.length + 1}`;
|
||||
slide.style.setProperty('background', `var(--sl-color-${color}-200)`);
|
||||
dynamicCarousel.appendChild(slide);
|
||||
dynamicRemove.disabled = false;
|
||||
};
|
||||
|
||||
const removeSlide = () => {
|
||||
const slide = dynamicCarousel.children[dynamicCarousel.children.length - 1];
|
||||
slide.remove();
|
||||
colorIndex--;
|
||||
const numSlides = dynamicCarousel.querySelectorAll('sl-carousel-item').length;
|
||||
|
||||
if (numSlides > 1) {
|
||||
slide.remove();
|
||||
colorIndex--;
|
||||
}
|
||||
|
||||
dynamicRemove.disabled = numSlides - 1 <= 1;
|
||||
};
|
||||
|
||||
dynamicAdd.addEventListener('click', addSlide);
|
||||
@@ -596,7 +610,9 @@ The content of the carousel can be changed by appending or removing carousel ite
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -654,13 +670,13 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Vertical Scrolling
|
||||
|
||||
Setting the `orientation` attribute to `vertical`, will make the carousel laying out vertically, making it
|
||||
possible for the user to scroll it up and down. In case of heterogeneous content, for example images of different sizes,
|
||||
it's important to specify a predefined height to the carousel through CSS.
|
||||
Setting the `orientation` attribute to `vertical` will render the carousel in a vertical layout. If the content of your slides vary in height, you will need to set amn explicit `height` or `max-height` on the carousel using CSS.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-carousel class="vertical" pagination orientation="vertical">
|
||||
<sl-carousel-item>
|
||||
<img
|
||||
@@ -713,7 +729,7 @@ it's important to specify a predefined height to the carousel through CSS.
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlCarousel, SlCarouselItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -778,7 +794,7 @@ const App = () => (
|
||||
|
||||
Use the `--aspect-ratio` custom property to customize the size of the carousel's viewport.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-carousel class="aspect-ratio" navigation pagination style="--aspect-ratio: 3/2;">
|
||||
<sl-carousel-item>
|
||||
<img
|
||||
@@ -832,7 +848,9 @@ Use the `--aspect-ratio` custom property to customize the size of the carousel's
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlCarousel, SlCarouselItem, SlDivider, SlSelect, SlOption } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -893,11 +911,13 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Scroll Hint
|
||||
|
||||
Use the `--scroll-hint` attribute to add inline padding in horizontal carousels and block padding in vertical carousels. Setting a padding will make the closest slides slightly visible, hinting that there are more items in the carousel.
|
||||
Use the `--scroll-hint` custom property to add inline padding in horizontal carousels and block padding in vertical carousels. This will make the closest slides slightly visible, hinting that there are more items in the carousel.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-carousel class="scroll-hint" pagination style="--scroll-hint: 10%;">
|
||||
<sl-carousel-item>
|
||||
<img
|
||||
@@ -932,7 +952,9 @@ Use the `--scroll-hint` attribute to add inline padding in horizontal carousels
|
||||
</sl-carousel>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlCarousel, SlCarouselItem, SlDivider, SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -974,11 +996,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Gallery Example
|
||||
|
||||
The carousel has a robust API that makes it possible to extend and customize. This example syncs the active slide with a set of thumbnails, effectively creating a gallery-style carousel.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-carousel class="carousel-thumbnails" navigation loop>
|
||||
<sl-carousel-item>
|
||||
<img
|
||||
@@ -1093,7 +1117,7 @@ The carousel has a robust API that makes it possible to extend and customize. Th
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useRef } from 'react';
|
||||
import { SlCarousel, SlCarouselItem, SlDivider, SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -1212,5 +1236,3 @@ const App = () => {
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
[component-metadata:sl-carousel]
|
||||
@@ -1,18 +1,23 @@
|
||||
# Checkbox
|
||||
---
|
||||
meta:
|
||||
title: Checkbox
|
||||
description: Checkboxes allow the user to toggle an option on or off.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-checkbox]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-checkbox>Checkbox</sl-checkbox>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlCheckbox } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlCheckbox>Checkbox</SlCheckbox>;
|
||||
```
|
||||
|
||||
?> This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
:::tip
|
||||
This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -20,11 +25,11 @@ const App = () => <SlCheckbox>Checkbox</SlCheckbox>;
|
||||
|
||||
Use the `checked` attribute to activate the checkbox.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-checkbox checked>Checked</sl-checkbox>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlCheckbox } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlCheckbox checked>Checked</SlCheckbox>;
|
||||
@@ -34,11 +39,11 @@ const App = () => <SlCheckbox checked>Checked</SlCheckbox>;
|
||||
|
||||
Use the `indeterminate` attribute to make the checkbox indeterminate.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-checkbox indeterminate>Indeterminate</sl-checkbox>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlCheckbox } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlCheckbox indeterminate>Indeterminate</SlCheckbox>;
|
||||
@@ -48,21 +53,21 @@ const App = () => <SlCheckbox indeterminate>Indeterminate</SlCheckbox>;
|
||||
|
||||
Use the `disabled` attribute to disable the checkbox.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-checkbox disabled>Disabled</sl-checkbox>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlCheckbox } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlCheckbox disabled>Disabled</SlCheckbox>;
|
||||
```
|
||||
|
||||
## Sizes
|
||||
### Sizes
|
||||
|
||||
Use the `size` attribute to change a checkbox's size.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-checkbox size="small">Small</sl-checkbox>
|
||||
<br />
|
||||
<sl-checkbox size="medium">Medium</sl-checkbox>
|
||||
@@ -70,7 +75,7 @@ Use the `size` attribute to change a checkbox's size.
|
||||
<sl-checkbox size="large">Large</sl-checkbox>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlCheckbox } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -88,7 +93,7 @@ const App = () => (
|
||||
|
||||
Use the `setCustomValidity()` method to set a custom validation message. This will prevent the form from submitting and make the browser display the error message you provide. To clear the error, call this function with an empty string.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<form class="custom-validity">
|
||||
<sl-checkbox>Check me</sl-checkbox>
|
||||
<br />
|
||||
@@ -100,7 +105,8 @@ Use the `setCustomValidity()` method to set a custom validation message. This wi
|
||||
const errorMessage = `Don't forget to check me!`;
|
||||
|
||||
// Set initial validity as soon as the element is defined
|
||||
customElements.whenDefined('sl-checkbox').then(() => {
|
||||
customElements.whenDefined('sl-checkbox').then(async () => {
|
||||
await checkbox.updateComplete;
|
||||
checkbox.setCustomValidity(errorMessage);
|
||||
});
|
||||
|
||||
@@ -117,7 +123,9 @@ Use the `setCustomValidity()` method to set a custom validation message. This wi
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { SlButton, SlCheckbox } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -152,4 +160,4 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
[component-metadata:sl-checkbox]
|
||||
{% endraw %}
|
||||
@@ -1,18 +1,23 @@
|
||||
# Color Picker
|
||||
---
|
||||
meta:
|
||||
title: Color Picker
|
||||
description: Color pickers allow the user to select a color.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-color-picker]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-color-picker label="Select a color"></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlColorPicker label="Select a color" />;
|
||||
```
|
||||
|
||||
?> This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
:::tip
|
||||
This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -20,11 +25,11 @@ const App = () => <SlColorPicker label="Select a color" />;
|
||||
|
||||
Use the `value` attribute to set an initial value for the color picker.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-color-picker value="#4a90e2" label="Select a color"></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlColorPicker value="#4a90e2" label="Select a color" />;
|
||||
@@ -34,11 +39,11 @@ const App = () => <SlColorPicker value="#4a90e2" label="Select a color" />;
|
||||
|
||||
Use the `opacity` attribute to enable the opacity slider. When this is enabled, the value will be displayed as HEXA, RGBA, HSLA, or HSVA based on `format`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-color-picker value="#f5a623ff" opacity label="Select a color"></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlColorPicker opacity label="Select a color" />;
|
||||
@@ -50,14 +55,14 @@ Set the color picker's format with the `format` attribute. Valid options include
|
||||
|
||||
To prevent users from toggling the format themselves, add the `no-format-toggle` attribute.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-color-picker format="hex" value="#4a90e2" label="Select a color"></sl-color-picker>
|
||||
<sl-color-picker format="rgb" value="rgb(80, 227, 194)" label="Select a color"></sl-color-picker>
|
||||
<sl-color-picker format="hsl" value="hsl(290, 87%, 47%)" label="Select a color"></sl-color-picker>
|
||||
<sl-color-picker format="hsv" value="hsv(55, 89%, 97%)" label="Select a color"></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -74,24 +79,24 @@ const App = () => (
|
||||
|
||||
Use the `swatches` attribute to add convenient presets to the color picker. Any format the color picker can parse is acceptable (including CSS color names), but each value must be separated by a semicolon (`;`). Alternatively, you can pass an array of color values to this property using JavaScript.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-color-picker
|
||||
label="Select a color"
|
||||
swatches="
|
||||
#d0021b; #f5a623; #f8e71c; #8b572a; #7ed321; #417505; #bd10e0; #9013fe;
|
||||
#d0021b; #f5a623; #f8e71c; #8b572a; #7ed321; #417505; #bd10e0; #9013fe;
|
||||
#4a90e2; #50e3c2; #b8e986; #000; #444; #888; #ccc; #fff;
|
||||
"
|
||||
></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlColorPicker
|
||||
label="Select a color"
|
||||
swatches="
|
||||
#d0021b; #f5a623; #f8e71c; #8b572a; #7ed321; #417505; #bd10e0; #9013fe;
|
||||
#d0021b; #f5a623; #f8e71c; #8b572a; #7ed321; #417505; #bd10e0; #9013fe;
|
||||
#4a90e2; #50e3c2; #b8e986; #000; #444; #888; #ccc; #fff;
|
||||
"
|
||||
/>
|
||||
@@ -102,13 +107,13 @@ const App = () => (
|
||||
|
||||
Use the `size` attribute to change the color picker's trigger size.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-color-picker size="small" label="Select a color"></sl-color-picker>
|
||||
<sl-color-picker size="medium" label="Select a color"></sl-color-picker>
|
||||
<sl-color-picker size="large" label="Select a color"></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -124,14 +129,12 @@ const App = () => (
|
||||
|
||||
The color picker can be rendered inline instead of in a dropdown using the `inline` attribute.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-color-picker inline label="Select a color"></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlColorPicker inline label="Select a color" />;
|
||||
```
|
||||
|
||||
[component-metadata:sl-color-picker]
|
||||
@@ -1,17 +1,20 @@
|
||||
---
|
||||
meta:
|
||||
title: Details
|
||||
description: Details show a brief summary and expand to show additional content.
|
||||
layout: component
|
||||
---
|
||||
|
||||
<!-- cspell:dictionaries lorem-ipsum -->
|
||||
|
||||
# Details
|
||||
|
||||
[component-header:sl-details]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-details summary="Toggle Me">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
</sl-details>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlDetails } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -28,14 +31,14 @@ const App = () => (
|
||||
|
||||
Use the `disable` attribute to prevent the details from expanding.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-details summary="Disabled" disabled>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
</sl-details>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlDetails } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -50,7 +53,7 @@ const App = () => (
|
||||
|
||||
Use the `expand-icon` and `collapse-icon` slots to change the expand and collapse icons, respectively. To disable the animation, override the `rotate` property on the `summary-icon` part as shown below.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-details summary="Toggle Me" class="custom-icons">
|
||||
<sl-icon name="plus-square" slot="expand-icon"></sl-icon>
|
||||
<sl-icon name="dash-square" slot="collapse-icon"></sl-icon>
|
||||
@@ -67,7 +70,7 @@ Use the `expand-icon` and `collapse-icon` slots to change the expand and collaps
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlDetails, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -96,7 +99,7 @@ const App = () => (
|
||||
|
||||
Details are designed to function independently, but you can simulate a group or "accordion" where only one is shown at a time by listening for the `sl-show` event.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="details-group-example">
|
||||
<sl-details summary="First" open>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
@@ -129,5 +132,3 @@ Details are designed to function independently, but you can simulate a group or
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
[component-metadata:sl-details]
|
||||
@@ -1,10 +1,13 @@
|
||||
---
|
||||
meta:
|
||||
title: Dialog
|
||||
description: 'Dialogs, sometimes called "modals", appear above the page and require the user''s immediate attention.'
|
||||
layout: component
|
||||
---
|
||||
|
||||
<!-- cspell:dictionaries lorem-ipsum -->
|
||||
|
||||
# Dialog
|
||||
|
||||
[component-header:sl-dialog]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-dialog label="Dialog" class="dialog-overview">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||
@@ -22,7 +25,7 @@
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDialog } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -44,19 +47,13 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
## UX Tips
|
||||
|
||||
- Use a dialog when you immediately require the user's attention, e.g. confirming a destructive action.
|
||||
- Always provide an obvious way for the user to dismiss the dialog.
|
||||
- Don't nest dialogs. It almost always leads to a poor experience for the user.
|
||||
|
||||
## Examples
|
||||
|
||||
### Custom Width
|
||||
|
||||
Use the `--width` custom property to set the dialog's width.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-dialog label="Dialog" class="dialog-width" style="--width: 50vw;">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||
@@ -74,7 +71,9 @@ Use the `--width` custom property to set the dialog's width.
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDialog } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -96,11 +95,13 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Scrolling
|
||||
|
||||
By design, a dialog's height will never exceed that of the viewport. As such, dialogs will not scroll with the page ensuring the header and footer are always accessible to the user.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-dialog label="Dialog" class="dialog-scrolling">
|
||||
<div style="height: 150vh; border: dashed 2px var(--sl-color-neutral-200); padding: 0 1rem;">
|
||||
<p>Scroll down and give it a try! 👇</p>
|
||||
@@ -120,7 +121,9 @@ By design, a dialog's height will never exceed that of the viewport. As such, di
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDialog } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -151,11 +154,13 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Header Actions
|
||||
|
||||
The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/components/icon-button) if needed.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-dialog label="Dialog" class="dialog-header-actions">
|
||||
<sl-icon-button class="new-window" slot="header-actions" name="box-arrow-up-right"></sl-icon-button>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
@@ -176,7 +181,7 @@ The header shows a functional close button by default. You can use the `header-a
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDialog, SlIconButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -206,13 +211,13 @@ const App = () => {
|
||||
|
||||
### Preventing the Dialog from Closing
|
||||
|
||||
By default, dialogs will close when the user clicks the close button, clicks the overlay, or presses the <kbd>Escape</kbd> key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur.
|
||||
By default, dialogs will close when the user clicks the close button, clicks the overlay, or presses the [[Escape]] key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur.
|
||||
|
||||
To keep the dialog open in such cases, you can cancel the `sl-request-close` event. When canceled, the dialog will remain open and pulse briefly to draw the user's attention to it.
|
||||
|
||||
You can use `event.detail.source` to determine what triggered the request to close. This example prevents the dialog from closing when the overlay is clicked, but allows the close button or <kbd>Escape</kbd> to dismiss it.
|
||||
You can use `event.detail.source` to determine what triggered the request to close. This example prevents the dialog from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-dialog label="Dialog" class="dialog-deny-close">
|
||||
This dialog will not close when you click on the overlay.
|
||||
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||
@@ -237,7 +242,7 @@ You can use `event.detail.source` to determine what triggered the request to clo
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDialog } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -270,7 +275,7 @@ const App = () => {
|
||||
|
||||
By default, the dialog's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the dialog. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-dialog label="Dialog" class="dialog-focus">
|
||||
<sl-input autofocus placeholder="I will have focus when the dialog is opened"></sl-input>
|
||||
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||
@@ -289,7 +294,7 @@ By default, the dialog's panel will gain focus when opened. This allows a subseq
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDialog, SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -311,6 +316,6 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
?> You can further customize initial focus behavior by canceling the `sl-initial-focus` event and setting focus yourself inside the event handler.
|
||||
|
||||
[component-metadata:sl-dialog]
|
||||
:::tip
|
||||
You can further customize initial focus behavior by canceling the `sl-initial-focus` event and setting focus yourself inside the event handler.
|
||||
:::
|
||||
@@ -1,12 +1,15 @@
|
||||
# Divider
|
||||
---
|
||||
meta:
|
||||
title: Divider
|
||||
description: Dividers are used to visually separate or group elements.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-divider]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-divider></sl-divider>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlDivider } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlDivider />;
|
||||
@@ -18,35 +21,43 @@ const App = () => <SlDivider />;
|
||||
|
||||
Use the `--width` custom property to change the width of the divider.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-divider style="--width: 4px;"></sl-divider>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlDivider } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlDivider style={{ '--width': '4px' }} />;
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Color
|
||||
|
||||
Use the `--color` custom property to change the color of the divider.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-divider style="--color: tomato;"></sl-divider>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlDivider } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlDivider style={{ '--color': 'tomato' }} />;
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Spacing
|
||||
|
||||
Use the `--spacing` custom property to change the amount of space between the divider and it's neighboring elements.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div style="text-align: center;">
|
||||
Above
|
||||
<sl-divider style="--spacing: 2rem;"></sl-divider>
|
||||
@@ -54,7 +65,9 @@ Use the `--spacing` custom property to change the amount of space between the di
|
||||
</div>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlDivider } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -66,11 +79,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Vertical
|
||||
|
||||
Add the `vertical` attribute to draw the divider in a vertical orientation. The divider will span the full height of its container. Vertical dividers work especially well inside of a flex container.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div style="display: flex; align-items: center; height: 2rem;">
|
||||
First
|
||||
<sl-divider vertical></sl-divider>
|
||||
@@ -80,7 +95,9 @@ Add the `vertical` attribute to draw the divider in a vertical orientation. The
|
||||
</div>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlDivider } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -100,11 +117,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Menu Dividers
|
||||
|
||||
Use dividers in [menus](/components/menu) to visually group menu items.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-menu style="max-width: 200px;">
|
||||
<sl-menu-item value="1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="2">Option 2</sl-menu-item>
|
||||
@@ -116,7 +135,9 @@ Use dividers in [menus](/components/menu) to visually group menu items.
|
||||
</sl-menu>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlDivider, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -132,4 +153,4 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-divider]
|
||||
{% endraw %}
|
||||
@@ -1,10 +1,13 @@
|
||||
---
|
||||
meta:
|
||||
title: Drawer
|
||||
description: Drawers slide in from a container to expose additional options and information.
|
||||
layout: component
|
||||
---
|
||||
|
||||
<!-- cspell:dictionaries lorem-ipsum -->
|
||||
|
||||
# Drawer
|
||||
|
||||
[component-header:sl-drawer]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-drawer label="Drawer" class="drawer-overview">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||
@@ -22,7 +25,7 @@
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDrawer } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -50,7 +53,7 @@ const App = () => {
|
||||
|
||||
By default, drawers slide in from the end. To make the drawer slide in from the start, set the `placement` attribute to `start`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-drawer label="Drawer" placement="start" class="drawer-placement-start">
|
||||
This drawer slides in from the start.
|
||||
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||
@@ -68,7 +71,7 @@ By default, drawers slide in from the end. To make the drawer slide in from the
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDrawer } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -94,7 +97,7 @@ const App = () => {
|
||||
|
||||
To make the drawer slide in from the top, set the `placement` attribute to `top`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-drawer label="Drawer" placement="top" class="drawer-placement-top">
|
||||
This drawer slides in from the top.
|
||||
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||
@@ -112,7 +115,7 @@ To make the drawer slide in from the top, set the `placement` attribute to `top`
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDrawer } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -138,7 +141,7 @@ const App = () => {
|
||||
|
||||
To make the drawer slide in from the bottom, set the `placement` attribute to `bottom`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-drawer label="Drawer" placement="bottom" class="drawer-placement-bottom">
|
||||
This drawer slides in from the bottom.
|
||||
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||
@@ -156,7 +159,7 @@ To make the drawer slide in from the bottom, set the `placement` attribute to `b
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDrawer } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -182,9 +185,9 @@ const App = () => {
|
||||
|
||||
By default, drawers slide out of their [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport. To make a drawer slide out of a parent element, add the `contained` attribute to the drawer and apply `position: relative` to its parent.
|
||||
|
||||
Unlike normal drawers, contained drawers are not modal. This means they do not show an overlay, they do not trap focus, and they are not dismissible with <kbd>Escape</kbd>. This is intentional to allow users to interact with elements outside of the drawer.
|
||||
Unlike normal drawers, contained drawers are not modal. This means they do not show an overlay, they do not trap focus, and they are not dismissible with [[Escape]]. This is intentional to allow users to interact with elements outside of the drawer.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div
|
||||
style="position: relative; border: solid 2px var(--sl-panel-border-color); height: 300px; padding: 1rem; margin-bottom: 1rem;"
|
||||
>
|
||||
@@ -208,7 +211,9 @@ Unlike normal drawers, contained drawers are not modal. This means they do not s
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDrawer } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -249,11 +254,13 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Custom Size
|
||||
|
||||
Use the `--size` custom property to set the drawer's size. This will be applied to the drawer's width or height depending on its `placement`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-drawer label="Drawer" class="drawer-custom-size" style="--size: 50vw;">
|
||||
This drawer is always 50% of the viewport.
|
||||
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||
@@ -271,7 +278,9 @@ Use the `--size` custom property to set the drawer's size. This will be applied
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDrawer } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -293,11 +302,13 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Scrolling
|
||||
|
||||
By design, a drawer's height will never exceed 100% of its container. As such, drawers will not scroll with the page to ensure the header and footer are always accessible to the user.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-drawer label="Drawer" class="drawer-scrolling">
|
||||
<div style="height: 150vh; border: dashed 2px var(--sl-color-neutral-200); padding: 0 1rem;">
|
||||
<p>Scroll down and give it a try! 👇</p>
|
||||
@@ -317,7 +328,9 @@ By design, a drawer's height will never exceed 100% of its container. As such, d
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDrawer } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -347,11 +360,13 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Header Actions
|
||||
|
||||
The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/components/icon-button) if needed.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-drawer label="Drawer" class="drawer-header-actions">
|
||||
<sl-icon-button class="new-window" slot="header-actions" name="box-arrow-up-right"></sl-icon-button>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
@@ -372,7 +387,7 @@ The header shows a functional close button by default. You can use the `header-a
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDrawer, SlIconButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -397,13 +412,13 @@ const App = () => {
|
||||
|
||||
### Preventing the Drawer from Closing
|
||||
|
||||
By default, drawers will close when the user clicks the close button, clicks the overlay, or presses the <kbd>Escape</kbd> key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur.
|
||||
By default, drawers will close when the user clicks the close button, clicks the overlay, or presses the [[Escape]] key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur.
|
||||
|
||||
To keep the drawer open in such cases, you can cancel the `sl-request-close` event. When canceled, the drawer will remain open and pulse briefly to draw the user's attention to it.
|
||||
|
||||
You can use `event.detail.source` to determine what triggered the request to close. This example prevents the drawer from closing when the overlay is clicked, but allows the close button or <kbd>Escape</kbd> to dismiss it.
|
||||
You can use `event.detail.source` to determine what triggered the request to close. This example prevents the drawer from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-drawer label="Drawer" class="drawer-deny-close">
|
||||
This drawer will not close when you click on the overlay.
|
||||
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||
@@ -428,7 +443,7 @@ You can use `event.detail.source` to determine what triggered the request to clo
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDrawer } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -461,7 +476,7 @@ const App = () => {
|
||||
|
||||
By default, the drawer's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the drawer. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-drawer label="Drawer" class="drawer-focus">
|
||||
<sl-input autofocus placeholder="I will have focus when the drawer is opened"></sl-input>
|
||||
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||
@@ -480,7 +495,7 @@ By default, the drawer's panel will gain focus when opened. This allows a subseq
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDrawer, SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -502,5 +517,6 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
?> You can further customize initial focus behavior by canceling the `sl-initial-focus` event and setting focus yourself inside the event handler.
|
||||
[component-metadata:sl-drawer]
|
||||
:::tip
|
||||
You can further customize initial focus behavior by canceling the `sl-initial-focus` event and setting focus yourself inside the event handler.
|
||||
:::
|
||||
@@ -1,12 +1,15 @@
|
||||
# Dropdown
|
||||
|
||||
[component-header:sl-dropdown]
|
||||
---
|
||||
meta:
|
||||
title: Dropdown
|
||||
description: 'Dropdowns expose additional content that "drops down" in a panel.'
|
||||
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) and [select](/components/select)). The API gives you complete control over showing, hiding, and positioning the panel.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-dropdown>
|
||||
<sl-button slot="trigger" caret>Dropdown</sl-button>
|
||||
<sl-menu>
|
||||
@@ -29,7 +32,7 @@ Dropdowns are designed to work well with [menus](/components/menu) to provide a
|
||||
</sl-dropdown>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlDivider, SlDropdown, SlIcon, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -66,7 +69,7 @@ const App = () => (
|
||||
|
||||
When dropdowns are used with [menus](/components/menu), you can listen for the [`sl-select`](/components/menu#events) event to determine which menu item was selected. The menu item element will be exposed in `event.detail.item`. You can set `value` props to make it easier to identify commands.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="dropdown-selection">
|
||||
<sl-dropdown>
|
||||
<sl-button slot="trigger" caret>Edit</sl-button>
|
||||
@@ -89,7 +92,7 @@ When dropdowns are used with [menus](/components/menu), you can listen for the [
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlDropdown, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => {
|
||||
@@ -115,7 +118,7 @@ const App = () => {
|
||||
|
||||
Alternatively, you can listen for the `click` event on individual menu items. Note that, using this approach, disabled menu items will still emit a `click` event.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="dropdown-selection-alt">
|
||||
<sl-dropdown>
|
||||
<sl-button slot="trigger" caret>Edit</sl-button>
|
||||
@@ -139,7 +142,7 @@ Alternatively, you can listen for the `click` event on individual menu items. No
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlDropdown, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => {
|
||||
@@ -174,7 +177,7 @@ const App = () => {
|
||||
|
||||
The preferred placement of the dropdown can be set with the `placement` attribute. Note that the actual position may vary to ensure the panel remains in the viewport.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-dropdown placement="top-start">
|
||||
<sl-button slot="trigger" caret>Edit</sl-button>
|
||||
<sl-menu>
|
||||
@@ -188,7 +191,7 @@ The preferred placement of the dropdown can be set with the `placement` attribut
|
||||
</sl-dropdown>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlDivider, SlDropdown, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -212,7 +215,7 @@ const App = () => (
|
||||
|
||||
The distance from the panel to the trigger can be customized using the `distance` attribute. This value is specified in pixels.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-dropdown distance="30">
|
||||
<sl-button slot="trigger" caret>Edit</sl-button>
|
||||
<sl-menu>
|
||||
@@ -226,7 +229,7 @@ The distance from the panel to the trigger can be customized using the `distance
|
||||
</sl-dropdown>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlDivider, SlDropdown, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -250,7 +253,7 @@ const App = () => (
|
||||
|
||||
The offset of the panel along the trigger can be customized using the `skidding` attribute. This value is specified in pixels.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-dropdown skidding="30">
|
||||
<sl-button slot="trigger" caret>Edit</sl-button>
|
||||
<sl-menu>
|
||||
@@ -264,7 +267,7 @@ The offset of the panel along the trigger can be customized using the `skidding`
|
||||
</sl-dropdown>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlDivider, SlDropdown, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -286,9 +289,9 @@ const App = () => (
|
||||
|
||||
### 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, 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.
|
||||
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.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="dropdown-hoist">
|
||||
<sl-dropdown>
|
||||
<sl-button slot="trigger" caret>No Hoist</sl-button>
|
||||
@@ -319,7 +322,7 @@ Dropdown panels will be clipped if they're inside a container that has `overflow
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlDivider, SlDropdown, SlIcon, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -360,5 +363,3 @@ const App = () => (
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-dropdown]
|
||||
@@ -1,8 +1,11 @@
|
||||
# Format Bytes
|
||||
---
|
||||
meta:
|
||||
title: Format Bytes
|
||||
description: Formats a number as a human readable bytes value.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-format-bytes]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="format-bytes-overview">
|
||||
The file is <sl-format-bytes value="1000"></sl-format-bytes> in size. <br /><br />
|
||||
<sl-input type="number" value="1000" label="Number to Format" style="max-width: 180px;"></sl-input>
|
||||
@@ -17,7 +20,9 @@
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlFormatBytes, SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -41,20 +46,22 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
## Examples
|
||||
|
||||
### Formatting Bytes
|
||||
|
||||
Set the `value` attribute to a number to get the value in bytes.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-format-bytes value="12"></sl-format-bytes><br />
|
||||
<sl-format-bytes value="1200"></sl-format-bytes><br />
|
||||
<sl-format-bytes value="1200000"></sl-format-bytes><br />
|
||||
<sl-format-bytes value="1200000000"></sl-format-bytes>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlFormatBytes } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -74,14 +81,14 @@ const App = () => (
|
||||
|
||||
To get the value in bits, set the `unit` attribute to `bit`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-format-bytes value="12" unit="bit"></sl-format-bytes><br />
|
||||
<sl-format-bytes value="1200" unit="bit"></sl-format-bytes><br />
|
||||
<sl-format-bytes value="1200000" unit="bit"></sl-format-bytes><br />
|
||||
<sl-format-bytes value="1200000000" unit="bit"></sl-format-bytes>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlFormatBytes } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -101,14 +108,14 @@ const App = () => (
|
||||
|
||||
Use the `lang` attribute to set the number formatting locale.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-format-bytes value="12" lang="de"></sl-format-bytes><br />
|
||||
<sl-format-bytes value="1200" lang="de"></sl-format-bytes><br />
|
||||
<sl-format-bytes value="1200000" lang="de"></sl-format-bytes><br />
|
||||
<sl-format-bytes value="1200000000" lang="de"></sl-format-bytes>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlFormatBytes } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -123,5 +130,3 @@ const App = () => (
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-format-bytes]
|
||||
@@ -1,15 +1,18 @@
|
||||
# Format Date
|
||||
|
||||
[component-header:sl-format-date]
|
||||
---
|
||||
meta:
|
||||
title: Format Date
|
||||
description: Formats a date/time using the specified locale and options.
|
||||
layout: component
|
||||
---
|
||||
|
||||
Localization is handled by the browser's [`Intl.DateTimeFormat` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat). No language packs are required.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<!-- Shoelace 2 release date 🎉 -->
|
||||
<sl-format-date date="2020-07-15T09:17:00-04:00"></sl-format-date>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlFormatDate } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlFormatDate date="2020-07-15T09:17:00-04:00" />;
|
||||
@@ -17,7 +20,9 @@ const App = () => <SlFormatDate date="2020-07-15T09:17:00-04:00" />;
|
||||
|
||||
The `date` attribute determines the date/time to use when formatting. It must be a string that [`Date.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) can interpret or a [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object set via JavaScript. If omitted, the current date/time will be assumed.
|
||||
|
||||
?> When using strings, avoid ambiguous dates such as `03/04/2020` which can be interpreted as March 4 or April 3 depending on the user's browser and locale. Instead, always use a valid [ISO 8601 date time string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse#Date_Time_String_Format) to ensure the date will be parsed properly by all clients.
|
||||
:::tip
|
||||
When using strings, avoid ambiguous dates such as `03/04/2020` which can be interpreted as March 4 or April 3 depending on the user's browser and locale. Instead, always use a valid [ISO 8601 date time string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse#Date_Time_String_Format) to ensure the date will be parsed properly by all clients.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -25,7 +30,7 @@ The `date` attribute determines the date/time to use when formatting. It must be
|
||||
|
||||
Formatting options are based on those found in the [`Intl.DateTimeFormat` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat). When formatting options are provided, the date/time will be formatted according to those values. When no formatting options are provided, a localized, numeric date will be displayed instead.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<!-- Human-readable date -->
|
||||
<sl-format-date month="long" day="numeric" year="numeric"></sl-format-date><br />
|
||||
|
||||
@@ -45,7 +50,7 @@ Formatting options are based on those found in the [`Intl.DateTimeFormat` API](h
|
||||
<sl-format-date></sl-format-date>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlFormatDate } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -80,12 +85,12 @@ const App = () => (
|
||||
|
||||
By default, the browser will determine whether to use 12-hour or 24-hour time. To force one or the other, set the `hour-format` attribute to `12` or `24`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-format-date hour="numeric" minute="numeric" hour-format="12"></sl-format-date><br />
|
||||
<sl-format-date hour="numeric" minute="numeric" hour-format="24"></sl-format-date>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlFormatDate } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -101,13 +106,13 @@ const App = () => (
|
||||
|
||||
Use the `lang` attribute to set the date/time formatting locale.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
English: <sl-format-date lang="en"></sl-format-date><br />
|
||||
French: <sl-format-date lang="fr"></sl-format-date><br />
|
||||
Russian: <sl-format-date lang="ru"></sl-format-date>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlFormatDate } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -120,5 +125,3 @@ const App = () => (
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-format-date]
|
||||
@@ -1,10 +1,13 @@
|
||||
# Format Number
|
||||
|
||||
[component-header:sl-format-number]
|
||||
---
|
||||
meta:
|
||||
title: Format Number
|
||||
description: Formats a number using the specified locale and options.
|
||||
layout: component
|
||||
---
|
||||
|
||||
Localization is handled by the browser's [`Intl.NumberFormat` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat). No language packs are required.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="format-number-overview">
|
||||
<sl-format-number value="1000"></sl-format-number>
|
||||
<br /><br />
|
||||
@@ -20,7 +23,9 @@ Localization is handled by the browser's [`Intl.NumberFormat` API](https://devel
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlFormatNumber, SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -44,13 +49,15 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
## Examples
|
||||
|
||||
### Percentages
|
||||
|
||||
To get the value as a percent, set the `type` attribute to `percent`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-format-number type="percent" value="0"></sl-format-number><br />
|
||||
<sl-format-number type="percent" value="0.25"></sl-format-number><br />
|
||||
<sl-format-number type="percent" value="0.50"></sl-format-number><br />
|
||||
@@ -58,7 +65,7 @@ To get the value as a percent, set the `type` attribute to `percent`.
|
||||
<sl-format-number type="percent" value="1"></sl-format-number>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlFormatNumber } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -80,13 +87,13 @@ const App = () => (
|
||||
|
||||
Use the `lang` attribute to set the number formatting locale.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
English: <sl-format-number value="2000" lang="en" minimum-fraction-digits="2"></sl-format-number><br />
|
||||
German: <sl-format-number value="2000" lang="de" minimum-fraction-digits="2"></sl-format-number><br />
|
||||
Russian: <sl-format-number value="2000" lang="ru" minimum-fraction-digits="2"></sl-format-number>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlFormatNumber } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -104,7 +111,7 @@ const App = () => (
|
||||
|
||||
To format a number as a monetary value, set the `type` attribute to `currency` and set the `currency` attribute to the desired ISO 4217 currency code. You should also specify `lang` to ensure the the number is formatted correctly for the target locale.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-format-number type="currency" currency="USD" value="2000" lang="en-US"></sl-format-number><br />
|
||||
<sl-format-number type="currency" currency="GBP" value="2000" lang="en-GB"></sl-format-number><br />
|
||||
<sl-format-number type="currency" currency="EUR" value="2000" lang="de"></sl-format-number><br />
|
||||
@@ -112,7 +119,7 @@ To format a number as a monetary value, set the `type` attribute to `currency` a
|
||||
<sl-format-number type="currency" currency="CNY" value="2000" lang="zh-cn"></sl-format-number>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlFormatNumber } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -129,5 +136,3 @@ const App = () => (
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-format-number]
|
||||
@@ -1,14 +1,17 @@
|
||||
# Icon Button
|
||||
|
||||
[component-header:sl-icon-button]
|
||||
---
|
||||
meta:
|
||||
title: Icon Button
|
||||
description: Icons buttons are simple, icon-only buttons that can be used for actions and in toolbars.
|
||||
layout: component
|
||||
---
|
||||
|
||||
For a full list of icons that come bundled with Shoelace, refer to the [icon component](/components/icon).
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-icon-button name="gear" label="Settings"></sl-icon-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlIconButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlIconButton name="gear" label="Settings" />;
|
||||
@@ -20,13 +23,15 @@ const App = () => <SlIconButton name="gear" label="Settings" />;
|
||||
|
||||
Icon buttons inherit their parent element's `font-size`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-icon-button name="pencil" label="Edit" style="font-size: 1.5rem;"></sl-icon-button>
|
||||
<sl-icon-button name="pencil" label="Edit" style="font-size: 2rem;"></sl-icon-button>
|
||||
<sl-icon-button name="pencil" label="Edit" style="font-size: 2.5rem;"></sl-icon-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlIconButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -38,11 +43,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Colors
|
||||
|
||||
Icon buttons are designed to have a uniform appearance, so their color is not inherited. However, you can still customize them by styling the `base` part.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="icon-button-color">
|
||||
<sl-icon-button name="type-bold" label="Bold"></sl-icon-button>
|
||||
<sl-icon-button name="type-italic" label="Italic"></sl-icon-button>
|
||||
@@ -65,7 +72,7 @@ Icon buttons are designed to have a uniform appearance, so their color is not in
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlIconButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -100,11 +107,11 @@ const App = () => (
|
||||
|
||||
Use the `href` attribute to convert the button to a link.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-icon-button name="gear" label="Settings" href="https://example.com" target="_blank"></sl-icon-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlIconButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlIconButton name="gear" label="Settings" href="https://example.com" target="_blank" />;
|
||||
@@ -114,13 +121,13 @@ const App = () => <SlIconButton name="gear" label="Settings" href="https://examp
|
||||
|
||||
Wrap a tooltip around an icon button to provide contextual information to the user.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-tooltip content="Settings">
|
||||
<sl-icon-button name="gear" label="Settings"></sl-icon-button>
|
||||
</sl-tooltip>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlIconButton, SlTooltip } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -134,14 +141,12 @@ const App = () => (
|
||||
|
||||
Use the `disabled` attribute to disable the icon button.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-icon-button name="gear" label="Settings" disabled></sl-icon-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlIconButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlIconButton name="gear" label="Settings" disabled />;
|
||||
```
|
||||
|
||||
[component-metadata:sl-icon-button]
|
||||
@@ -1,10 +1,15 @@
|
||||
# Icon
|
||||
|
||||
[component-header:sl-icon]
|
||||
---
|
||||
meta:
|
||||
title: Icon
|
||||
description: Icons are symbols that can be used to represent various options within an application.
|
||||
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.
|
||||
|
||||
?> 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.
|
||||
:::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.
|
||||
:::
|
||||
|
||||
## Default Icons
|
||||
|
||||
@@ -35,7 +40,7 @@ All available icons in the `default` icon library are shown below. Click or tap
|
||||
|
||||
Icons inherit their color from the current text color. Thus, you can set the `color` property on the `<sl-icon>` element or an ancestor to change the color.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div style="color: #4a90e2;">
|
||||
<sl-icon name="exclamation-triangle"></sl-icon>
|
||||
<sl-icon name="archive"></sl-icon>
|
||||
@@ -62,7 +67,9 @@ Icons inherit their color from the current text color. Thus, you can set the `co
|
||||
</div>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -95,11 +102,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Sizing
|
||||
|
||||
Icons are sized relative to the current font size. To change their size, set the `font-size` property on the icon itself or on a parent element as shown below.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div style="font-size: 32px;">
|
||||
<sl-icon name="exclamation-triangle"></sl-icon>
|
||||
<sl-icon name="archive"></sl-icon>
|
||||
@@ -120,7 +129,9 @@ Icons are sized relative to the current font size. To change their size, set the
|
||||
</div>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -145,15 +156,17 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Labels
|
||||
|
||||
For non-decorative icons, use the `label` attribute to announce it to assistive devices.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-icon name="star-fill" label="Add to favorites"></sl-icon>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlIcon name="star-fill" label="Add to favorites" />;
|
||||
@@ -163,16 +176,20 @@ const App = () => <SlIcon name="star-fill" label="Add to favorites" />;
|
||||
|
||||
Custom icons can be loaded individually with the `src` attribute. Only SVGs on a local or CORS-enabled endpoint are supported. If you're using more than one custom icon, it might make sense to register a [custom icon library](#icon-libraries).
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-icon src="https://shoelace.style/assets/images/shoe.svg" style="font-size: 8rem;"></sl-icon>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlIcon src="https://shoelace.style/assets/images/shoe.svg" style={{ fontSize: '8rem' }}></SlIcon>;
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
## Icon Libraries
|
||||
|
||||
You can register additional icons to use with the `<sl-icon>` component through icon libraries. Icon files can exist locally or on a CORS-enabled endpoint (e.g. a CDN). There is no limit to how many icon libraries you can register and there is no cost associated with registering them, as individual icons are only requested when they're used.
|
||||
@@ -213,7 +230,7 @@ This will register the [Boxicons](https://boxicons.com/) library using the jsDel
|
||||
|
||||
Icons in this library are licensed under the [Creative Commons 4.0 License](https://github.com/atisawd/boxicons#license).
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||
|
||||
@@ -258,7 +275,7 @@ This will register the [Lucide](https://lucide.dev/) icon library using the jsDe
|
||||
|
||||
Icons in this library are licensed under the [MIT License](https://github.com/lucide-icons/lucide/blob/master/LICENSE).
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div style="font-size: 24px;">
|
||||
<sl-icon library="lucide" name="feather"></sl-icon>
|
||||
<sl-icon library="lucide" name="pie-chart"></sl-icon>
|
||||
@@ -283,7 +300,7 @@ This will register the [Font Awesome Free](https://fontawesome.com/) library usi
|
||||
|
||||
Icons in this library are licensed under the [Font Awesome Free License](https://github.com/FortAwesome/Font-Awesome/blob/master/LICENSE.txt). Some of the icons that appear on the Font Awesome website require a license and are therefore not available in the CDN.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||
|
||||
@@ -329,7 +346,7 @@ This will register the [Heroicons](https://heroicons.com/) library using the jsD
|
||||
|
||||
Icons in this library are licensed under the [MIT License](https://github.com/tailwindlabs/heroicons/blob/master/LICENSE).
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||
|
||||
@@ -354,7 +371,7 @@ This will register the [Iconoir](https://iconoir.com/) library using the jsDeliv
|
||||
|
||||
Icons in this library are licensed under the [MIT License](https://github.com/lucaburgio/iconoir/blob/master/LICENSE).
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||
|
||||
@@ -379,7 +396,7 @@ This will register the [Ionicons](https://ionicons.com/) library using the jsDel
|
||||
|
||||
Icons in this library are licensed under the [MIT License](https://github.com/ionic-team/ionicons/blob/master/LICENSE).
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||
|
||||
@@ -424,7 +441,7 @@ This will register the [Jam Icons](https://jam-icons.com/) library using the jsD
|
||||
|
||||
Icons in this library are licensed under the [MIT License](https://github.com/michaelampr/jam/blob/master/LICENSE).
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||
|
||||
@@ -457,7 +474,7 @@ This will register the [Material Icons](https://material.io/resources/icons/?sty
|
||||
|
||||
Icons in this library are licensed under the [Apache 2.0 License](https://github.com/google/material-design-icons/blob/master/LICENSE).
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||
|
||||
@@ -500,7 +517,7 @@ This will register the [Remix Icon](https://remixicon.com/) library using the js
|
||||
|
||||
Icons in this library are licensed under the [Apache 2.0 License](https://github.com/Remix-Design/RemixIcon/blob/master/License).
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||
|
||||
@@ -537,7 +554,7 @@ This will register the [Tabler Icons](https://tabler-icons.io/) library using th
|
||||
|
||||
Icons in this library are licensed under the [MIT License](https://github.com/tabler/tabler-icons/blob/master/LICENSE).
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||
|
||||
@@ -569,7 +586,7 @@ This will register the [Unicons](https://iconscout.com/unicons) library using th
|
||||
|
||||
Icons in this library are licensed under the [Apache 2.0 License](https://github.com/Iconscout/unicons/blob/master/LICENSE). Some of the icons that appear on the Unicons website, particularly many of the solid variations, require a license and are therefore not available in the CDN.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||
|
||||
@@ -617,6 +634,39 @@ This example will load the same set of icons from the jsDelivr CDN instead of yo
|
||||
</script>
|
||||
```
|
||||
|
||||
#### Customize the default library to use SVG sprites
|
||||
|
||||
To improve performance you can use a SVG sprites to avoid multiple trips for each SVG. The browser will load the sprite sheet once and then you reference the particular SVG within the sprite sheet using hash selector.
|
||||
|
||||
As always, make sure to benchmark these changes. When using HTTP/2, it may in fact be more bandwidth-friendly to use multiple small requests instead of 1 large sprite sheet.
|
||||
|
||||
:::danger
|
||||
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.
|
||||
:::
|
||||
|
||||
```html:preview
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||
|
||||
registerIconLibrary('sprite', {
|
||||
resolver: name => `/assets/images/sprite.svg#${name}`,
|
||||
mutator: svg => svg.setAttribute('fill', 'currentColor'),
|
||||
spriteSheet: true
|
||||
});
|
||||
</script>
|
||||
|
||||
<div style="font-size: 24px;">
|
||||
<sl-icon library="sprite" name="clock"></sl-icon>
|
||||
<sl-icon library="sprite" name="speedometer"></sl-icon>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Customizing the System Library
|
||||
|
||||
The system library contains only the icons used internally by Shoelace components. Unlike the default icon library, the system library does not rely on physical assets. Instead, its icons are hard-coded as data URIs into the resolver to ensure their availability.
|
||||
@@ -649,7 +699,7 @@ If you want to change the icons Shoelace uses internally, you can register an ic
|
||||
}
|
||||
|
||||
fetch('/dist/assets/icons/icons.json')
|
||||
.then(res => res.json())
|
||||
.then(res => res.json())
|
||||
.then(icons => {
|
||||
const container = document.querySelector('.icon-search');
|
||||
const input = container.querySelector('sl-input');
|
||||
@@ -668,12 +718,12 @@ If you want to change the icons Shoelace uses internally, you can register an ic
|
||||
item.setAttribute('data-terms', [i.name, i.title, ...(i.tags || []), ...(i.categories || [])].join(' '));
|
||||
item.innerHTML = `
|
||||
<svg width="1em" height="1em" fill="currentColor">
|
||||
<use xlink:href="/assets/icons/sprite.svg#${i.name}"></use>
|
||||
</svg>
|
||||
<use href="/assets/images/sprite.svg#${i.name}"></use>
|
||||
</svg>
|
||||
`;
|
||||
list.appendChild(item);
|
||||
|
||||
// Wrap it with a tooltip the first time the mouse lands on it. We do this instead of baking them into the DOM
|
||||
// Wrap it with a tooltip the first time the mouse lands on it. We do this instead of baking them into the DOM
|
||||
// to improve this page's performance. See: https://github.com/shoelace-style/shoelace/issues/1122
|
||||
item.addEventListener('mouseover', () => wrapWithTooltip(item), { once: true });
|
||||
|
||||
@@ -708,12 +758,12 @@ If you want to change the icons Shoelace uses internally, you can register an ic
|
||||
});
|
||||
|
||||
// Sort by type and remember preference
|
||||
const iconType = localStorage.getItem('sl-icon:type') || 'outline';
|
||||
const iconType = sessionStorage.getItem('sl-icon:type') || 'outline';
|
||||
select.value = iconType;
|
||||
list.setAttribute('data-type', select.value);
|
||||
select.addEventListener('sl-change', () => {
|
||||
list.setAttribute('data-type', select.value);
|
||||
localStorage.setItem('sl-icon:type', select.value);
|
||||
sessionStorage.setItem('sl-icon:type', select.value);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -816,8 +866,6 @@ If you want to change the icons Shoelace uses internally, you can register an ic
|
||||
@media screen and (max-width: 500px) {
|
||||
.icon-list {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
[component-metadata:sl-icon]
|
||||
@@ -1,10 +1,13 @@
|
||||
# Image Comparer
|
||||
|
||||
[component-header:sl-image-comparer]
|
||||
---
|
||||
meta:
|
||||
title: Image Comparer
|
||||
description: Compare visual differences between similar photos with a sliding panel.
|
||||
layout: component
|
||||
---
|
||||
|
||||
For best results, use images that share the same dimensions. The slider can be controlled by dragging or pressing the left and right arrow keys. (Tip: press shift + arrows to move the slider in larger intervals, or home + end to jump to the beginning or end.)
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-image-comparer>
|
||||
<img
|
||||
slot="before"
|
||||
@@ -19,7 +22,7 @@ For best results, use images that share the same dimensions. The slider can be c
|
||||
</sl-image-comparer>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlImageComparer } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -44,7 +47,7 @@ const App = () => (
|
||||
|
||||
Use the `position` attribute to set the initial position of the slider. This is a percentage from `0` to `100`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-image-comparer position="25">
|
||||
<img
|
||||
slot="before"
|
||||
@@ -59,7 +62,7 @@ Use the `position` attribute to set the initial position of the slider. This is
|
||||
</sl-image-comparer>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlImageComparer } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -77,5 +80,3 @@ const App = () => (
|
||||
</SlImageComparer>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-image-comparer]
|
||||
@@ -1,16 +1,19 @@
|
||||
# Include
|
||||
|
||||
[component-header:sl-include]
|
||||
---
|
||||
meta:
|
||||
title: Include
|
||||
description: Includes give you the power to embed external HTML files into the page.
|
||||
layout: component
|
||||
---
|
||||
|
||||
Included files are asynchronously requested using `window.fetch()`. Requests are cached, so the same file can be included multiple times, but only one request will be made.
|
||||
|
||||
The included content will be inserted into the `<sl-include>` element's default slot so it can be easily accessed and styled through the light DOM.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-include src="https://shoelace.style/assets/examples/include.html"></sl-include>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlInclude } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlInclude src="https://shoelace.style/assets/examples/include.html" />;
|
||||
@@ -43,5 +46,3 @@ If the request fails, the `sl-error` event will be emitted. In this case, `event
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
[component-metadata:sl-include]
|
||||
@@ -1,18 +1,23 @@
|
||||
# Input
|
||||
---
|
||||
meta:
|
||||
title: Input
|
||||
description: Inputs collect data from the user.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-input]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-input></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlInput />;
|
||||
```
|
||||
|
||||
?> This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
:::tip
|
||||
This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -20,11 +25,11 @@ const App = () => <SlInput />;
|
||||
|
||||
Use the `label` attribute to give the input an accessible label. For labels that contain HTML, use the `label` slot instead.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-input label="What is your name?"></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlIcon, SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlInput label="What is your name?" />;
|
||||
@@ -34,11 +39,11 @@ const App = () => <SlInput label="What is your name?" />;
|
||||
|
||||
Add descriptive help text to an input with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-input label="Nickname" help-text="What would you like people to call you?"></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlIcon, SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlInput label="Nickname" help-text="What would you like people to call you?" />;
|
||||
@@ -48,11 +53,11 @@ const App = () => <SlInput label="Nickname" help-text="What would you like peopl
|
||||
|
||||
Use the `placeholder` attribute to add a placeholder.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-input placeholder="Type something"></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlInput placeholder="Type something" />;
|
||||
@@ -62,11 +67,11 @@ const App = () => <SlInput placeholder="Type something" />;
|
||||
|
||||
Add the `clearable` attribute to add a clear button when the input has content.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-input placeholder="Clearable" clearable></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlInput placeholder="Clearable" clearable />;
|
||||
@@ -76,11 +81,11 @@ const App = () => <SlInput placeholder="Clearable" clearable />;
|
||||
|
||||
Add the `password-toggle` attribute to add a toggle button that will show the password when activated.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-input type="password" placeholder="Password Toggle" password-toggle></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlInput type="password" placeholder="Password Toggle" size="medium" password-toggle />;
|
||||
@@ -90,11 +95,11 @@ const App = () => <SlInput type="password" placeholder="Password Toggle" size="m
|
||||
|
||||
Add the `filled` attribute to draw a filled input.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-input placeholder="Type something" filled></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlInput placeholder="Type something" filled />;
|
||||
@@ -104,11 +109,11 @@ const App = () => <SlInput placeholder="Type something" filled />;
|
||||
|
||||
Use the `disabled` attribute to disable an input.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-input placeholder="Disabled" disabled></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlInput placeholder="Disabled" disabled />;
|
||||
@@ -118,7 +123,7 @@ const App = () => <SlInput placeholder="Disabled" disabled />;
|
||||
|
||||
Use the `size` attribute to change an input's size.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-input placeholder="Small" size="small"></sl-input>
|
||||
<br />
|
||||
<sl-input placeholder="Medium" size="medium"></sl-input>
|
||||
@@ -126,7 +131,7 @@ Use the `size` attribute to change an input's size.
|
||||
<sl-input placeholder="Large" size="large"></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -144,7 +149,7 @@ const App = () => (
|
||||
|
||||
Use the `pill` attribute to give inputs rounded edges.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-input placeholder="Small" size="small" pill></sl-input>
|
||||
<br />
|
||||
<sl-input placeholder="Medium" size="medium" pill></sl-input>
|
||||
@@ -152,7 +157,7 @@ Use the `pill` attribute to give inputs rounded edges.
|
||||
<sl-input placeholder="Large" size="large" pill></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -170,7 +175,7 @@ const App = () => (
|
||||
|
||||
The `type` attribute controls the type of input the browser renders.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-input type="email" placeholder="Email"></sl-input>
|
||||
<br />
|
||||
<sl-input type="number" placeholder="Number"></sl-input>
|
||||
@@ -178,7 +183,7 @@ The `type` attribute controls the type of input the browser renders.
|
||||
<sl-input type="date" placeholder="Date"></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -196,7 +201,7 @@ const App = () => (
|
||||
|
||||
Use the `prefix` and `suffix` slots to add icons.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-input placeholder="Small" size="small">
|
||||
<sl-icon name="house" slot="prefix"></sl-icon>
|
||||
<sl-icon name="chat" slot="suffix"></sl-icon>
|
||||
@@ -213,7 +218,7 @@ Use the `prefix` and `suffix` slots to add icons.
|
||||
</sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlIcon, SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -240,8 +245,8 @@ const App = () => (
|
||||
|
||||
Use [CSS parts](#css-parts) to customize the way form controls are drawn. This example uses CSS grid to position the label to the left of the control, but the possible orientations are nearly endless. The same technique works for inputs, textareas, radio groups, and similar form controls.
|
||||
|
||||
```html preview
|
||||
<sl-input class="label-on-left" label="Name" help-text="Enter your name""></sl-input>
|
||||
```html:preview
|
||||
<sl-input class="label-on-left" label="Name" help-text="Enter your name"></sl-input>
|
||||
<sl-input class="label-on-left" label="Email" type="email" help-text="Enter your email"></sl-input>
|
||||
<sl-textarea class="label-on-left" label="Bio" help-text="Tell us something about yourself"></sl-textarea>
|
||||
|
||||
@@ -271,5 +276,3 @@ Use [CSS parts](#css-parts) to customize the way form controls are drawn. This e
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
[component-metadata:sl-input]
|
||||
@@ -1,8 +1,11 @@
|
||||
# Menu Item
|
||||
---
|
||||
meta:
|
||||
title: Menu Item
|
||||
description: Menu items provide options for the user to pick from in a menu.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-menu-item]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-menu style="max-width: 200px;">
|
||||
<sl-menu-item>Option 1</sl-menu-item>
|
||||
<sl-menu-item>Option 2</sl-menu-item>
|
||||
@@ -22,7 +25,9 @@
|
||||
</sl-menu>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlDivider, SlIcon, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -48,13 +53,15 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
## Examples
|
||||
|
||||
### Disabled
|
||||
|
||||
Add the `disabled` attribute to disable the menu item so it cannot be selected.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-menu style="max-width: 200px;">
|
||||
<sl-menu-item>Option 1</sl-menu-item>
|
||||
<sl-menu-item disabled>Option 2</sl-menu-item>
|
||||
@@ -62,7 +69,9 @@ Add the `disabled` attribute to disable the menu item so it cannot be selected.
|
||||
</sl-menu>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -74,11 +83,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Prefix & Suffix
|
||||
|
||||
Add content to the start and end of menu items using the `prefix` and `suffix` slots.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-menu style="max-width: 200px;">
|
||||
<sl-menu-item>
|
||||
<sl-icon slot="prefix" name="house"></sl-icon>
|
||||
@@ -100,7 +111,9 @@ Add content to the start and end of menu items using the `prefix` and `suffix` s
|
||||
</sl-menu>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlBadge, SlDivider, SlIcon, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -128,13 +141,15 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Checkbox Menu Items
|
||||
|
||||
Set the `type` attribute to `checkbox` to create a menu item that will toggle on and off when selected. You can use the `checked` attribute to set the initial state.
|
||||
|
||||
Checkbox menu items are visually indistinguishable from regular menu items. Their ability to be toggled is primarily inferred from context, much like you'd find in the menu of a native app.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-menu style="max-width: 200px;">
|
||||
<sl-menu-item type="checkbox">Autosave</sl-menu-item>
|
||||
<sl-menu-item type="checkbox" checked>Check Spelling</sl-menu-item>
|
||||
@@ -142,7 +157,9 @@ Checkbox menu items are visually indistinguishable from regular menu items. Thei
|
||||
</sl-menu>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -156,11 +173,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Value & Selection
|
||||
|
||||
The `value` attribute can be used to assign a hidden value, such as a unique identifier, to a menu item. When an item is selected, the `sl-select` event will be emitted and a reference to the item will be available at `event.detail.item`. You can use this reference to access the selected item's value, its checked state, and more.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-menu class="menu-value" style="max-width: 200px;">
|
||||
<sl-menu-item value="opt-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="opt-2">Option 2</sl-menu-item>
|
||||
@@ -187,7 +206,9 @@ The `value` attribute can be used to assign a hidden value, such as a unique ide
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => {
|
||||
@@ -211,4 +232,4 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
[component-metadata:sl-menu-item]
|
||||
{% endraw %}
|
||||
@@ -1,8 +1,11 @@
|
||||
# Menu Label
|
||||
---
|
||||
meta:
|
||||
title: Menu Label
|
||||
description: Menu labels are used to describe a group of menu items.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-menu-label]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-menu style="max-width: 200px;">
|
||||
<sl-menu-label>Fruits</sl-menu-label>
|
||||
<sl-menu-item value="apple">Apple</sl-menu-item>
|
||||
@@ -16,7 +19,9 @@
|
||||
</sl-menu>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlDivider, SlMenu, SlMenuLabel, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -34,4 +39,4 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-menu-label]
|
||||
{% endraw %}
|
||||
@@ -1,10 +1,13 @@
|
||||
# Menu
|
||||
|
||||
[component-header:sl-menu]
|
||||
---
|
||||
meta:
|
||||
title: Menu
|
||||
description: Menus provide a list of options for the user to choose from.
|
||||
layout: component
|
||||
---
|
||||
|
||||
You can use [menu items](/components/menu-item), [menu labels](/components/menu-label), and [dividers](/components/divider) to compose a menu. Menus support keyboard interactions, including type-to-select an option.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-menu style="max-width: 200px;">
|
||||
<sl-menu-item value="undo">Undo</sl-menu-item>
|
||||
<sl-menu-item value="redo">Redo</sl-menu-item>
|
||||
@@ -16,7 +19,9 @@ You can use [menu items](/components/menu-item), [menu labels](/components/menu-
|
||||
</sl-menu>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlDivider, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -32,6 +37,8 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
?> 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 `<nav>` and `<a>` elements instead.
|
||||
{% endraw %}
|
||||
|
||||
[component-metadata:sl-menu]
|
||||
:::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 `<nav>` and `<a>` elements instead.
|
||||
:::
|
||||
@@ -1,10 +1,13 @@
|
||||
# Mutation Observer
|
||||
|
||||
[component-header:sl-mutation-observer]
|
||||
---
|
||||
meta:
|
||||
title: Mutation Observer
|
||||
description: The Mutation Observer component offers a thin, declarative interface to the MutationObserver API.
|
||||
layout: component
|
||||
---
|
||||
|
||||
The mutation observer will report changes to the content it wraps through the `sl-mutation` event. When emitted, a collection of [MutationRecord](https://developer.mozilla.org/en-US/docs/Web/API/MutationRecord) objects will be attached to `event.detail` that contains information about how it changed.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="mutation-overview">
|
||||
<sl-mutation-observer attr="variant">
|
||||
<sl-button variant="primary">Click to mutate</sl-button>
|
||||
@@ -40,15 +43,15 @@ The mutation observer will report changes to the content it wraps through the `s
|
||||
</div>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlMutationObserver } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
.resize-observer-overview div {
|
||||
display: flex;
|
||||
border: solid 2px var(--sl-input-border-color);
|
||||
align-items: center;
|
||||
display: flex;
|
||||
border: solid 2px var(--sl-input-border-color);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 4rem 2rem;
|
||||
@@ -80,7 +83,9 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
?> When you create a mutation observer, you must indicate what changes it should respond to by including at least one of `attr`, `child-list`, or `char-data`. If you don't specify at least one of these attributes, no mutation events will be emitted.
|
||||
:::tip
|
||||
When you create a mutation observer, you must indicate what changes it should respond to by including at least one of `attr`, `child-list`, or `char-data`. If you don't specify at least one of these attributes, no mutation events will be emitted.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -88,7 +93,7 @@ const App = () => {
|
||||
|
||||
Use the `child-list` attribute to watch for new child elements that are added or removed.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="mutation-child-list">
|
||||
<sl-mutation-observer child-list>
|
||||
<div class="buttons">
|
||||
@@ -139,7 +144,7 @@ Use the `child-list` attribute to watch for new child elements that are added or
|
||||
</div>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlMutationObserver } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -187,5 +192,3 @@ const App = () => {
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
[component-metadata:sl-mutation-observer]
|
||||
@@ -1,8 +1,11 @@
|
||||
# Option
|
||||
---
|
||||
meta:
|
||||
title: Option
|
||||
description: Options define the selectable items within various form controls such as select.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-option]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-select label="Select one">
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2">Option 2</sl-option>
|
||||
@@ -10,7 +13,7 @@
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -28,7 +31,7 @@ const App = () => (
|
||||
|
||||
Use the `disabled` attribute to disable an option and prevent it from being selected.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-select label="Select one">
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2" disabled>Option 2</sl-option>
|
||||
@@ -36,7 +39,7 @@ Use the `disabled` attribute to disable an option and prevent it from being sele
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -54,7 +57,7 @@ const App = () => (
|
||||
|
||||
Add icons to the start and end of menu items using the `prefix` and `suffix` slots.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-select label="Select one">
|
||||
<sl-option value="option-1">
|
||||
<sl-icon slot="prefix" name="envelope"></sl-icon>
|
||||
@@ -75,5 +78,3 @@ Add icons to the start and end of menu items using the `prefix` and `suffix` slo
|
||||
</sl-option>
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
[component-metadata:sl-option]
|
||||
@@ -1,14 +1,19 @@
|
||||
# Popup
|
||||
|
||||
[component-header:sl-popup]
|
||||
---
|
||||
meta:
|
||||
title: Popup
|
||||
description: 'Popup is a utility that lets you declaratively anchor "popup" containers to another element.'
|
||||
layout: component
|
||||
---
|
||||
|
||||
This component's name is inspired by [`<popup>`](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Popup/explainer.md). It uses [Floating UI](https://floating-ui.com/) under the hood to provide a well-tested, lightweight, and fully declarative positioning utility for tooltips, dropdowns, and more.
|
||||
|
||||
Popup doesn't provide any styles — just positioning! The popup's preferred placement, distance, and skidding (offset) can be configured using attributes. An arrow that points to the anchor can be shown and customized to your liking. Additional positioning options are available and described in more detail below.
|
||||
|
||||
!> Popup is a low-level utility built specifically for positioning elements. Do not mistake it for a [tooltip](/components/tooltip) or similar because _it does not facilitate an accessible experience!_ Almost every correct usage of `<sl-popup>` will involve building other components. It should rarely, if ever, occur directly in your HTML.
|
||||
:::warning
|
||||
Popup is a low-level utility built specifically for positioning elements. Do not mistake it for a [tooltip](/components/tooltip) or similar because _it does not facilitate an accessible experience!_ Almost every correct usage of `<sl-popup>` will involve building other components. It should rarely, if ever, occur directly in your HTML.
|
||||
:::
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="popup-overview">
|
||||
<sl-popup placement="top" active>
|
||||
<span slot="anchor"></span>
|
||||
@@ -97,7 +102,7 @@ Popup doesn't provide any styles — just positioning! The popup's preferred pla
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlPopup, SlSelect, SlMenuItem, SlInput, SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -215,7 +220,9 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
?> A popup's anchor should not be styled with `display: contents` since the coordinates will not be eligible for calculation. However, if the anchor is a `<slot>` element, popup will use the first assigned element as the anchor. This behavior allows other components to pass anchors through more easily via composition.
|
||||
:::tip
|
||||
A popup's anchor should not be styled with `display: contents` since the coordinates will not be eligible for calculation. However, if the anchor is a `<slot>` element, popup will use the first assigned element as the anchor. This behavior allows other components to pass anchors through more easily via composition.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -223,7 +230,7 @@ const App = () => {
|
||||
|
||||
Popups are inactive and hidden until the `active` attribute is applied. Removing the attribute will tear down all positioning logic and listeners, meaning you can have many idle popups on the page without affecting performance.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="popup-active">
|
||||
<sl-popup placement="top" active>
|
||||
<span slot="anchor"></span>
|
||||
@@ -260,7 +267,7 @@ Popups are inactive and hidden until the `active` attribute is applied. Removing
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlPopup, SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -308,7 +315,7 @@ const App = () => {
|
||||
|
||||
By default, anchors are slotted into the popup using the `anchor` slot. If your anchor needs to live outside of the popup, you can pass the anchor's `id` to the `anchor` attribute. Alternatively, you can pass an element reference to the `anchor` property to achieve the same effect without using an `id`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<span id="external-anchor"></span>
|
||||
|
||||
<sl-popup anchor="external-anchor" placement="top" active>
|
||||
@@ -333,7 +340,7 @@ By default, anchors are slotted into the popup using the `anchor` slot. If your
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlPopup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -374,7 +381,7 @@ Use the `placement` attribute to tell the popup the preferred placement of the p
|
||||
|
||||
Since placement is preferred when using `flip`, you can observe the popup's current placement when it's active by looking at the `data-current-placement` attribute. This attribute will update as the popup flips to find available space and it will be removed when the popup is deactivated.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="popup-placement">
|
||||
<sl-popup placement="top" active>
|
||||
<span slot="anchor"></span>
|
||||
@@ -427,7 +434,7 @@ Since placement is preferred when using `flip`, you can observe the popup's curr
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlPopup, SlSelect, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -489,7 +496,7 @@ const App = () => {
|
||||
|
||||
Use the `distance` attribute to change the distance between the popup and its anchor. A positive value will move the popup further away and a negative value will move it closer.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="popup-distance">
|
||||
<sl-popup placement="top" distance="0" active>
|
||||
<span slot="anchor"></span>
|
||||
@@ -529,7 +536,7 @@ Use the `distance` attribute to change the distance between the popup and its an
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlPopup, SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -585,7 +592,7 @@ const App = () => {
|
||||
|
||||
The `skidding` attribute is similar to `distance`, but instead allows you to offset the popup along the anchor's axis. Both positive and negative values are allowed.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="popup-skidding">
|
||||
<sl-popup placement="top" skidding="0" active>
|
||||
<span slot="anchor"></span>
|
||||
@@ -625,7 +632,7 @@ The `skidding` attribute is similar to `distance`, but instead allows you to off
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlPopup, SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -683,7 +690,7 @@ Add an arrow to your popup with the `arrow` attribute. It's usually a good idea
|
||||
|
||||
By default, the arrow will be aligned as close to the center of the _anchor_ as possible, considering available space and `arrow-padding`. You can use the `arrow-placement` attribute to force the arrow to align to the start, end, or center of the _popup_ instead.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="popup-arrow">
|
||||
<sl-popup placement="top" arrow arrow-placement="anchor" distance="8" active>
|
||||
<span slot="anchor"></span>
|
||||
@@ -768,7 +775,7 @@ By default, the arrow will be aligned as close to the center of the _anchor_ as
|
||||
</div>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlPopup, SlSelect, SlMenuItem, SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -873,7 +880,7 @@ const App = () => {
|
||||
|
||||
Use the `sync` attribute to make the popup the same width or height as the anchor element. This is useful for controls that need the popup to stay the same width or height as the trigger.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="popup-sync">
|
||||
<sl-popup placement="top" sync="width" active>
|
||||
<span slot="anchor"></span>
|
||||
@@ -921,7 +928,7 @@ Use the `sync` attribute to make the popup the same width or height as the ancho
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlPopup, SlSelect, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -977,13 +984,13 @@ const App = () => {
|
||||
|
||||
By default, the popup is positioned using an absolute positioning strategy. However, if your anchor is fixed or exists within a container that has `overflow: auto|hidden`, the popup risks being clipped. To work around this, you can use a fixed positioning strategy by setting the `strategy` attribute to `fixed`.
|
||||
|
||||
The fixed positioning strategy reduces jumpiness when the anchor is fixed and allows the popup to break out containers that clip. When using this strategy, it's important to note that the content will be positioned _relative to its 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.
|
||||
The fixed positioning strategy reduces jumpiness when the anchor is fixed and allows the popup to break out containers that clip. When using this strategy, it's important to note that the content 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.
|
||||
|
||||
In this example, you can see how the popup breaks out of the overflow container when it's fixed. The fixed positioning strategy tends to be less performant than absolute, so avoid using it unnecessarily.
|
||||
|
||||
Toggle the switch and scroll the container to see the difference.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="popup-strategy">
|
||||
<div class="overflow">
|
||||
<sl-popup placement="top" strategy="fixed" active>
|
||||
@@ -1032,7 +1039,7 @@ Toggle the switch and scroll the container to see the difference.
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlPopup, SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -1094,7 +1101,7 @@ When the popup doesn't have enough room in its preferred placement, it can autom
|
||||
|
||||
Scroll the container to see how the popup flips to prevent clipping.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="popup-flip">
|
||||
<div class="overflow">
|
||||
<sl-popup placement="top" flip active>
|
||||
@@ -1140,7 +1147,7 @@ Scroll the container to see how the popup flips to prevent clipping.
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlPopup, SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -1203,7 +1210,7 @@ If no fallback placement works, the final placement will be determined by `flip-
|
||||
|
||||
Scroll the container to see how the popup changes it's fallback placement to prevent clipping.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="popup-flip-fallbacks">
|
||||
<div class="overflow">
|
||||
<sl-popup placement="top" flip flip-fallback-placements="right bottom" flip-fallback-strategy="initial" active>
|
||||
@@ -1238,7 +1245,7 @@ Scroll the container to see how the popup changes it's fallback placement to pre
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlPopup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -1289,7 +1296,7 @@ When a popup is longer than its anchor, it risks being clipped by an overflowing
|
||||
|
||||
Toggle the switch to see the difference.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="popup-shift">
|
||||
<div class="overflow">
|
||||
<sl-popup placement="top" shift shift-padding="10" active>
|
||||
@@ -1333,7 +1340,7 @@ Toggle the switch to see the difference.
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlPopup, SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -1392,7 +1399,7 @@ When using `auto-size`, one or both of `--auto-size-available-width` and `--auto
|
||||
|
||||
Scroll the container to see the popup resize as its available space changes.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="popup-auto-size">
|
||||
<div class="overflow">
|
||||
<sl-popup placement="top" auto-size="both" auto-size-padding="10" active>
|
||||
@@ -1445,7 +1452,7 @@ Scroll the container to see the popup resize as its available space changes.
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlPopup, SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -1504,5 +1511,3 @@ const App = () => {
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
[component-metadata:sl-popup]
|
||||
@@ -1,12 +1,15 @@
|
||||
# Progress Bar
|
||||
---
|
||||
meta:
|
||||
title: Progress Bar
|
||||
description: Progress bars are used to show the status of an ongoing operation.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-progress-bar]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-progress-bar value="50"></sl-progress-bar>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlProgressBar } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlProgressBar value={50} />;
|
||||
@@ -18,11 +21,11 @@ const App = () => <SlProgressBar value={50} />;
|
||||
|
||||
Use the `label` attribute to label the progress bar and tell assistive devices how to announce it.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-progress-bar value="50" label="Upload progress"></sl-progress-bar>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlProgressBar } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlProgressBar value="50" label="Upload progress" />;
|
||||
@@ -32,21 +35,25 @@ const App = () => <SlProgressBar value="50" label="Upload progress" />;
|
||||
|
||||
Use the `--height` custom property to set the progress bar's height.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-progress-bar value="50" style="--height: 6px;"></sl-progress-bar>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlProgressBar } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlProgressBar value={50} style={{ '--height': '6px' }} />;
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Showing Values
|
||||
|
||||
Use the default slot to show a value.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-progress-bar value="50" class="progress-bar-values">50%</sl-progress-bar>
|
||||
|
||||
<br />
|
||||
@@ -73,7 +80,7 @@ Use the default slot to show a value.
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlIcon, SlProgressBar } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -109,14 +116,12 @@ const App = () => {
|
||||
|
||||
The `indeterminate` attribute can be used to inform the user that the operation is pending, but its status cannot currently be determined. In this state, `value` is ignored and the label, if present, will not be shown.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-progress-bar indeterminate></sl-progress-bar>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlProgressBar } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlProgressBar indeterminate />;
|
||||
```
|
||||
|
||||
[component-metadata:sl-progress-bar]
|
||||
@@ -1,12 +1,15 @@
|
||||
# Progress Ring
|
||||
---
|
||||
meta:
|
||||
title: Progress Ring
|
||||
description: Progress rings are used to show the progress of a determinate operation in a circular fashion.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-progress-ring]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-progress-ring value="25"></sl-progress-ring>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlProgressRing } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlProgressRing value="25" />;
|
||||
@@ -18,45 +21,55 @@ const App = () => <SlProgressRing value="25" />;
|
||||
|
||||
Use the `--size` custom property to set the diameter of the progress ring.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-progress-ring value="50" style="--size: 200px;"></sl-progress-ring>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlProgressRing } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlProgressRing value="50" style={{ '--size': '200px' }} />;
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Track and Indicator Width
|
||||
|
||||
Use the `--track-width` and `--indicator-width` custom properties to set the width of the progress ring's track and indicator.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-progress-ring value="50" style="--track-width: 6px; --indicator-width: 12px;"></sl-progress-ring>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlProgressRing } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlProgressRing value="50" style={{ '--track-width': '6px', '--indicator-width': '12px' }} />;
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Colors
|
||||
|
||||
To change the color, use the `--track-color` and `--indicator-color` custom properties.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-progress-ring
|
||||
value="50"
|
||||
style="
|
||||
--track-color: pink;
|
||||
--track-color: pink;
|
||||
--indicator-color: deeppink;
|
||||
"
|
||||
></sl-progress-ring>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlProgressRing } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -70,15 +83,17 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Labels
|
||||
|
||||
Use the `label` attribute to label the progress ring and tell assistive devices how to announce it.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-progress-ring value="50" label="Upload progress"></sl-progress-ring>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlProgressRing } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlProgressRing value="50" label="Upload progress" />;
|
||||
@@ -88,7 +103,7 @@ const App = () => <SlProgressRing value="50" label="Upload progress" />;
|
||||
|
||||
Use the default slot to show a label inside the progress ring.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-progress-ring value="50" class="progress-ring-values" style="margin-bottom: .5rem;">50%</sl-progress-ring>
|
||||
|
||||
<br />
|
||||
@@ -115,7 +130,9 @@ Use the default slot to show a label inside the progress ring.
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlIcon, SlProgressRing } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -149,4 +166,4 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
[component-metadata:sl-progress-ring]
|
||||
{% endraw %}
|
||||
@@ -1,10 +1,13 @@
|
||||
# QR Code
|
||||
|
||||
[component-header:sl-qr-code]
|
||||
---
|
||||
meta:
|
||||
title: QR Code
|
||||
description: Generates a QR code and renders it using the Canvas API.
|
||||
layout: component
|
||||
---
|
||||
|
||||
QR codes are useful for providing small pieces of information to users who can quickly scan them with a smartphone. Most smartphones have built-in QR code scanners, so simply pointing the camera at a QR code will decode it and allow the user to visit a website, dial a phone number, read a message, etc.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="qr-overview">
|
||||
<sl-qr-code value="https://shoelace.style/" label="Scan this code to visit Shoelace on the web!"></sl-qr-code>
|
||||
<br />
|
||||
@@ -17,8 +20,10 @@ QR codes are useful for providing small pieces of information to users who can q
|
||||
const qrCode = container.querySelector('sl-qr-code');
|
||||
const input = container.querySelector('sl-input');
|
||||
|
||||
input.value = qrCode.value;
|
||||
input.addEventListener('sl-input', () => (qrCode.value = input.value));
|
||||
customElements.whenDefined('sl-qr-code').then(() => {
|
||||
input.value = qrCode.value;
|
||||
input.addEventListener('sl-input', () => (qrCode.value = input.value));
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -32,7 +37,7 @@ QR codes are useful for providing small pieces of information to users who can q
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlQrCode, SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -70,11 +75,11 @@ const App = () => {
|
||||
|
||||
Use the `fill` and `background` attributes to modify the QR code's colors. You should always ensure good contrast for optimal compatibility with QR code scanners.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-qr-code value="https://shoelace.style/" fill="deeppink" background="white"></sl-qr-code>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlQrCode } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlQrCode value="https://shoelace.style/" fill="deeppink" background="white" />;
|
||||
@@ -84,11 +89,11 @@ const App = () => <SlQrCode value="https://shoelace.style/" fill="deeppink" back
|
||||
|
||||
Use the `size` attribute to change the size of the QR code.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-qr-code value="https://shoelace.style/" size="64"></sl-qr-code>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlQrCode } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlQrCode value="https://shoelace.style/" size="64" />;
|
||||
@@ -98,11 +103,11 @@ const App = () => <SlQrCode value="https://shoelace.style/" size="64" />;
|
||||
|
||||
Create a rounded effect with the `radius` attribute.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-qr-code value="https://shoelace.style/" radius="0.5"></sl-qr-code>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlQrCode } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlQrCode value="https://shoelace.style/" radius="0.5" />;
|
||||
@@ -112,7 +117,7 @@ const App = () => <SlQrCode value="https://shoelace.style/" radius="0.5" />;
|
||||
|
||||
QR codes can be rendered with various levels of [error correction](https://www.qrcode.com/en/about/error_correction.html) that can be set using the `error-correction` attribute. This example generates four codes with the same value using different error correction levels.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="qr-error-correction">
|
||||
<sl-qr-code value="https://shoelace.style/" error-correction="L"></sl-qr-code>
|
||||
<sl-qr-code value="https://shoelace.style/" error-correction="M"></sl-qr-code>
|
||||
@@ -129,7 +134,7 @@ QR codes can be rendered with various levels of [error correction](https://www.q
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlQrCode } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -155,5 +160,3 @@ const App = () => {
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
[component-metadata:sl-qr-code]
|
||||
@@ -1,10 +1,13 @@
|
||||
# Radio Button
|
||||
|
||||
[component-header:sl-radio-button]
|
||||
---
|
||||
meta:
|
||||
title: Radio Button
|
||||
description: Radios buttons allow the user to select a single option from a group using a button-like control.
|
||||
layout: component
|
||||
---
|
||||
|
||||
Radio buttons are designed to be used with [radio groups](/components/radio-group). When a radio button has focus, the arrow keys can be used to change the selected option just like standard radio controls.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-radio-group label="Select an option" name="a" value="1">
|
||||
<sl-radio-button value="1">Option 1</sl-radio-button>
|
||||
<sl-radio-button value="2">Option 2</sl-radio-button>
|
||||
@@ -12,7 +15,7 @@ Radio buttons are designed to be used with [radio groups](/components/radio-grou
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -30,7 +33,7 @@ const App = () => (
|
||||
|
||||
To set the initial value and checked state, use the `value` attribute on the containing radio group.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-radio-group label="Select an option" name="a" value="1">
|
||||
<sl-radio-button value="1">Option 1</sl-radio-button>
|
||||
<sl-radio-button value="2">Option 2</sl-radio-button>
|
||||
@@ -38,7 +41,7 @@ To set the initial value and checked state, use the `value` attribute on the con
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -54,7 +57,7 @@ const App = () => (
|
||||
|
||||
Use the `disabled` attribute to disable a radio button.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-radio-group label="Select an option" name="a" value="1">
|
||||
<sl-radio-button value="1">Option 1</sl-radio-button>
|
||||
<sl-radio-button value="2" disabled>Option 2</sl-radio-button>
|
||||
@@ -62,7 +65,7 @@ Use the `disabled` attribute to disable a radio button.
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -80,7 +83,7 @@ const App = () => (
|
||||
|
||||
Use the `size` attribute to change a radio button's size.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-radio-group label="Select an option" name="a" value="1">
|
||||
<sl-radio-button size="small" value="1">Option 1</sl-radio-button>
|
||||
<sl-radio-button size="small" value="2">Option 2</sl-radio-button>
|
||||
@@ -104,7 +107,7 @@ Use the `size` attribute to change a radio button's size.
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -136,7 +139,7 @@ const App = () => (
|
||||
|
||||
Use the `pill` attribute to give radio buttons rounded edges.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-radio-group label="Select an option" name="a" value="1">
|
||||
<sl-radio-button pill size="small" value="1">Option 1</sl-radio-button>
|
||||
<sl-radio-button pill size="small" value="2">Option 2</sl-radio-button>
|
||||
@@ -160,7 +163,7 @@ Use the `pill` attribute to give radio buttons rounded edges.
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -192,7 +195,7 @@ const App = () => (
|
||||
|
||||
Use the `prefix` and `suffix` slots to add icons.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-radio-group label="Select an option" name="a" value="1">
|
||||
<sl-radio-button value="1">
|
||||
<sl-icon slot="prefix" name="archive"></sl-icon>
|
||||
@@ -212,7 +215,7 @@ Use the `prefix` and `suffix` slots to add icons.
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlIcon, SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -240,7 +243,7 @@ const App = () => (
|
||||
|
||||
You can omit button labels and use icons instead. Make sure to set a `label` attribute on each icon so screen readers will announce each option correctly.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-radio-group label="Select an option" name="a" value="neutral">
|
||||
<sl-radio-button value="angry">
|
||||
<sl-icon name="emoji-angry" label="Angry"></sl-icon>
|
||||
@@ -264,7 +267,7 @@ You can omit button labels and use icons instead. Make sure to set a `label` att
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlIcon, SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -291,5 +294,3 @@ const App = () => (
|
||||
</SlRadioGroup>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-radio-button]
|
||||
@@ -1,8 +1,11 @@
|
||||
# Radio Group
|
||||
---
|
||||
meta:
|
||||
title: Radio Group
|
||||
description: Radio groups are used to group multiple radios or radio buttons so they function as a single form control.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-radio-group]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-radio-group label="Select an option" name="a" value="1">
|
||||
<sl-radio value="1">Option 1</sl-radio>
|
||||
<sl-radio value="2">Option 2</sl-radio>
|
||||
@@ -10,7 +13,7 @@
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -28,7 +31,7 @@ const App = () => (
|
||||
|
||||
Add descriptive help text to a radio group with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-radio-group label="Select an option" help-text="Choose the most appropriate option." name="a" value="1">
|
||||
<sl-radio value="1">Option 1</sl-radio>
|
||||
<sl-radio value="2">Option 2</sl-radio>
|
||||
@@ -36,7 +39,7 @@ Add descriptive help text to a radio group with the `help-text` attribute. For h
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -52,7 +55,7 @@ const App = () => (
|
||||
|
||||
[Radio buttons](/components/radio-button) offer an alternate way to display radio controls. In this case, an internal [button group](/components/button-group) is used to group the buttons into a single, cohesive control.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-radio-group label="Select an option" help-text="Select an option that makes you proud." name="a" value="1">
|
||||
<sl-radio-button value="1">Option 1</sl-radio-button>
|
||||
<sl-radio-button value="2">Option 2</sl-radio-button>
|
||||
@@ -60,7 +63,7 @@ const App = () => (
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRadioButton, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -76,7 +79,7 @@ const App = () => (
|
||||
|
||||
Radios and radio buttons can be disabled by adding the `disabled` attribute to the respective options inside the radio group.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-radio-group label="Select an option" name="a" value="1">
|
||||
<sl-radio value="1">Option 1</sl-radio>
|
||||
<sl-radio value="2" disabled>Option 2</sl-radio>
|
||||
@@ -84,7 +87,7 @@ Radios and radio buttons can be disabled by adding the `disabled` attribute to t
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -98,11 +101,60 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### Sizing Options
|
||||
|
||||
The size of [Radios](/components/radio) and [Radio Buttons](/components/radio-buttons) will be determined by the Radio Group's `size` attribute.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option" size="medium" value="medium" class="radio-group-size">
|
||||
<sl-radio value="small">Small</sl-radio>
|
||||
<sl-radio value="medium">Medium</sl-radio>
|
||||
<sl-radio value="large">Large</sl-radio>
|
||||
</sl-radio-group>
|
||||
|
||||
<script>
|
||||
const radioGroup = document.querySelector('.radio-group-size');
|
||||
|
||||
radioGroup.addEventListener('sl-change', () => {
|
||||
radioGroup.size = radioGroup.value;
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { useState } from 'react';
|
||||
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => {
|
||||
const [size, setSize] = useState('medium');
|
||||
|
||||
return (
|
||||
<>
|
||||
<SlRadioGroup
|
||||
label="Select an option"
|
||||
size={size}
|
||||
value={size}
|
||||
class="radio-group-size"
|
||||
onSlChange={event => setSize(event.target.value)}
|
||||
>
|
||||
<SlRadio value="small">Small</SlRadio>
|
||||
<SlRadio value="medium">Medium</SlRadio>
|
||||
<SlRadio value="large">Large</SlRadio>
|
||||
</SlRadioGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
:::tip
|
||||
[Radios](/components/radio) and [Radio Buttons](/components/radio-button) also have a `size` attribute. This can be useful in certain compositions, but it will be ignored when used inside of a Radio Group.
|
||||
:::
|
||||
|
||||
### Validation
|
||||
|
||||
Setting the `required` attribute to make selecting an option mandatory. If a value has not been selected, it will prevent the form from submitting and display an error message.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<form class="validation">
|
||||
<sl-radio-group label="Select an option" name="a" required>
|
||||
<sl-radio value="1">Option 1</sl-radio>
|
||||
@@ -124,7 +176,7 @@ Setting the `required` attribute to make selecting an option mandatory. If a val
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlButton, SlIcon, SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
const App = () => {
|
||||
function handleSubmit(event) {
|
||||
@@ -158,7 +210,7 @@ const App = () => {
|
||||
|
||||
Use the `setCustomValidity()` method to set a custom validation message. This will prevent the form from submitting and make the browser display the error message you provide. To clear the error, call this function with an empty string.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<form class="custom-validity">
|
||||
<sl-radio-group label="Select an option" name="a" value="1">
|
||||
<sl-radio value="1">Not me</sl-radio>
|
||||
@@ -175,7 +227,7 @@ Use the `setCustomValidity()` method to set a custom validation message. This wi
|
||||
const errorMessage = 'You must choose the last option';
|
||||
|
||||
// Set initial validity as soon as the element is defined
|
||||
customElements.whenDefined('sl-radio-group').then(() => {
|
||||
customElements.whenDefined('sl-radio').then(() => {
|
||||
radioGroup.setCustomValidity(errorMessage);
|
||||
});
|
||||
|
||||
@@ -193,7 +245,7 @@ Use the `setCustomValidity()` method to set a custom validation message. This wi
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { SlButton, SlIcon, SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
const App = () => {
|
||||
@@ -228,5 +280,3 @@ const App = () => {
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
[component-metadata:sl-radio-group]
|
||||
@@ -1,10 +1,13 @@
|
||||
# Radio
|
||||
|
||||
[component-header:sl-radio]
|
||||
---
|
||||
meta:
|
||||
title: Radio
|
||||
description: Radios allow the user to select a single option from a group.
|
||||
layout: component
|
||||
---
|
||||
|
||||
Radios are designed to be used with [radio groups](/components/radio-group).
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-radio-group label="Select an option" name="a" value="1">
|
||||
<sl-radio value="1">Option 1</sl-radio>
|
||||
<sl-radio value="2">Option 2</sl-radio>
|
||||
@@ -12,7 +15,7 @@ Radios are designed to be used with [radio groups](/components/radio-group).
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -24,7 +27,9 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
?> This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
:::tip
|
||||
This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -32,7 +37,7 @@ const App = () => (
|
||||
|
||||
To set the initial value and checked state, use the `value` attribute on the containing radio group.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-radio-group label="Select an option" name="a" value="3">
|
||||
<sl-radio value="1">Option 1</sl-radio>
|
||||
<sl-radio value="2">Option 2</sl-radio>
|
||||
@@ -40,7 +45,7 @@ To set the initial value and checked state, use the `value` attribute on the con
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -56,7 +61,7 @@ const App = () => (
|
||||
|
||||
Use the `disabled` attribute to disable a radio.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-radio-group label="Select an option" name="a" value="1">
|
||||
<sl-radio value="1">Option 1</sl-radio>
|
||||
<sl-radio value="2" disabled>Option 2</sl-radio>
|
||||
@@ -64,7 +69,7 @@ Use the `disabled` attribute to disable a radio.
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -80,12 +85,30 @@ const App = () => (
|
||||
|
||||
## Sizes
|
||||
|
||||
Use the `size` attribute to change a radio's size.
|
||||
Add the `size` attribute to the [Radio Group](/components/radio-group) to change the radios' size.
|
||||
|
||||
```html preview
|
||||
<sl-radio size="small">Small</sl-radio>
|
||||
<sl-radio size="medium">Medium</sl-radio>
|
||||
<sl-radio size="large">Large</sl-radio>
|
||||
<sl-radio-group size="small" value="1">
|
||||
<sl-radio value="1">Small 1</sl-radio>
|
||||
<sl-radio value="2">Small 2</sl-radio>
|
||||
<sl-radio value="3">Small 3</sl-radio>
|
||||
</sl-radio-group>
|
||||
|
||||
<br />
|
||||
|
||||
<sl-radio-group size="medium" value="1">
|
||||
<sl-radio value="1">Medium 1</sl-radio>
|
||||
<sl-radio value="2">Medium 2</sl-radio>
|
||||
<sl-radio value="3">Medium 3</sl-radio>
|
||||
</sl-radio-group>
|
||||
|
||||
<br />
|
||||
|
||||
<sl-radio-group size="large" value="1">
|
||||
<sl-radio value="1">Large 1</sl-radio>
|
||||
<sl-radio value="2">Large 2</sl-radio>
|
||||
<sl-radio value="3">Large 3</sl-radio>
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
@@ -93,13 +116,27 @@ import { SlRadio } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlRadio size="small">Small</SlRadio>
|
||||
<SlRadioGroup size="small" value="1">
|
||||
<SlRadio value="1">Small 1</SlRadio>
|
||||
<SlRadio value="2">Small 2</SlRadio>
|
||||
<SlRadio value="3">Small 3</SlRadio>
|
||||
</SlRadioGroup>
|
||||
|
||||
<br />
|
||||
<SlRadio size="medium">Medium</SlRadio>
|
||||
|
||||
<SlRadioGroup size="medium" value="1">
|
||||
<SlRadio value="1">Medium 1</SlRadio>
|
||||
<SlRadio value="2">Medium 2</SlRadio>
|
||||
<SlRadio value="3">Medium 3</SlRadio>
|
||||
</SlRadioGroup>
|
||||
|
||||
<br />
|
||||
<SlRadio size="large">Large</SlRadio>
|
||||
|
||||
<SlRadioGroup size="large" value="1">
|
||||
<SlRadio value="1">Large 1</SlRadio>
|
||||
<SlRadio value="2">Large 2</SlRadio>
|
||||
<SlRadio value="3">Large 3</SlRadio>
|
||||
</SlRadioGroup>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-radio]
|
||||
@@ -1,18 +1,23 @@
|
||||
# Range
|
||||
---
|
||||
meta:
|
||||
title: Range
|
||||
description: Ranges allow the user to select a single value within a given range using a slider.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-range]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-range></sl-range>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlRange />;
|
||||
```
|
||||
|
||||
?> This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
:::tip
|
||||
This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -20,11 +25,11 @@ const App = () => <SlRange />;
|
||||
|
||||
Use the `label` attribute to give the range an accessible label. For labels that contain HTML, use the `label` slot instead.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-range label="Volume" min="0" max="100"></sl-range>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlRange label="Volume" min={0} max={100} />;
|
||||
@@ -34,11 +39,11 @@ const App = () => <SlRange label="Volume" min={0} max={100} />;
|
||||
|
||||
Add descriptive help text to a range with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-range label="Volume" help-text="Controls the volume of the current song." min="0" max="100"></sl-range>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
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} />;
|
||||
@@ -48,11 +53,11 @@ const App = () => <SlRange label="Volume" help-text="Controls the volume of the
|
||||
|
||||
Use the `min` and `max` attributes to set the range's minimum and maximum values, respectively. The `step` attribute determines the value's interval when increasing and decreasing.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-range min="0" max="10" step="1"></sl-range>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlRange min={0} max={10} step={1} />;
|
||||
@@ -62,11 +67,11 @@ const App = () => <SlRange min={0} max={10} step={1} />;
|
||||
|
||||
Use the `disabled` attribute to disable a slider.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-range disabled></sl-range>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlRange disabled />;
|
||||
@@ -76,11 +81,11 @@ const App = () => <SlRange disabled />;
|
||||
|
||||
By default, the tooltip is shown on top. Set `tooltip` to `bottom` to show it below the slider.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-range tooltip="bottom"></sl-range>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlRange tooltip="bottom" />;
|
||||
@@ -90,11 +95,11 @@ const App = () => <SlRange tooltip="bottom" />;
|
||||
|
||||
To disable the tooltip, set `tooltip` to `none`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-range tooltip="none"></sl-range>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlRange tooltip="none" />;
|
||||
@@ -104,7 +109,7 @@ const App = () => <SlRange tooltip="none" />;
|
||||
|
||||
You can customize the active and inactive portions of the track using the `--track-color-active` and `--track-color-inactive` custom properties.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-range
|
||||
style="
|
||||
--track-color-active: var(--sl-color-primary-600);
|
||||
@@ -113,7 +118,9 @@ You can customize the active and inactive portions of the track using the `--tra
|
||||
></sl-range>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -126,11 +133,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Custom Track Offset
|
||||
|
||||
You can customize the initial offset of the active track using the `--track-active-offset` custom property.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-range
|
||||
min="-100"
|
||||
max="100"
|
||||
@@ -142,7 +151,9 @@ You can customize the initial offset of the active track using the `--track-acti
|
||||
></sl-range>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -158,11 +169,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Custom Tooltip Formatter
|
||||
|
||||
You can change the tooltip's content by setting the `tooltipFormatter` property to a function that accepts the range's value as an argument.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-range min="0" max="100" step="1" class="range-with-custom-formatter"></sl-range>
|
||||
|
||||
<script>
|
||||
@@ -171,10 +184,8 @@ You can change the tooltip's content by setting the `tooltipFormatter` property
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlRange min={0} max={100} step={1} tooltipFormatter={value => `Total - ${value}%`} />;
|
||||
```
|
||||
|
||||
[component-metadata:sl-range]
|
||||
@@ -1,12 +1,15 @@
|
||||
# Rating
|
||||
---
|
||||
meta:
|
||||
title: Rating
|
||||
description: Ratings give users a way to quickly view and provide feedback.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-rating]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-rating label="Rating"></sl-rating>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlRating label="Rating" />;
|
||||
@@ -18,11 +21,11 @@ const App = () => <SlRating label="Rating" />;
|
||||
|
||||
Ratings are commonly identified contextually, so labels aren't displayed. However, you should always provide one for assistive devices using the `label` attribute.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-rating label="Rate this component"></sl-rating>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlRating label="Rate this component" />;
|
||||
@@ -32,11 +35,11 @@ const App = () => <SlRating label="Rate this component" />;
|
||||
|
||||
Ratings are 0-5 by default. To change the maximum possible value, use the `max` attribute.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-rating label="Rating" max="3"></sl-rating>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlRating label="Rating" max={3} />;
|
||||
@@ -46,11 +49,11 @@ const App = () => <SlRating label="Rating" max={3} />;
|
||||
|
||||
Use the `precision` attribute to let users select fractional ratings.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-rating label="Rating" precision="0.5" value="2.5"></sl-rating>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlRating label="Rating" precision={0.5} value={2.5} />;
|
||||
@@ -60,25 +63,29 @@ const App = () => <SlRating label="Rating" precision={0.5} value={2.5} />;
|
||||
|
||||
Set the `--symbol-size` custom property to adjust the size.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-rating label="Rating" style="--symbol-size: 2rem;"></sl-rating>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlRating label="Rating" style={{ '--symbol-size': '2rem' }} />;
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Readonly
|
||||
|
||||
Use the `readonly` attribute to display a rating that users can't change.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-rating label="Rating" readonly value="3"></sl-rating>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlRating label="Rating" readonly value={3} />;
|
||||
@@ -88,11 +95,11 @@ const App = () => <SlRating label="Rating" readonly value={3} />;
|
||||
|
||||
Use the `disable` attribute to disable the rating.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-rating label="Rating" disabled value="3"></sl-rating>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlRating label="Rating" disabled value={3} />;
|
||||
@@ -104,7 +111,7 @@ Use the `sl-hover` event to detect when the user hovers over (or touch and drag)
|
||||
|
||||
The event has a payload with `phase` and `value` properties. The `phase` property tells when hovering starts, moves to a new value, and ends. The `value` property tells what the rating's value would be if the user were to commit to the hovered value.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="detect-hover">
|
||||
<sl-rating label="Rating"></sl-rating>
|
||||
<span></span>
|
||||
@@ -143,7 +150,7 @@ The event has a payload with `phase` and `value` properties. The `phase` propert
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -195,7 +202,7 @@ const App = () => {
|
||||
|
||||
You can provide custom icons by passing a function to the `getSymbol` property.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-rating label="Rating" class="rating-hearts" style="--symbol-color-active: #ff4136;"></sl-rating>
|
||||
|
||||
<script>
|
||||
@@ -204,7 +211,9 @@ You can provide custom icons by passing a function to the `getSymbol` property.
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -216,11 +225,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Value-based Icons
|
||||
|
||||
You can also use the `getSymbol` property to render different icons based on value.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-rating label="Rating" class="rating-emojis"></sl-rating>
|
||||
|
||||
<script>
|
||||
@@ -233,7 +244,7 @@ You can also use the `getSymbol` property to render different icons based on val
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
function getSymbol(value) {
|
||||
@@ -243,5 +254,3 @@ function getSymbol(value) {
|
||||
|
||||
const App = () => <SlRating label="Rating" getSymbol={getSymbol} />;
|
||||
```
|
||||
|
||||
[component-metadata:sl-rating]
|
||||
@@ -1,15 +1,18 @@
|
||||
# Relative Time
|
||||
|
||||
[component-header:sl-relative-time]
|
||||
---
|
||||
meta:
|
||||
title: Relative Time
|
||||
description: Outputs a localized time phrase relative to the current date and time.
|
||||
layout: component
|
||||
---
|
||||
|
||||
Localization is handled by the browser's [`Intl.RelativeTimeFormat` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat). No language packs are required.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<!-- Shoelace 2 release date 🎉 -->
|
||||
<sl-relative-time date="2020-07-15T09:17:00-04:00"></sl-relative-time>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRelativeTime } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlRelativeTime date="2020-07-15T09:17:00-04:00" />;
|
||||
@@ -17,7 +20,9 @@ const App = () => <SlRelativeTime date="2020-07-15T09:17:00-04:00" />;
|
||||
|
||||
The `date` attribute determines when the date/time is calculated from. It must be a string that [`Date.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) can interpret or a [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object set via JavaScript.
|
||||
|
||||
?> When using strings, avoid ambiguous dates such as `03/04/2020` which can be interpreted as March 4 or April 3 depending on the user's browser and locale. Instead, always use a valid [ISO 8601 date time string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse#Date_Time_String_Format) to ensure the date will be parsed properly by all clients.
|
||||
:::tip
|
||||
When using strings, avoid ambiguous dates such as `03/04/2020` which can be interpreted as March 4 or April 3 depending on the user's browser and locale. Instead, always use a valid [ISO 8601 date time string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse#Date_Time_String_Format) to ensure the date will be parsed properly by all clients.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -25,7 +30,7 @@ The `date` attribute determines when the date/time is calculated from. It must b
|
||||
|
||||
Use the `sync` attribute to update the displayed value automatically as time passes.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="relative-time-sync">
|
||||
<sl-relative-time sync></sl-relative-time>
|
||||
</div>
|
||||
@@ -38,7 +43,7 @@ Use the `sync` attribute to update the displayed value automatically as time pas
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRelativeTime } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const date = new Date(new Date().getTime() - 60000);
|
||||
@@ -50,13 +55,13 @@ const App = () => <SlRelativeTime date={date} sync />;
|
||||
|
||||
You can change how the time is displayed using the `format` attribute. Note that some locales may display the same values for `narrow` and `short` formats.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-relative-time date="2020-07-15T09:17:00-04:00" format="narrow"></sl-relative-time><br />
|
||||
<sl-relative-time date="2020-07-15T09:17:00-04:00" format="short"></sl-relative-time><br />
|
||||
<sl-relative-time date="2020-07-15T09:17:00-04:00" format="long"></sl-relative-time>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRelativeTime } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -74,7 +79,7 @@ const App = () => (
|
||||
|
||||
Use the `lang` attribute to set the desired locale.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
English: <sl-relative-time date="2020-07-15T09:17:00-04:00" lang="en-US"></sl-relative-time><br />
|
||||
Chinese: <sl-relative-time date="2020-07-15T09:17:00-04:00" lang="zh-CN"></sl-relative-time><br />
|
||||
German: <sl-relative-time date="2020-07-15T09:17:00-04:00" lang="de"></sl-relative-time><br />
|
||||
@@ -82,7 +87,7 @@ Greek: <sl-relative-time date="2020-07-15T09:17:00-04:00" lang="el"></sl-relativ
|
||||
Russian: <sl-relative-time date="2020-07-15T09:17:00-04:00" lang="ru"></sl-relative-time>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlRelativeTime } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -99,5 +104,3 @@ const App = () => (
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-relative-time]
|
||||
@@ -1,10 +1,13 @@
|
||||
# Resize Observer
|
||||
|
||||
[component-header:sl-resize-observer]
|
||||
---
|
||||
meta:
|
||||
title: Resize Observer
|
||||
description: The Resize Observer component offers a thin, declarative interface to the ResizeObserver API.
|
||||
layout: component
|
||||
---
|
||||
|
||||
The resize observer will report changes to the dimensions of the elements it wraps through the `sl-resize` event. When emitted, a collection of [`ResizeObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry) objects will be attached to `event.detail` that contains the target element and information about its dimensions.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="resize-observer-overview">
|
||||
<sl-resize-observer>
|
||||
<div>Resize this box and watch the console 👉</div>
|
||||
@@ -32,14 +35,14 @@ The resize observer will report changes to the dimensions of the elements it wra
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlResizeObserver } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
.resize-observer-overview div {
|
||||
display: flex;
|
||||
border: solid 2px var(--sl-input-border-color);
|
||||
align-items: center;
|
||||
display: flex;
|
||||
border: solid 2px var(--sl-input-border-color);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 4rem 2rem;
|
||||
@@ -58,5 +61,3 @@ const App = () => (
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-resize-observer]
|
||||
@@ -1,8 +1,11 @@
|
||||
# Select
|
||||
---
|
||||
meta:
|
||||
title: Select
|
||||
description: Selects allow you to choose items from a menu of predefined options.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-select]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-select>
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2">Option 2</sl-option>
|
||||
@@ -13,7 +16,7 @@
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -28,7 +31,9 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
?> This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
:::tip
|
||||
This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -36,7 +41,7 @@ const App = () => (
|
||||
|
||||
Use the `label` attribute to give the select an accessible label. For labels that contain HTML, use the `label` slot instead.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-select label="Select one">
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2">Option 2</sl-option>
|
||||
@@ -44,7 +49,7 @@ Use the `label` attribute to give the select an accessible label. For labels tha
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -60,7 +65,7 @@ const App = () => (
|
||||
|
||||
Add descriptive help text to a select with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-select label="Experience" help-text="Please tell us your skill level.">
|
||||
<sl-option value="1">Novice</sl-option>
|
||||
<sl-option value="2">Intermediate</sl-option>
|
||||
@@ -68,7 +73,7 @@ Add descriptive help text to a select with the `help-text` attribute. For help t
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -84,7 +89,7 @@ const App = () => (
|
||||
|
||||
Use the `placeholder` attribute to add a placeholder.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-select placeholder="Select one">
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2">Option 2</sl-option>
|
||||
@@ -92,7 +97,7 @@ Use the `placeholder` attribute to add a placeholder.
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -108,7 +113,7 @@ const App = () => (
|
||||
|
||||
Use the `clearable` attribute to make the control clearable. The clear button only appears when an option is selected.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-select clearable value="option-1">
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2">Option 2</sl-option>
|
||||
@@ -116,7 +121,7 @@ Use the `clearable` attribute to make the control clearable. The clear button on
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -132,7 +137,7 @@ const App = () => (
|
||||
|
||||
Add the `filled` attribute to draw a filled select.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-select filled>
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2">Option 2</sl-option>
|
||||
@@ -140,7 +145,7 @@ Add the `filled` attribute to draw a filled select.
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -156,7 +161,7 @@ const App = () => (
|
||||
|
||||
Use the `pill` attribute to give selects rounded edges.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-select pill>
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2">Option 2</sl-option>
|
||||
@@ -164,7 +169,7 @@ Use the `pill` attribute to give selects rounded edges.
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -180,7 +185,7 @@ const App = () => (
|
||||
|
||||
Use the `disabled` attribute to disable a select.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-select placeholder="Disabled" disabled>
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2">Option 2</sl-option>
|
||||
@@ -188,7 +193,7 @@ Use the `disabled` attribute to disable a select.
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -204,7 +209,7 @@ const App = () => (
|
||||
|
||||
To allow multiple options to be selected, use the `multiple` attribute. It's a good practice to use `clearable` when this option is enabled. To set multiple values at once, set `value` to a space-delimited list of values.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-select label="Select a Few" value="option-1 option-2 option-3" multiple clearable>
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2">Option 2</sl-option>
|
||||
@@ -215,7 +220,7 @@ To allow multiple options to be selected, use the `multiple` attribute. It's a g
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -230,13 +235,15 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
?> Note that multi-select options may wrap, causing the control to expand vertically. You can use the `max-options-visible` attribute to control the maximum number of selected options to show at once.
|
||||
:::tip
|
||||
Note that multi-select options may wrap, causing the control to expand vertically. You can use the `max-options-visible` attribute to control the maximum number of selected options to show at once.
|
||||
:::
|
||||
|
||||
### Setting Initial Values
|
||||
|
||||
Use the `value` attribute to set the initial selection. When using `multiple`, use space-delimited values to select more than one option.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-select value="option-1 option-2" multiple clearable>
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2">Option 2</sl-option>
|
||||
@@ -245,7 +252,7 @@ Use the `value` attribute to set the initial selection. When using `multiple`, u
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlDivider, SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -261,7 +268,7 @@ const App = () => (
|
||||
|
||||
Use `<sl-divider>` to group listbox items visually. You can also use `<small>` to provide labels, but they won't be announced by most assistive devices.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-select>
|
||||
<small>Section 1</small>
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
@@ -275,7 +282,7 @@ Use `<sl-divider>` to group listbox items visually. You can also use `<small>` t
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -294,7 +301,7 @@ const App = () => (
|
||||
|
||||
Use the `size` attribute to change a select's size. Note that size does not apply to listbox options.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-select placeholder="Small" size="small">
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2">Option 2</sl-option>
|
||||
@@ -318,7 +325,7 @@ Use the `size` attribute to change a select's size. Note that size does not appl
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -352,7 +359,7 @@ const App = () => (
|
||||
|
||||
The preferred placement of the select's listbox can be set with the `placement` attribute. Note that the actual position may vary to ensure the panel remains in the viewport. Valid placements are `top` and `bottom`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-select placement="top">
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2">Option 2</sl-option>
|
||||
@@ -360,7 +367,7 @@ The preferred placement of the select's listbox can be set with the `placement`
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import {
|
||||
SlOption,
|
||||
SlSelect
|
||||
@@ -379,7 +386,7 @@ const App = () => (
|
||||
|
||||
Use the `prefix` slot to prepend an icon to the control.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-select placeholder="Small" size="small" clearable>
|
||||
<sl-icon name="house" slot="prefix"></sl-icon>
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
@@ -402,7 +409,7 @@ Use the `prefix` slot to prepend an icon to the control.
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlIcon, SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -430,5 +437,3 @@ const App = () => (
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-select]
|
||||
@@ -1,12 +1,15 @@
|
||||
# Skeleton
|
||||
|
||||
[component-header:sl-skeleton]
|
||||
---
|
||||
meta:
|
||||
title: Skeleton
|
||||
description: Skeletons are used to provide a visual representation of where content will eventually be drawn.
|
||||
layout: component
|
||||
---
|
||||
|
||||
These are simple containers for scaffolding layouts that mimic what users will see when content has finished loading. This prevents large areas of empty space during asynchronous operations.
|
||||
|
||||
Skeletons try not to be opinionated, as there are endless possibilities for designing layouts. Therefore, you'll likely use more than one skeleton to create the effect you want. If you find yourself using them frequently, consider creating a template that renders them with the desired arrangement and styles.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="skeleton-overview">
|
||||
<header>
|
||||
<sl-skeleton></sl-skeleton>
|
||||
@@ -52,7 +55,7 @@ Skeletons try not to be opinionated, as there are endless possibilities for desi
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlSkeleton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -112,7 +115,7 @@ const App = () => (
|
||||
|
||||
There are two built-in effects, `sheen` and `pulse`. Effects are intentionally subtle, as they can be distracting when used extensively. The default is `none`, which displays a static, non-animated skeleton.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="skeleton-effects">
|
||||
<sl-skeleton effect="none"></sl-skeleton>
|
||||
None
|
||||
@@ -135,7 +138,7 @@ There are two built-in effects, `sheen` and `pulse`. Effects are intentionally s
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlSkeleton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -168,7 +171,7 @@ const App = () => (
|
||||
|
||||
Use multiple skeletons and some clever styles to simulate paragraphs.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="skeleton-paragraphs">
|
||||
<sl-skeleton></sl-skeleton>
|
||||
<sl-skeleton></sl-skeleton>
|
||||
@@ -196,7 +199,7 @@ Use multiple skeletons and some clever styles to simulate paragraphs.
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlSkeleton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -236,7 +239,7 @@ const App = () => (
|
||||
|
||||
Set a matching width and height to make a circle, square, or rounded avatar skeleton.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="skeleton-avatars">
|
||||
<sl-skeleton></sl-skeleton>
|
||||
<sl-skeleton></sl-skeleton>
|
||||
@@ -261,7 +264,7 @@ Set a matching width and height to make a circle, square, or rounded avatar skel
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlSkeleton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -275,7 +278,7 @@ const css = `
|
||||
.skeleton-avatars sl-skeleton:nth-child(1) {
|
||||
--border-radius: 0;
|
||||
}
|
||||
|
||||
|
||||
.skeleton-avatars sl-skeleton:nth-child(2) {
|
||||
--border-radius: var(--sl-border-radius-medium);
|
||||
}
|
||||
@@ -298,7 +301,7 @@ const App = () => (
|
||||
|
||||
Use the `--border-radius` custom property to make circles, squares, and rectangles. For more complex shapes, you can apply `clip-path` to the `indicator` part. [Try Clippy](https://bennettfeely.com/clippy/) if you need help generating custom shapes.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="skeleton-shapes">
|
||||
<sl-skeleton class="square"></sl-skeleton>
|
||||
<sl-skeleton class="circle"></sl-skeleton>
|
||||
@@ -356,7 +359,7 @@ Use the `--border-radius` custom property to make circles, squares, and rectangl
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlSkeleton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -413,11 +416,13 @@ const App = () => (
|
||||
|
||||
Set the `--color` and `--sheen-color` custom properties to adjust the skeleton's color.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-skeleton effect="sheen" style="--color: tomato; --sheen-color: #ffb094;"></sl-skeleton>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlSkeleton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -431,7 +436,7 @@ const css = `
|
||||
.skeleton-avatars sl-skeleton:nth-child(1) {
|
||||
--border-radius: 0;
|
||||
}
|
||||
|
||||
|
||||
.skeleton-avatars sl-skeleton:nth-child(2) {
|
||||
--border-radius: var(--sl-border-radius-medium);
|
||||
}
|
||||
@@ -440,4 +445,4 @@ const css = `
|
||||
const App = () => <SlSkeleton effect="sheen" style={{ '--color': 'tomato', '--sheen-color': '#ffb094' }} />;
|
||||
```
|
||||
|
||||
[component-metadata:sl-skeleton]
|
||||
{% endraw %}
|
||||
@@ -1,12 +1,15 @@
|
||||
# Spinner
|
||||
---
|
||||
meta:
|
||||
title: Spinner
|
||||
description: Spinners are used to show the progress of an indeterminate operation.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-spinner]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-spinner></sl-spinner>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlSpinner } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlSpinner />;
|
||||
@@ -18,13 +21,15 @@ const App = () => <SlSpinner />;
|
||||
|
||||
Spinners are sized based on the current font size. To change their size, set the `font-size` property on the spinner itself or on a parent element as shown below.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-spinner></sl-spinner>
|
||||
<sl-spinner style="font-size: 2rem;"></sl-spinner>
|
||||
<sl-spinner style="font-size: 3rem;"></sl-spinner>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlSpinner } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -36,15 +41,19 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Track Width
|
||||
|
||||
The width of the spinner's track can be changed by setting the `--track-width` custom property.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-spinner style="font-size: 50px; --track-width: 10px;"></sl-spinner>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlSpinner } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -57,15 +66,19 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Color
|
||||
|
||||
The spinner's colors can be changed by setting the `--indicator-color` and `--track-color` custom properties.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-spinner style="font-size: 3rem; --indicator-color: deeppink; --track-color: pink;"></sl-spinner>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlSpinner } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -79,4 +92,4 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-spinner]
|
||||
{% endraw %}
|
||||
@@ -1,8 +1,11 @@
|
||||
# Split Panel
|
||||
---
|
||||
meta:
|
||||
title: Split Panel
|
||||
description: Split panels display two adjacent panels, allowing the user to reposition them.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-split-panel]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-split-panel>
|
||||
<div
|
||||
slot="start"
|
||||
@@ -19,7 +22,9 @@
|
||||
</sl-split-panel>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlSplitPanel } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -52,13 +57,15 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
## Examples
|
||||
|
||||
### Initial Position
|
||||
|
||||
To set the initial position, use the `position` attribute. If no position is provided, it will default to 50% of the available space.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-split-panel position="75">
|
||||
<div
|
||||
slot="start"
|
||||
@@ -79,7 +86,7 @@ To set the initial position, use the `position` attribute. If no position is pro
|
||||
|
||||
To set the initial position in pixels instead of a percentage, use the `position-in-pixels` attribute.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-split-panel position-in-pixels="150">
|
||||
<div
|
||||
slot="start"
|
||||
@@ -96,7 +103,9 @@ To set the initial position in pixels instead of a percentage, use the `position
|
||||
</sl-split-panel>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlSplitPanel } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -129,11 +138,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Vertical
|
||||
|
||||
Add the `vertical` attribute to render the split panel in a vertical orientation where the start and end panels are stacked. You also need to set a height when using the vertical orientation.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-split-panel vertical style="height: 400px;">
|
||||
<div
|
||||
slot="start"
|
||||
@@ -150,7 +161,9 @@ Add the `vertical` attribute to render the split panel in a vertical orientation
|
||||
</sl-split-panel>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlSplitPanel } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -183,11 +196,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Snapping
|
||||
|
||||
To snap panels at specific positions while dragging, add the `snap` attribute with one or more space-separated values. Values must be in pixels or percentages. For example, to snap the panel at `100px` and `50%`, use `snap="100px 50%"`. You can also customize how close the divider must be before snapping with the `snap-threshold` attribute.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="split-panel-snapping">
|
||||
<sl-split-panel snap="100px 50%">
|
||||
<div
|
||||
@@ -234,7 +249,9 @@ To snap panels at specific positions while dragging, add the `snap` attribute wi
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlSplitPanel } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -301,11 +318,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Disabled
|
||||
|
||||
Add the `disabled` attribute to prevent the divider from being repositioned.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-split-panel disabled>
|
||||
<div
|
||||
slot="start"
|
||||
@@ -322,7 +341,9 @@ Add the `disabled` attribute to prevent the divider from being repositioned.
|
||||
</sl-split-panel>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlSplitPanel } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -355,13 +376,15 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Setting the Primary Panel
|
||||
|
||||
By default, both panels will grow or shrink proportionally when the host element is resized. If a primary panel is designated, it will maintain its size and the secondary panel will grow or shrink to fit the remaining space. You can set the primary panel to `start` or `end` using the `primary` attribute.
|
||||
|
||||
Try resizing the example below with each option and notice how the panels respond.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="split-panel-primary">
|
||||
<sl-split-panel>
|
||||
<div
|
||||
@@ -394,7 +417,9 @@ Try resizing the example below with each option and notice how the panels respon
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlSplitPanel, SlSelect, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
@@ -445,13 +470,15 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Min & Max
|
||||
|
||||
To set a minimum or maximum size of the primary panel, use the `--min` and `--max` custom properties. Since the secondary panel is flexible, size constraints can only be applied to the primary panel. If no primary panel is designated, these constraints will be applied to the `start` panel.
|
||||
|
||||
This examples demonstrates how you can ensure both panels are at least 150px using `--min`, `--max`, and the `calc()` function.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-split-panel style="--min: 150px; --max: calc(100% - 150px);">
|
||||
<div
|
||||
slot="start"
|
||||
@@ -468,7 +495,9 @@ This examples demonstrates how you can ensure both panels are at least 150px usi
|
||||
</sl-split-panel>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlSplitPanel } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -501,11 +530,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Nested Split Panels
|
||||
|
||||
Create complex layouts that can be repositioned independently by nesting split panels.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-split-panel>
|
||||
<div
|
||||
slot="start"
|
||||
@@ -532,7 +563,9 @@ Create complex layouts that can be repositioned independently by nesting split p
|
||||
</sl-split-panel>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlSplitPanel } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -581,11 +614,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Customizing the Divider
|
||||
|
||||
You can target the `divider` part to apply CSS properties to the divider. To add a custom handle, slot an icon into the `divider` slot. When customizing the divider, make sure to think about focus styles for keyboard users.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-split-panel style="--divider-width: 20px;">
|
||||
<sl-icon slot="divider" name="grip-vertical"></sl-icon>
|
||||
<div
|
||||
@@ -603,7 +638,9 @@ You can target the `divider` part to apply CSS properties to the divider. To add
|
||||
</sl-split-panel>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlSplitPanel, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -637,9 +674,11 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
Here's a more elaborate example that changes the divider's color and width and adds a styled handle.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="split-panel-divider">
|
||||
<sl-split-panel>
|
||||
<sl-icon slot="divider" name="grip-vertical"></sl-icon>
|
||||
@@ -686,7 +725,9 @@ Here's a more elaborate example that changes the divider's color and width and a
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlSplitPanel, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -753,4 +794,4 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-split-panel]
|
||||
{% endraw %}
|
||||
@@ -1,18 +1,23 @@
|
||||
# Switch
|
||||
---
|
||||
meta:
|
||||
title: Switch
|
||||
description: Switches allow the user to toggle an option on or off.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-switch]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-switch>Switch</sl-switch>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlSwitch>Switch</SlSwitch>;
|
||||
```
|
||||
|
||||
?> This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
:::tip
|
||||
This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -20,11 +25,11 @@ const App = () => <SlSwitch>Switch</SlSwitch>;
|
||||
|
||||
Use the `checked` attribute to activate the switch.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-switch checked>Checked</sl-switch>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlSwitch checked>Checked</SlSwitch>;
|
||||
@@ -34,21 +39,21 @@ const App = () => <SlSwitch checked>Checked</SlSwitch>;
|
||||
|
||||
Use the `disabled` attribute to disable the switch.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-switch disabled>Disabled</sl-switch>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => <SlSwitch disabled>Disabled</SlSwitch>;
|
||||
```
|
||||
|
||||
## Sizes
|
||||
### Sizes
|
||||
|
||||
Use the `size` attribute to change a switch's size.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-switch size="small">Small</sl-switch>
|
||||
<br />
|
||||
<sl-switch size="medium">Medium</sl-switch>
|
||||
@@ -56,7 +61,7 @@ Use the `size` attribute to change a switch's size.
|
||||
<sl-switch size="large">Large</sl-switch>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -74,11 +79,13 @@ const App = () => (
|
||||
|
||||
Use the available custom properties to change how the switch is styled.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-switch style="--width: 80px; --height: 40px; --thumb-size: 36px;">Really big</sl-switch>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -92,4 +99,4 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-switch]
|
||||
{% endraw %}
|
||||
@@ -1,10 +1,13 @@
|
||||
# Tab Group
|
||||
|
||||
[component-header:sl-tab-group]
|
||||
---
|
||||
meta:
|
||||
title: Tab Group
|
||||
description: Tab groups organize content into a container that shows one section at a time.
|
||||
layout: component
|
||||
---
|
||||
|
||||
Tab groups make use of [tabs](/components/tab) and [tab panels](/components/tab-panel). Each tab must be slotted into the `nav` slot and its `panel` must refer to a tab panel of the same name.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-tab-group>
|
||||
<sl-tab slot="nav" panel="general">General</sl-tab>
|
||||
<sl-tab slot="nav" panel="custom">Custom</sl-tab>
|
||||
@@ -18,7 +21,7 @@ Tab groups make use of [tabs](/components/tab) and [tab panels](/components/tab-
|
||||
</sl-tab-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlTab, SlTabGroup, SlTabPanel } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -50,7 +53,7 @@ const App = () => (
|
||||
|
||||
Tabs can be shown on the bottom by setting `placement` to `bottom`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-tab-group placement="bottom">
|
||||
<sl-tab slot="nav" panel="general">General</sl-tab>
|
||||
<sl-tab slot="nav" panel="custom">Custom</sl-tab>
|
||||
@@ -64,7 +67,7 @@ Tabs can be shown on the bottom by setting `placement` to `bottom`.
|
||||
</sl-tab-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlTab, SlTabGroup, SlTabPanel } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -94,7 +97,7 @@ const App = () => (
|
||||
|
||||
Tabs can be shown on the starting side by setting `placement` to `start`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-tab-group placement="start">
|
||||
<sl-tab slot="nav" panel="general">General</sl-tab>
|
||||
<sl-tab slot="nav" panel="custom">Custom</sl-tab>
|
||||
@@ -108,7 +111,7 @@ Tabs can be shown on the starting side by setting `placement` to `start`.
|
||||
</sl-tab-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlTab, SlTabGroup, SlTabPanel } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -138,7 +141,7 @@ const App = () => (
|
||||
|
||||
Tabs can be shown on the ending side by setting `placement` to `end`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-tab-group placement="end">
|
||||
<sl-tab slot="nav" panel="general">General</sl-tab>
|
||||
<sl-tab slot="nav" panel="custom">Custom</sl-tab>
|
||||
@@ -152,7 +155,7 @@ Tabs can be shown on the ending side by setting `placement` to `end`.
|
||||
</sl-tab-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlTab, SlTabGroup, SlTabPanel } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -182,7 +185,7 @@ const App = () => (
|
||||
|
||||
Add the `closable` attribute to a tab to show a close button. This example shows how you can dynamically remove tabs from the DOM when the close button is activated.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-tab-group class="tabs-closable">
|
||||
<sl-tab slot="nav" panel="general">General</sl-tab>
|
||||
<sl-tab slot="nav" panel="closable-1" closable>Closable 1</sl-tab>
|
||||
@@ -214,7 +217,7 @@ Add the `closable` attribute to a tab to show a close button. This example shows
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlTab, SlTabGroup, SlTabPanel } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => {
|
||||
@@ -260,7 +263,7 @@ const App = () => {
|
||||
|
||||
When there are more tabs than horizontal space allows, the nav will be scrollable.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-tab-group>
|
||||
<sl-tab slot="nav" panel="tab-1">Tab 1</sl-tab>
|
||||
<sl-tab slot="nav" panel="tab-2">Tab 2</sl-tab>
|
||||
@@ -306,7 +309,7 @@ When there are more tabs than horizontal space allows, the nav will be scrollabl
|
||||
</sl-tab-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlTab, SlTabGroup, SlTabPanel } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -398,9 +401,9 @@ const App = () => (
|
||||
|
||||
### Manual Activation
|
||||
|
||||
When focused, keyboard users can press <kbd>Left</kbd> or <kbd>Right</kbd> to select the desired tab. By default, the corresponding tab panel will be shown immediately (automatic activation). You can change this behavior by setting `activation="manual"` which will require the user to press <kbd>Space</kbd> or <kbd>Enter</kbd> before showing the tab panel (manual activation).
|
||||
When focused, keyboard users can press [[Left]] or [[Right]] to select the desired tab. By default, the corresponding tab panel will be shown immediately (automatic activation). You can change this behavior by setting `activation="manual"` which will require the user to press [[Space]] or [[Enter]] before showing the tab panel (manual activation).
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-tab-group activation="manual">
|
||||
<sl-tab slot="nav" panel="general">General</sl-tab>
|
||||
<sl-tab slot="nav" panel="custom">Custom</sl-tab>
|
||||
@@ -414,7 +417,7 @@ When focused, keyboard users can press <kbd>Left</kbd> or <kbd>Right</kbd> to se
|
||||
</sl-tab-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlTab, SlTabGroup, SlTabPanel } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -439,5 +442,3 @@ const App = () => (
|
||||
</SlTabGroup>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-tab-group]
|
||||
@@ -1,8 +1,11 @@
|
||||
# Tab Panel
|
||||
---
|
||||
meta:
|
||||
title: Tab Panel
|
||||
description: Tab panels are used inside tab groups to display tabbed content.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-tab-panel]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-tab-group>
|
||||
<sl-tab slot="nav" panel="general">General</sl-tab>
|
||||
<sl-tab slot="nav" panel="custom">Custom</sl-tab>
|
||||
@@ -16,7 +19,7 @@
|
||||
</sl-tab-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlTab, SlTabGroup, SlTabPanel } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -42,6 +45,6 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
?> Additional demonstrations can be found in the [tab group examples](/components/tab-group).
|
||||
|
||||
[component-metadata:sl-tab-panel]
|
||||
:::tip
|
||||
Additional demonstrations can be found in the [tab group examples](/components/tab-group).
|
||||
:::
|
||||
@@ -1,15 +1,18 @@
|
||||
# Tab
|
||||
---
|
||||
meta:
|
||||
title: Tab
|
||||
description: Tabs are used inside tab groups to represent and activate tab panels.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-tab]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-tab>Tab</sl-tab>
|
||||
<sl-tab active>Active</sl-tab>
|
||||
<sl-tab closable>Closable</sl-tab>
|
||||
<sl-tab disabled>Disabled</sl-tab>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlTab } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -22,6 +25,6 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
?> Additional demonstrations can be found in the [tab group examples](/components/tab-group).
|
||||
|
||||
[component-metadata:sl-tab]
|
||||
:::tip
|
||||
Additional demonstrations can be found in the [tab group examples](/components/tab-group).
|
||||
:::
|
||||
@@ -1,8 +1,11 @@
|
||||
# Tag
|
||||
---
|
||||
meta:
|
||||
title: Tag
|
||||
description: Tags are used as labels to organize things or to indicate a selection.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-tag]
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-tag variant="primary">Primary</sl-tag>
|
||||
<sl-tag variant="success">Success</sl-tag>
|
||||
<sl-tag variant="neutral">Neutral</sl-tag>
|
||||
@@ -10,7 +13,7 @@
|
||||
<sl-tag variant="danger">Danger</sl-tag>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlTag } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -30,13 +33,13 @@ const App = () => (
|
||||
|
||||
Use the `size` attribute to change a tab's size.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-tag size="small">Small</sl-tag>
|
||||
<sl-tag size="medium">Medium</sl-tag>
|
||||
<sl-tag size="large">Large</sl-tag>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlTag } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -52,13 +55,13 @@ const App = () => (
|
||||
|
||||
Use the `pill` attribute to give tabs rounded edges.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-tag size="small" pill>Small</sl-tag>
|
||||
<sl-tag size="medium" pill>Medium</sl-tag>
|
||||
<sl-tag size="large" pill>Large</sl-tag>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlTag } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -80,7 +83,7 @@ const App = () => (
|
||||
|
||||
Use the `removable` attribute to add a remove button to the tag.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="tags-removable">
|
||||
<sl-tag size="small" removable>Small</sl-tag>
|
||||
<sl-tag size="medium" removable>Medium</sl-tag>
|
||||
@@ -104,7 +107,7 @@ Use the `removable` attribute to add a remove button to the tag.
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { SlTag } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
@@ -141,5 +144,3 @@ const App = () => {
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
[component-metadata:sl-tag]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user