Compare commits
153 Commits
themer
...
demo-layou
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f278ce1d6d | ||
|
|
0bbf9bb275 | ||
|
|
4deb928682 | ||
|
|
4066e8c591 | ||
|
|
8eb6ee6609 | ||
|
|
620525891d | ||
|
|
384147502f | ||
|
|
730e20bd38 | ||
|
|
9b3669c428 | ||
|
|
b003273175 | ||
|
|
faae5c21fd | ||
|
|
af89149939 | ||
|
|
9b5ceb989c | ||
|
|
7862c374dc | ||
|
|
03508ec523 | ||
|
|
aadf4830c6 | ||
|
|
cd282e50ef | ||
|
|
57ddd25662 | ||
|
|
a37f2d594e | ||
|
|
6fbf921f4b | ||
|
|
c11f3d468b | ||
|
|
306eefa44b | ||
|
|
cd237d3057 | ||
|
|
c7757b8cbe | ||
|
|
b53c1d940a | ||
|
|
edd62490f8 | ||
|
|
96a381d3a3 | ||
|
|
a2a72de2cf | ||
|
|
9a51e69320 | ||
|
|
07be57847d | ||
|
|
0095ca5fe7 | ||
|
|
4b8a86f0e2 | ||
|
|
41de947779 | ||
|
|
cb1c423aea | ||
|
|
93306c99ce | ||
|
|
5f8c69064c | ||
|
|
f51a09ddf0 | ||
|
|
11337197d7 | ||
|
|
f1739309eb | ||
|
|
5007924dbd | ||
|
|
92533c0297 | ||
|
|
94558e6ea5 | ||
|
|
bab673fbdc | ||
|
|
7b20f9c87a | ||
|
|
ddbd91ad89 | ||
|
|
130844df1c | ||
|
|
d6cfa1ab24 | ||
|
|
ec613f8d32 | ||
|
|
52e2518365 | ||
|
|
9b7aad71a9 | ||
|
|
b7541d240b | ||
|
|
c67da1e818 | ||
|
|
265e523a56 | ||
|
|
bfe05d0692 | ||
|
|
651eae8cb6 | ||
|
|
8c8b3f1853 | ||
|
|
d1ed504dd8 | ||
|
|
5335c9421a | ||
|
|
1b380f3f1d | ||
|
|
d166bc0e48 | ||
|
|
595cc303e7 | ||
|
|
4260b27fd2 | ||
|
|
7c6f018c5b | ||
|
|
b892f1f86a | ||
|
|
a0e9125d61 | ||
|
|
5b741006a1 | ||
|
|
30bfabc397 | ||
|
|
4e1bea7d94 | ||
|
|
f2bb9fefee | ||
|
|
9987ce8d4f | ||
|
|
4ace1efbe0 | ||
|
|
d7920f2e75 | ||
|
|
4198cf0f15 | ||
|
|
7562905bbf | ||
|
|
4914cdb352 | ||
|
|
0c95c70192 | ||
|
|
430730f24a | ||
|
|
ab0c615e10 | ||
|
|
716ab94069 | ||
|
|
ccc6f1aa23 | ||
|
|
893f8b2740 | ||
|
|
00d5164912 | ||
|
|
fc9151e573 | ||
|
|
cf97bc3c6c | ||
|
|
eb9dbf097c | ||
|
|
5422e6431c | ||
|
|
f3a921022e | ||
|
|
be1440aee0 | ||
|
|
fe23a7ddb8 | ||
|
|
f53a643cf3 | ||
|
|
3f604fcee1 | ||
|
|
d8b6db8c5b | ||
|
|
31215dbda4 | ||
|
|
f00e8c3a65 | ||
|
|
a4f8bf94ee | ||
|
|
8ae1303188 | ||
|
|
ffc0248e4c | ||
|
|
81d3f22da6 | ||
|
|
0fa8e6f550 | ||
|
|
a67d1df89a | ||
|
|
0fe400c6f4 | ||
|
|
349aa45d2b | ||
|
|
fcf0a136f2 | ||
|
|
8acfc4c9de | ||
|
|
4f8417806c | ||
|
|
65cb3175af | ||
|
|
06135e686b | ||
|
|
340351ca4b | ||
|
|
5701bef6e9 | ||
|
|
62417ed1d1 | ||
|
|
545162eaae | ||
|
|
77a8c418ea | ||
|
|
641e92a340 | ||
|
|
3f8535e7b8 | ||
|
|
81a66df7e4 | ||
|
|
ae2480dfe2 | ||
|
|
c95b0b6c66 | ||
|
|
dee01269ad | ||
|
|
e11eb363aa | ||
|
|
0d33cabec4 | ||
|
|
b5d9b49b27 | ||
|
|
1b654c7c85 | ||
|
|
4e53ce870d | ||
|
|
932e2e7566 | ||
|
|
e76a1dc1f6 | ||
|
|
38302a7c28 | ||
|
|
71e5b10f3b | ||
|
|
3eda5510c3 | ||
|
|
32494e783c | ||
|
|
cdf38fe147 | ||
|
|
302c174055 | ||
|
|
fb044aae89 | ||
|
|
bf299d8234 | ||
|
|
30a3164a96 | ||
|
|
2a22fb683c | ||
|
|
325ddafb13 | ||
|
|
fcb2c7868c | ||
|
|
46b198866d | ||
|
|
9a4da9b763 | ||
|
|
7869144f5e | ||
|
|
2d7d400040 | ||
|
|
6f5e5a2433 | ||
|
|
e59a4659d8 | ||
|
|
2f9732fc3d | ||
|
|
fdede79155 | ||
|
|
3277284473 | ||
|
|
b2b8d0d941 | ||
|
|
91bfd38a9a | ||
|
|
f4971456d0 | ||
|
|
cc18a90a86 | ||
|
|
60b6803437 | ||
|
|
2b57157502 | ||
|
|
967208d69b |
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,4 +1,7 @@
|
||||
contact_links:
|
||||
- name: Feature Requests
|
||||
url: https://github.com/shoelace-style/shoelace/discussions/categories/ideas
|
||||
about: All requests for new features should go here.
|
||||
- name: Help & Support
|
||||
url: https://github.com/shoelace-style/shoelace/discussions/categories/help
|
||||
about: Please don't create issues for personal help requests. Instead, ask your question on the discussion forum.
|
||||
|
||||
15
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,15 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for this project.
|
||||
title: ''
|
||||
labels: feature
|
||||
---
|
||||
|
||||
### What issue are you having?
|
||||
Provide a clear and concise description of the problem you're facing.
|
||||
|
||||
### Describe the solution you'd like
|
||||
How would you like to see the library solve it?
|
||||
|
||||
### Describe alternatives you've considered
|
||||
In what ways have you tried to solve this with the current version?
|
||||
2
.gitignore
vendored
@@ -5,6 +5,8 @@ package.json
|
||||
package-lock.json
|
||||
dist
|
||||
docs/assets/images/sprite.svg
|
||||
docs/public/pagefind
|
||||
node_modules
|
||||
src/react
|
||||
cdn
|
||||
.astro
|
||||
|
||||
2
.vscode/settings.json
vendored
@@ -2,7 +2,7 @@
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"debug.enableStatusBarColor": false
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"dropdowns",
|
||||
"easings",
|
||||
"endraw",
|
||||
"endregion",
|
||||
"enterkeyhint",
|
||||
"eqeqeq",
|
||||
"erroneou",
|
||||
@@ -102,6 +103,7 @@
|
||||
"monospace",
|
||||
"mousedown",
|
||||
"mousemove",
|
||||
"mouseout",
|
||||
"mouseup",
|
||||
"multiselectable",
|
||||
"nextjs",
|
||||
@@ -113,6 +115,7 @@
|
||||
"Numberish",
|
||||
"oklab",
|
||||
"oklch",
|
||||
"onscrollend",
|
||||
"outdir",
|
||||
"ParamagicDev",
|
||||
"peta",
|
||||
@@ -170,10 +173,13 @@
|
||||
"valpha",
|
||||
"valuenow",
|
||||
"valuetext",
|
||||
"viewports",
|
||||
"Vuejs",
|
||||
"WCAG",
|
||||
"webawesome",
|
||||
"WEBP",
|
||||
"Webpacker"
|
||||
"Webpacker",
|
||||
"xmark"
|
||||
],
|
||||
"ignorePaths": [
|
||||
"package.json",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as path from 'path';
|
||||
import { customElementJetBrainsPlugin } from 'custom-element-jet-brains-integration';
|
||||
import { customElementVsCodePlugin } from 'custom-element-vs-code-integration';
|
||||
import { customElementVuejsPlugin } from 'custom-element-vuejs-integration';
|
||||
import { parse } from 'comment-parser';
|
||||
import { pascalCase } from 'pascal-case';
|
||||
import commandLineArgs from 'command-line-args';
|
||||
@@ -38,6 +39,7 @@ export default {
|
||||
customElementsManifest.package = { name, description, version, author, homepage, license };
|
||||
}
|
||||
},
|
||||
|
||||
// Infer tag names because we no longer use @customElement decorators.
|
||||
{
|
||||
name: 'wa-infer-tag-names',
|
||||
@@ -66,6 +68,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Parse custom jsDoc tags
|
||||
{
|
||||
name: 'wa-custom-tags',
|
||||
@@ -137,6 +140,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'wa-react-event-names',
|
||||
analyzePhase({ ts, node, moduleDoc }) {
|
||||
@@ -155,6 +159,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'wa-translate-module-paths',
|
||||
packageLinkPhase({ customElementsManifest }) {
|
||||
@@ -191,6 +196,7 @@ export default {
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Generate custom VS Code data
|
||||
customElementVsCodePlugin({
|
||||
outdir,
|
||||
@@ -202,15 +208,23 @@ export default {
|
||||
}
|
||||
]
|
||||
}),
|
||||
|
||||
customElementJetBrainsPlugin({
|
||||
outdir: './dist',
|
||||
excludeCss: true,
|
||||
packageJson: false,
|
||||
referencesTemplate: (_, tag) => {
|
||||
return {
|
||||
name: 'Documentation',
|
||||
url: `https://shoelace.style/components/${tag.replace('wa-', '')}`
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
customElementVuejsPlugin({
|
||||
outdir: './dist/types/vue',
|
||||
fileName: 'index.d.ts',
|
||||
componentTypePath: (_, tag) => `../../components/${tag.replace('wa-', '')}/${tag.replace('wa-', '')}.component.js`
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
4
docs/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
11
docs/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,349 +0,0 @@
|
||||
{% extends "default.njk" %}
|
||||
|
||||
{# Find the component based on the `tag` front matter #}
|
||||
{% set component = getComponent('wa-' + page.fileSlug) %}
|
||||
|
||||
{% block content %}
|
||||
{# Determine the badge variant #}
|
||||
{% if component.status == 'stable' %}
|
||||
{% set badgeVariant = 'brand' %}
|
||||
{% 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">
|
||||
<wa-badge variant="neutral" pill>
|
||||
Since {{component.since or '?' }}
|
||||
</wa-badge>
|
||||
<wa-badge variant="{{ badgeVariant }}" pill style="text-transform: capitalize;">
|
||||
{{ component.status }}
|
||||
</wa-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>
|
||||
|
||||
<wa-tab-group>
|
||||
<wa-tab slot="nav" panel="script">Script</wa-tab>
|
||||
<wa-tab slot="nav" panel="import">Import</wa-tab>
|
||||
<wa-tab slot="nav" panel="bundler">Bundler</wa-tab>
|
||||
<wa-tab slot="nav" panel="react">React</wa-tab>
|
||||
|
||||
<wa-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>
|
||||
</wa-tab-panel>
|
||||
|
||||
<wa-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>
|
||||
</wa-tab-panel>
|
||||
|
||||
<wa-tab-panel name="bundler">
|
||||
<p>
|
||||
To import this component using <a href="{{ rootUrl('/getting-started/installation#bundling') }}">a bundler</a>:
|
||||
</p>
|
||||
<pre><code class="language-js">import '@shoelace-style/shoelace/{{ meta.npmdir }}/{{ component.path }}';</code></pre>
|
||||
</wa-tab-panel>
|
||||
|
||||
<wa-tab-panel name="react">
|
||||
<p>
|
||||
To import this component as a <a href="/frameworks/react">React component</a>:
|
||||
</p>
|
||||
<pre><code class="language-js">import {{ component.name }} from '@shoelace-style/shoelace/{{ meta.npmdir }}/react/{{ component.tagNameWithoutPrefix }}';</code></pre>
|
||||
</wa-tab-panel>
|
||||
</wa-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 | length > 0 %}
|
||||
{% if prop.attribute != prop.name %}
|
||||
<br>
|
||||
<wa-tooltip content="This attribute is different from its property">
|
||||
<small>
|
||||
<code class="nowrap">
|
||||
{{ prop.attribute }}
|
||||
</code>
|
||||
</small>
|
||||
</wa-tooltip>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ prop.description | markdownInline | safe }}
|
||||
</td>
|
||||
<td style="text-align: center;">
|
||||
{% if prop.reflects %}
|
||||
<wa-icon label="yes" name="check-lg"></wa-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#attributes-and-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/customizing/#css-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/customizing#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 %}
|
||||
@@ -1,129 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html
|
||||
lang="en"
|
||||
data-layout="{{ layout }}"
|
||||
data-wa-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/favicon.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) }}" />
|
||||
|
||||
{# Web Awesome #}
|
||||
<link rel="stylesheet" href="/dist/themes/applied.css" />
|
||||
<link rel="stylesheet" href="/dist/themes/forms.css" />
|
||||
<link id="theme-stylesheet" rel="stylesheet" href="/dist/themes/default.css" />
|
||||
<script type="module" src="/dist/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('wa-theme-default-dark', theme === 'dark' || (theme === 'auto' && prefersDark));
|
||||
})();
|
||||
</script>
|
||||
|
||||
{# Web Fonts #}
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,500;0,600;1,400;1,500;1,600&family=Noto+Sans+Mono&display=swap" rel="stylesheet">
|
||||
|
||||
{# 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="wa-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>
|
||||
|
||||
<aside id="sidebar" data-preserve-scroll>
|
||||
<header>
|
||||
<a href="/">
|
||||
{% include 'logo.njk' %}
|
||||
</a>
|
||||
<div class="sidebar-version">
|
||||
{{ meta.version }}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="sidebar-buttons">
|
||||
<wa-button size="small" class="repo-button repo-button--github" href="https://github.com/shoelace-style/shoelace" target="_blank">
|
||||
<wa-icon slot="prefix" name="github"></wa-icon> Code
|
||||
</wa-button>
|
||||
<wa-button size="small" class="repo-button repo-button--star" href="https://github.com/shoelace-style/shoelace/stargazers" target="_blank">
|
||||
<wa-icon slot="prefix" name="star-fill"></wa-icon> Star
|
||||
</wa-button>
|
||||
<wa-button size="small" class="repo-button repo-button--twitter" href="https://twitter.com/shoelace_style" target="_blank">
|
||||
<wa-icon slot="prefix" name="twitter"></wa-icon> Follow
|
||||
</wa-button>
|
||||
</div>
|
||||
|
||||
<button class="search-box" type="button" title="Press / to search" aria-label="Search" data-plugin="search">
|
||||
<wa-icon name="search"></wa-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>
|
||||
@@ -1,84 +0,0 @@
|
||||
<ul>
|
||||
<li>
|
||||
<h2>Experimental</h2>
|
||||
<ul>
|
||||
<li><a href="/experimental/themer">Themer</a></li>
|
||||
<li><a href="/experimental/style-guide">Style Guide</a></li>
|
||||
<li><a href="/experimental/form-validation">Form Validation Styles</a></li>
|
||||
<li style="margin-top: .5rem;"><wa-switch id="theme-toggle">Dark mode</wa-switch></li>
|
||||
<script type="module">
|
||||
// Temporary dark toggle
|
||||
const toggle = document.getElementById('theme-toggle');
|
||||
toggle.checked = document.documentElement.classList.contains('wa-theme-default-dark');
|
||||
|
||||
toggle.addEventListener('wa-change', () => {
|
||||
document.documentElement.classList.toggle('wa-theme-default-dark');
|
||||
localStorage.setItem('theme', toggle.checked ? 'dark' : 'light');
|
||||
});
|
||||
</script>
|
||||
</ul>
|
||||
</li>
|
||||
<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 | removeWaPrefix }}">
|
||||
{{ 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/borders">Borders</a></li>
|
||||
<li><a href="/tokens/shadows">Shadows</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>
|
||||
@@ -1,35 +0,0 @@
|
||||
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;
|
||||
};
|
||||
@@ -1,64 +0,0 @@
|
||||
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;
|
||||
};
|
||||
@@ -1,138 +0,0 @@
|
||||
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">
|
||||
<wa-icon name="grip-vertical"></wa-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;
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
let codeBlockId = 0;
|
||||
|
||||
/**
|
||||
* Adds copy code buttons to code fields. The provided doc should be a document object provided by JSDOM. The same
|
||||
* document will be returned with the appropriate DOM manipulations.
|
||||
*/
|
||||
module.exports = function (doc) {
|
||||
doc.querySelectorAll('pre > code').forEach(code => {
|
||||
const pre = code.closest('pre');
|
||||
const button = doc.createElement('wa-copy-button');
|
||||
|
||||
if (!code.id) {
|
||||
code.id = `code-block-${++codeBlockId}`;
|
||||
}
|
||||
|
||||
button.classList.add('copy-code-button');
|
||||
button.setAttribute('from', code.id);
|
||||
|
||||
pre.append(button);
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
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;
|
||||
};
|
||||
@@ -1,63 +0,0 @@
|
||||
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;
|
||||
};
|
||||
@@ -1,75 +0,0 @@
|
||||
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 => {
|
||||
const variant = type === 'tip' ? 'brand' : type;
|
||||
let icon = 'info-circle';
|
||||
if (type === 'warning') icon = 'exclamation-circle';
|
||||
if (type === 'danger') icon = 'exclamation-triangle';
|
||||
|
||||
markdown.use(markdownItContainer, type, {
|
||||
render: function (tokens, idx) {
|
||||
if (tokens[idx].nesting === 1) {
|
||||
return `
|
||||
<wa-alert class="callout" variant="${variant}" open>
|
||||
<wa-icon slot="icon" name="${icon}"></wa-icon>
|
||||
`;
|
||||
}
|
||||
return '</wa-alert>\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;
|
||||
@@ -1,26 +0,0 @@
|
||||
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);
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* @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) {
|
||||
/** This seems trivial, but by assigning to a string first, THEN using innerHTML after iterating over every replacement, we reduce the calculations of JSDOM. At the time of writing benchmarks show a reduction from 9seconds to 3 seconds by doing so. */
|
||||
let html = content.body.innerHTML;
|
||||
|
||||
replacements.forEach(replacement => {
|
||||
html = html.replaceAll(replacement.pattern, replacement.replacement);
|
||||
});
|
||||
|
||||
content.body.innerHTML = html;
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
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;
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
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;
|
||||
};
|
||||
@@ -1,206 +0,0 @@
|
||||
//
|
||||
// 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();
|
||||
})();
|
||||
|
||||
//
|
||||
// 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();
|
||||
});
|
||||
})();
|
||||
|
||||
//
|
||||
// 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);
|
||||
})();
|
||||
@@ -1,384 +0,0 @@
|
||||
(() => {
|
||||
// Append the search dialog to the body
|
||||
const siteSearch = document.createElement('div');
|
||||
const scrollbarWidth = Math.abs(window.innerWidth - document.documentElement.clientWidth);
|
||||
|
||||
siteSearch.classList.add('search');
|
||||
siteSearch.innerHTML = `
|
||||
<div class="search__overlay"></div>
|
||||
<dialog id="search-dialog" class="search__dialog">
|
||||
<div class="search__content">
|
||||
<div class="search__header">
|
||||
<div id="search-combobox" class="search__input-wrapper">
|
||||
<wa-icon name="search"></wa-icon>
|
||||
<input
|
||||
id="search-input"
|
||||
class="search__input"
|
||||
type="search"
|
||||
placeholder="Search"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
enterkeyhint="go"
|
||||
spellcheck="false"
|
||||
maxlength="100"
|
||||
role="combobox"
|
||||
aria-autocomplete="list"
|
||||
aria-expanded="true"
|
||||
aria-controls="search-listbox"
|
||||
aria-haspopup="listbox"
|
||||
aria-activedescendant
|
||||
>
|
||||
<button type="button" class="search__clear-button" aria-label="Clear entry" tabindex="-1" hidden>
|
||||
<wa-icon name="x-circle-fill"></wa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search__body">
|
||||
<ul
|
||||
id="search-listbox"
|
||||
class="search__results"
|
||||
role="listbox"
|
||||
aria-label="Search results"
|
||||
></ul>
|
||||
<div class="search__empty">No matching pages</div>
|
||||
</div>
|
||||
<footer class="search__footer">
|
||||
<small><kbd>↑</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-wa-version');
|
||||
const key = `search_${version}`;
|
||||
const searchDebounce = 50;
|
||||
const animationDuration = 150;
|
||||
let isShowing = false;
|
||||
let searchTimeout;
|
||||
let searchIndex;
|
||||
let map;
|
||||
|
||||
const loadSearchIndex = new Promise(resolve => {
|
||||
const cache = localStorage.getItem(key);
|
||||
const wait = 'requestIdleCallback' in window ? requestIdleCallback : requestAnimationFrame;
|
||||
|
||||
// Cleanup older search indices (everything before this version)
|
||||
try {
|
||||
const items = { ...localStorage };
|
||||
|
||||
Object.keys(items).forEach(k => {
|
||||
if (key > k) {
|
||||
localStorage.removeItem(k);
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
// Look for a cached index
|
||||
try {
|
||||
if (cache) {
|
||||
const data = JSON.parse(cache);
|
||||
|
||||
searchIndex = window.lunr.Index.load(data.searchIndex);
|
||||
map = data.map;
|
||||
|
||||
return resolve();
|
||||
}
|
||||
} catch {
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
// Wait until idle to fetch the index
|
||||
wait(() => {
|
||||
fetch('/assets/search.json')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (!window.lunr) {
|
||||
console.error('The Lunr search client has not yet been loaded.');
|
||||
}
|
||||
|
||||
searchIndex = window.lunr.Index.load(data.searchIndex);
|
||||
map = data.map;
|
||||
|
||||
// Cache the search index for this version
|
||||
if (version) {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(data));
|
||||
} catch (err) {
|
||||
console.warn(`Unable to cache the search index: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function show() {
|
||||
isShowing = true;
|
||||
document.body.append(siteSearch);
|
||||
document.body.classList.add('search-visible');
|
||||
document.body.style.setProperty('--docs-search-scroll-lock-size', `${scrollbarWidth}px`);
|
||||
clearButton.hidden = true;
|
||||
requestAnimationFrame(() => input.focus());
|
||||
updateResults();
|
||||
|
||||
dialog.showModal();
|
||||
|
||||
await Promise.all([
|
||||
dialog.animate(
|
||||
[
|
||||
{ opacity: 0, transform: 'scale(.9)', transformOrigin: 'top' },
|
||||
{ opacity: 1, transform: 'scale(1)', transformOrigin: 'top' }
|
||||
],
|
||||
{ duration: animationDuration }
|
||||
).finished,
|
||||
overlay.animate([{ opacity: 0 }, { opacity: 1 }], { duration: animationDuration }).finished
|
||||
]);
|
||||
|
||||
dialog.addEventListener('mousedown', handleMouseDown);
|
||||
dialog.addEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
|
||||
async function hide() {
|
||||
isShowing = false;
|
||||
|
||||
await Promise.all([
|
||||
dialog.animate(
|
||||
[
|
||||
{ opacity: 1, transform: 'scale(1)', transformOrigin: 'top' },
|
||||
{ opacity: 0, transform: 'scale(.9)', transformOrigin: 'top' }
|
||||
],
|
||||
{ duration: animationDuration }
|
||||
).finished,
|
||||
overlay.animate([{ opacity: 1 }, { opacity: 0 }], { duration: animationDuration }).finished
|
||||
]);
|
||||
|
||||
dialog.close();
|
||||
|
||||
input.blur(); // otherwise Safari will scroll to the bottom of the page on close
|
||||
input.value = '';
|
||||
document.body.classList.remove('search-visible');
|
||||
document.body.style.removeProperty('--docs-search-scroll-lock-size');
|
||||
siteSearch.remove();
|
||||
updateResults();
|
||||
|
||||
dialog.removeEventListener('mousedown', handleMouseDown);
|
||||
dialog.removeEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
|
||||
function handleInput() {
|
||||
clearButton.hidden = input.value === '';
|
||||
|
||||
// Debounce search queries
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => updateResults(input.value), searchDebounce);
|
||||
}
|
||||
|
||||
function handleClear() {
|
||||
clearButton.hidden = true;
|
||||
input.value = '';
|
||||
input.focus();
|
||||
updateResults();
|
||||
}
|
||||
|
||||
function handleMouseDown(event) {
|
||||
if (!event.target.closest('.search__content')) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(event) {
|
||||
// Close when pressing escape
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault(); // prevent <dialog> from closing immediately so it can animate
|
||||
event.stopImmediatePropagation();
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle keyboard selections
|
||||
if (['ArrowDown', 'ArrowUp', 'Home', 'End', 'Enter'].includes(event.key)) {
|
||||
event.preventDefault();
|
||||
|
||||
const currentEl = results.querySelector('[data-selected="true"]');
|
||||
const items = [...results.querySelectorAll('li')];
|
||||
const index = items.indexOf(currentEl);
|
||||
let nextEl;
|
||||
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowUp':
|
||||
nextEl = items[Math.max(0, index - 1)];
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
nextEl = items[Math.min(items.length - 1, index + 1)];
|
||||
break;
|
||||
case 'Home':
|
||||
nextEl = items[0];
|
||||
break;
|
||||
case 'End':
|
||||
nextEl = items[items.length - 1];
|
||||
break;
|
||||
case 'Enter':
|
||||
currentEl?.querySelector('a')?.click();
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the selected item
|
||||
items.forEach(item => {
|
||||
if (item === nextEl) {
|
||||
input.setAttribute('aria-activedescendant', item.id);
|
||||
item.setAttribute('data-selected', 'true');
|
||||
nextEl.scrollIntoView({ block: 'nearest' });
|
||||
} else {
|
||||
item.setAttribute('data-selected', 'false');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updateResults(query = '') {
|
||||
try {
|
||||
await loadSearchIndex;
|
||||
|
||||
const hasQuery = query.length > 0;
|
||||
const searchTerms = query
|
||||
.split(' ')
|
||||
.map((term, index, arr) => {
|
||||
// Search API: https://lunrjs.com/guides/searching.html
|
||||
if (index === arr.length - 1) {
|
||||
// The last term is not mandatory and 1x fuzzy. We also duplicate it with a wildcard to match partial words
|
||||
// as the user types.
|
||||
return `${term}~1 ${term}*`;
|
||||
} else {
|
||||
// All other terms are mandatory and 1x fuzzy
|
||||
return `+${term}~1`;
|
||||
}
|
||||
})
|
||||
.join(' ');
|
||||
const matches = hasQuery ? searchIndex.search(searchTerms) : [];
|
||||
const hasResults = hasQuery && matches.length > 0;
|
||||
|
||||
siteSearch.classList.toggle('search--has-results', hasQuery && hasResults);
|
||||
siteSearch.classList.toggle('search--no-results', hasQuery && !hasResults);
|
||||
|
||||
input.setAttribute('aria-activedescendant', '');
|
||||
results.innerHTML = '';
|
||||
|
||||
matches.forEach((match, index) => {
|
||||
const page = map[match.ref];
|
||||
const li = document.createElement('li');
|
||||
const a = document.createElement('a');
|
||||
const displayTitle = page.title ?? '';
|
||||
const displayDescription = page.description ?? '';
|
||||
const displayUrl = page.url.replace(/^\//, '').replace(/\/$/, '');
|
||||
let icon = 'file-text';
|
||||
|
||||
a.setAttribute('role', 'option');
|
||||
a.setAttribute('id', `search-result-item-${match.ref}`);
|
||||
|
||||
if (page.url.includes('getting-started/')) {
|
||||
icon = 'lightbulb';
|
||||
}
|
||||
if (page.url.includes('resources/')) {
|
||||
icon = 'book';
|
||||
}
|
||||
if (page.url.includes('components/')) {
|
||||
icon = 'puzzle';
|
||||
}
|
||||
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">
|
||||
<wa-icon name="${icon}"></wa-icon>
|
||||
</div>
|
||||
<div class="search__result__details">
|
||||
<div class="search__result-title"></div>
|
||||
<div class="search__result-description"></div>
|
||||
<div class="search__result-url"></div>
|
||||
</div>
|
||||
`;
|
||||
a.querySelector('.search__result-title').textContent = displayTitle;
|
||||
a.querySelector('.search__result-description').textContent = displayDescription;
|
||||
a.querySelector('.search__result-url').textContent = displayUrl;
|
||||
|
||||
li.appendChild(a);
|
||||
results.appendChild(li);
|
||||
});
|
||||
} catch {
|
||||
// Ignore query errors as the user types
|
||||
}
|
||||
}
|
||||
|
||||
// Show the search dialog when clicking on data-plugin="search"
|
||||
document.addEventListener('click', event => {
|
||||
const searchButton = event.target.closest('[data-plugin="search"]');
|
||||
if (searchButton) {
|
||||
show();
|
||||
}
|
||||
});
|
||||
|
||||
// Show the search dialog when slash (or CMD+K) is pressed and focus is not inside a form element
|
||||
document.addEventListener('keydown', event => {
|
||||
if (
|
||||
!isShowing &&
|
||||
(event.key === '/' || (event.key === 'k' && (event.metaKey || event.ctrlKey))) &&
|
||||
!event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))
|
||||
) {
|
||||
event.preventDefault();
|
||||
show();
|
||||
}
|
||||
});
|
||||
|
||||
// Purge cache when we press CMD+CTRL+R
|
||||
document.addEventListener('keydown', event => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key === 'r') {
|
||||
localStorage.clear();
|
||||
}
|
||||
});
|
||||
|
||||
input.addEventListener('input', handleInput);
|
||||
clearButton.addEventListener('click', handleClear);
|
||||
|
||||
// Close when a result is selected
|
||||
results.addEventListener('click', event => {
|
||||
if (event.target.closest('a')) {
|
||||
hide();
|
||||
}
|
||||
});
|
||||
|
||||
// We're using Turbo, so when a user searches for something, visits a result, and presses the back button, the search
|
||||
// UI will still be visible but not interactive. This removes the search UI when Turbo renders a page so they don't
|
||||
// get trapped.
|
||||
window.addEventListener('turbo:render', () => {
|
||||
document.body.classList.remove('search-visible');
|
||||
document.querySelectorAll('.search__overlay, .search__dialog').forEach(el => el.remove());
|
||||
});
|
||||
})();
|
||||
@@ -1,29 +0,0 @@
|
||||
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);
|
||||
})();
|
||||
@@ -2,26 +2,26 @@
|
||||
:root {
|
||||
--docs-search-box-background: var(--wa-form-controls-background);
|
||||
--docs-search-box-border-width: var(--wa-form-controls-border-width);
|
||||
--docs-search-box-border-color: var(--wa-form-controls-border-color-resting);
|
||||
--docs-search-box-border-color: var(--wa-form-controls-resting-color);
|
||||
--docs-search-box-color: var(--wa-form-controls-placeholder-color);
|
||||
|
||||
--docs-search-dialog-background: var(--wa-color-surface-raised);
|
||||
--docs-search-border-width: var(--wa-border-width-thin);
|
||||
--docs-search-border-color: var(--wa-color-surface-outline);
|
||||
--docs-search-border-width: var(--wa-border-width-s);
|
||||
--docs-search-border-color: var(--wa-color-surface-border);
|
||||
--docs-search-text-color: var(--wa-color-text-normal);
|
||||
--docs-search-text-color-muted: var(--wa-color-text-quiet);
|
||||
--docs-search-font-weight-normal: var(--wa-font-weight-normal);
|
||||
--docs-search-font-weight-semibold: var(--wa-font-weight-medium);
|
||||
--docs-search-border-radius: calc(2 * var(--wa-corners-1x));
|
||||
--docs-search-border-radius: calc(2 * var(--wa-corners-s));
|
||||
|
||||
--docs-search-accent-color: var(--wa-color-brand-text-on-surface);
|
||||
--docs-search-icon-color: var(--wa-color-neutral-fill-vivid);
|
||||
--docs-search-icon-color-active: color-mix(in lch, var(--wa-color-neutral-fill-vivid), 8% black);
|
||||
--docs-search-icon-color: var(--wa-color-neutral-spot);
|
||||
--docs-search-icon-color-active: color-mix(in lch, var(--wa-color-neutral-spot), 8% black);
|
||||
--docs-search-shadow: var(--wa-shadow-level-3);
|
||||
--docs-search-result-background-hover: var(--wa-color-neutral-fill-muted-alt);
|
||||
--docs-search-result-color-hover: var(--wa-color-neutral-text-on-muted);
|
||||
--docs-search-result-background-active: var(--wa-color-brand-fill-vivid);
|
||||
--docs-search-result-color-active: var(--wa-color-brand-text-on-vivid);
|
||||
--docs-search-result-background-hover: var(--wa-color-neutral-fill-highlight);
|
||||
--docs-search-result-color-hover: var(--wa-color-neutral-text-on-fill);
|
||||
--docs-search-result-background-active: var(--wa-color-brand-spot);
|
||||
--docs-search-result-color-active: var(--wa-color-brand-text-on-spot);
|
||||
--docs-search-focus-ring: var(--wa-focus-ring);
|
||||
--docs-search-overlay-background: rgb(0 0 0 / 0.33);
|
||||
}
|
||||
|
||||
146
docs/astro.config.mjs
Normal file
@@ -0,0 +1,146 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
import starlight from '@astrojs/starlight';
|
||||
import * as url from 'node:url';
|
||||
import * as path from 'node:path';
|
||||
// const __filename = url.fileURLToPath(import.meta.url);
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
import FullReload from 'vite-plugin-full-reload';
|
||||
|
||||
import { customElementsManifest } from './src/js/cem.js';
|
||||
import { RemarkPluginFindAndReplace } from 'remark-plugin-find-and-replace';
|
||||
import rehypeExternalLinks from 'rehype-external-links';
|
||||
import remarkCodeHighlighter from './src/plugins/prism';
|
||||
import GithubAutolink from './src/plugins/github-autolink.ts';
|
||||
|
||||
const version = customElementsManifest().package.version;
|
||||
const cdndir = 'cdn';
|
||||
const npmdir = 'dist';
|
||||
|
||||
function remarkFrontmatterPlugin() {
|
||||
// All remark and rehype plugins return a separate function
|
||||
return function (tree, file) {
|
||||
const frontmatter = file.data.astro.frontmatter;
|
||||
|
||||
frontmatter.npmdir = npmdir;
|
||||
frontmatter.cdndir = cdndir;
|
||||
frontmatter.version = version;
|
||||
};
|
||||
}
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
server: {
|
||||
open: true,
|
||||
port: 4000,
|
||||
host: true,
|
||||
fs: {
|
||||
strict: false
|
||||
}
|
||||
},
|
||||
vite: {
|
||||
plugins: [
|
||||
FullReload([
|
||||
path.relative(__dirname, '../dist/custom-elements.json'),
|
||||
path.relative(__dirname, './public/**/*.*')
|
||||
])
|
||||
]
|
||||
},
|
||||
outDir: '../_site',
|
||||
site: 'https://shoelace.style',
|
||||
compressHTML: false,
|
||||
markdown: {
|
||||
syntaxHighlight: 'prism',
|
||||
remarkPlugins: [
|
||||
remarkFrontmatterPlugin,
|
||||
RemarkPluginFindAndReplace({
|
||||
replacements: [
|
||||
{ pattern: '%VERSION%', replacement: version },
|
||||
{ pattern: '%CDNDIR%', replacement: cdndir },
|
||||
{ pattern: '%NPMDIR%', replacement: npmdir }
|
||||
]
|
||||
}),
|
||||
GithubAutolink,
|
||||
remarkCodeHighlighter
|
||||
],
|
||||
rehypePlugins: [
|
||||
() =>
|
||||
rehypeExternalLinks({
|
||||
rel: ['nofollow', 'noopener', 'noreferrer'],
|
||||
target: ['_blank'],
|
||||
properties: {
|
||||
class: 'external-link'
|
||||
}
|
||||
})
|
||||
]
|
||||
},
|
||||
integrations: [
|
||||
starlight({
|
||||
expressiveCode: false,
|
||||
title: 'Web Awesome',
|
||||
social: {
|
||||
github: 'https://github.com/shoelace-style/shoelace',
|
||||
twitter: 'https://twitter.com/shoelace_style'
|
||||
},
|
||||
sidebar: [
|
||||
{
|
||||
label: 'Experimental',
|
||||
autogenerate: { directory: 'experimental' }
|
||||
},
|
||||
{
|
||||
label: 'Getting Started',
|
||||
autogenerate: { directory: 'getting-started' }
|
||||
},
|
||||
{
|
||||
label: 'Frameworks',
|
||||
autogenerate: { directory: 'frameworks' }
|
||||
},
|
||||
{
|
||||
label: 'Resources',
|
||||
autogenerate: { directory: 'resources' },
|
||||
items: [
|
||||
{
|
||||
label: 'Community',
|
||||
link: '/resources/community'
|
||||
},
|
||||
{
|
||||
label: 'Help & Support',
|
||||
link: 'https://github.com/shoelace-style/shoelace/discussions'
|
||||
},
|
||||
{
|
||||
label: 'Accessibility',
|
||||
link: '/resources/accessibility'
|
||||
},
|
||||
{
|
||||
label: 'Contributing',
|
||||
link: '/resources/contributing'
|
||||
},
|
||||
{
|
||||
label: 'Changelog',
|
||||
link: '/resources/changelog'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Components',
|
||||
autogenerate: { directory: 'components' }
|
||||
},
|
||||
{
|
||||
label: 'Design Tokens',
|
||||
autogenerate: { directory: 'tokens' }
|
||||
},
|
||||
{
|
||||
label: 'Tutorials',
|
||||
autogenerate: { directory: 'tutorials' }
|
||||
}
|
||||
],
|
||||
// Component overrides
|
||||
components: {
|
||||
// Override the default `Head` component.
|
||||
Head: './src/components/overrides/Head.astro',
|
||||
TableOfContents: './src/components/overrides/TableOfContents.astro',
|
||||
Search: './src/components/overrides/Search.astro'
|
||||
}
|
||||
})
|
||||
]
|
||||
});
|
||||
@@ -1,240 +0,0 @@
|
||||
/* 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 webAwesomeFlavoredMarkdown = 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: 'Web Awesome',
|
||||
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 wa- prefix).`
|
||||
);
|
||||
}
|
||||
return component;
|
||||
});
|
||||
|
||||
//
|
||||
// Custom markdown syntaxes
|
||||
//
|
||||
eleventyConfig.setLibrary('md', webAwesomeFlavoredMarkdown);
|
||||
|
||||
//
|
||||
// Filters
|
||||
//
|
||||
eleventyConfig.addFilter('markdown', content => {
|
||||
return webAwesomeFlavoredMarkdown.render(content);
|
||||
});
|
||||
|
||||
eleventyConfig.addFilter('markdownInline', content => {
|
||||
return webAwesomeFlavoredMarkdown.renderInline(content);
|
||||
});
|
||||
|
||||
eleventyConfig.addFilter('classNameToComponentName', className => {
|
||||
let name = capitalCase(className.replace(/^Wa/, ''));
|
||||
if (name === 'Qr Code') name = 'QR Code'; // manual override
|
||||
return name;
|
||||
});
|
||||
|
||||
eleventyConfig.addFilter('removeWaPrefix', tagName => {
|
||||
return tagName.replace(/^wa-/, '');
|
||||
});
|
||||
|
||||
//
|
||||
// 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, '/') // convert backslashes to forward slashes
|
||||
.replace(/\/index.html$/, '/'); // convert trailing /index.html to /
|
||||
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,19 +0,0 @@
|
||||
---
|
||||
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,37 +0,0 @@
|
||||
---
|
||||
meta:
|
||||
title: Border Tokens
|
||||
description: Border tokens are used to control borders and corners.
|
||||
---
|
||||
|
||||
# Border Tokens
|
||||
|
||||
Border radius tokens are used to give sharp edges a more subtle, rounded effect. They use rem units so they scale with the base font size. The pixel values displayed are based on a 16px font size.
|
||||
|
||||
| Token | Value | Example |
|
||||
| -------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------ |
|
||||
| `--wa-border-style` | `solid` | |
|
||||
| `--wa-border-width-thin` | `0.0625rem` (1px) | <div class="border-demo" style="border-width: var(--wa-border-width-thin);"></div> |
|
||||
| `--wa-border-width-medium` | `calc(var(--wa-border-width-thin) * 2)` (2px) | <div class="border-demo" style="border-width: var(--wa-border-width-medium);"></div> |
|
||||
| `--wa-border-width-thick` | `calc(var(--wa-border-width-thin) * 3)` (3px) | <div class="border-demo" style="border-width: var(--wa-border-width-thick);"></div> |
|
||||
|
||||
## Corners
|
||||
|
||||
TODO
|
||||
|
||||
| Token | Value | Example |
|
||||
| ------------------- | ---------------------------------- | ------------------------------------------------------------------------------ |
|
||||
| `--wa-corners-half` | `calc(var(--wa-corners-1x) * 0.5)` | <div class="corner-demo" style="border-radius: var(--wa-corners-half);"></div> |
|
||||
| `--wa-corners-1x` | `0.25rem` | <div class="corner-demo" style="border-radius: var(--wa-corners-1x);"></div> |
|
||||
| `--wa-corners-2x` | `calc(var(--wa-corners-1x) * 2)` | <div class="corner-demo" style="border-radius: var(--wa-corners-2x);"></div> |
|
||||
| `--wa-corners-3x` | `calc(var(--wa-corners-1x) * 3)` | <div class="corner-demo" style="border-radius: var(--wa-corners-3x);"></div> |
|
||||
|
||||
## Special Corners
|
||||
|
||||
TODO
|
||||
|
||||
| Token | Value | Example |
|
||||
| --------------------- | -------- | ------------------------------------------------------------------------------------------- |
|
||||
| `--wa-corners-pill` | `9999px` | <div class="corner-demo" style="width: 6rem; border-radius: var(--wa-corners-pill);"></div> |
|
||||
| `--wa-corners-circle` | `50%` | <div class="corner-demo" style="border-radius: var(--wa-corners-circle);"></div> |
|
||||
| `--wa-corners-sharp` | `0` | <div class="corner-demo" style="border-radius: var(--wa-corners-sharp);"></div> |
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
meta:
|
||||
title: More Design Tokens
|
||||
description: Additional design tokens can be found here.
|
||||
---
|
||||
|
||||
# More Design Tokens
|
||||
|
||||
## Focus Rings
|
||||
|
||||
Focus ring tokens control the appearance of focus rings.
|
||||
|
||||
| Token | Value |
|
||||
| ------------------------ | ---------------------------------------------------------- |
|
||||
| `--wa-focus-ring` | `solid var(--wa-border-width-thick) var(--wa-color-focus)` |
|
||||
| `--wa-focus-ring-offset` | `0.0625rem` (1px) |
|
||||
|
||||
## Form Controls
|
||||
|
||||
Form control tokens control the appearance of form controls such as [input](/components/input), [select](/components/select), [textarea](/components/textarea), etc.
|
||||
|
||||
| Token | Value |
|
||||
| ------------------------------------------- | ------------------------------------------- |
|
||||
| `--wa-form-controls-background` | `var(--wa-color-surface-raised)` |
|
||||
| `--wa-form-controls-border-style` | `var(--wa-border-style)` |
|
||||
| `--wa-form-controls-border-width` | `var(--wa-border-width-thin)` |
|
||||
| `--wa-form-controls-corners` | `var(--wa-corners-1x)` |
|
||||
| `--wa-form-controls-border-color-resting` | `var(--wa-color-neutral-outline-muted-alt)` |
|
||||
| `--wa-form-controls-border-color-activated` | `var(--wa-color-brand-action-vivid)` |
|
||||
| `--wa-form-controls-value-line-height` | `var(--wa-font-height-compact)` |
|
||||
| `--wa-form-controls-placeholder-color` | `var(--wa-color-neutral-60)` |
|
||||
@@ -1,57 +0,0 @@
|
||||
---
|
||||
meta:
|
||||
title: Spacing Tokens
|
||||
description: Spacing tokens are used to provide consistent spacing between content in your app.
|
||||
---
|
||||
|
||||
# Spacing Tokens
|
||||
|
||||
Spacing tokens are used to provide consistent spacing between content in your app.
|
||||
|
||||
| Token | Value | Example |
|
||||
| ---------------- | -------------- | ------------------------------------------------------------------------------------- |
|
||||
| `--wa-space-3xs` | 0.125rem (2px) | <div class="spacing-demo" style="width: var(--wa-space-3xs); height: 0.25rem;"></div> |
|
||||
| `--wa-space-2xs` | 0.25rem (4px) | <div class="spacing-demo" style="width: var(--wa-space-2xs); height: 0.25rem;"></div> |
|
||||
| `--wa-space-xs` | 0.5rem (8px) | <div class="spacing-demo" style="width: var(--wa-space-xs); height: 0.25rem;"></div> |
|
||||
| `--wa-space-s` | 0.75rem (12px) | <div class="spacing-demo" style="width: var(--wa-space-s); height: 0.25rem;"></div> |
|
||||
| `--wa-space-m` | 1rem (16px) | <div class="spacing-demo" style="width: var(--wa-space-m); height: 0.25rem;"></div> |
|
||||
| `--wa-space-l` | 1.25rem (20px) | <div class="spacing-demo" style="width: var(--wa-space-l); height: 0.25rem;"></div> |
|
||||
| `--wa-space-xl` | 1.5rem (24px) | <div class="spacing-demo" style="width: var(--wa-space-xl); height: 0.25rem;"></div> |
|
||||
| `--wa-space-2xl` | 2rem (32px) | <div class="spacing-demo" style="width: var(--wa-space-2xl); height: 0.25rem;"></div> |
|
||||
| `--wa-space-3xl` | 3rem (48px) | <div class="spacing-demo" style="width: var(--wa-space-3xl); height: 0.25rem;"></div> |
|
||||
|
||||
## Square Tokens
|
||||
|
||||
TODO
|
||||
|
||||
| Token | Value | Example |
|
||||
| ---------------------- | -------------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| `--wa-space-square-xs` | `var(--wa-space-xs)` | <div class="spacing-demo" style="display: inline-block; padding: var(--wa-space-square-xs);"></div> |
|
||||
| `--wa-space-square-s` | `var(--wa-space-s)` | <div class="spacing-demo" style="display: inline-block; padding: var(--wa-space-square-s);"></div> |
|
||||
| `--wa-space-square-m` | `var(--wa-space-m)` | <div class="spacing-demo" style="display: inline-block; padding: var(--wa-space-square-m);"></div> |
|
||||
| `--wa-space-square-l` | `var(--wa-space-l)` | <div class="spacing-demo" style="display: inline-block; padding: var(--wa-space-square-l);"></div> |
|
||||
| `--wa-space-square-xl` | `var(--wa-space-xl)` | <div class="spacing-demo" style="display: inline-block; padding: var(--wa-space-square-xl);"></div> |
|
||||
|
||||
## Stretch Tokens
|
||||
|
||||
TODO
|
||||
|
||||
| Token | Value | Example |
|
||||
| ----------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `--wa-space-stretch-xs` | `var(--wa-space-xs) var(--wa-space-m)` | <div class="spacing-demo" style="display: inline-block; padding: var(--wa-space-stretch-xs);"></div> |
|
||||
| `--wa-space-stretch-s` | `var(--wa-space-s) var(--wa-space-l)` | <div class="spacing-demo" style="display: inline-block; padding: var(--wa-space-stretch-s);"></div> |
|
||||
| `--wa-space-stretch-m` | `var(--wa-space-m) var(--wa-space-xl)` | <div class="spacing-demo" style="display: inline-block; padding: var(--wa-space-stretch-m);"></div> |
|
||||
| `--wa-space-stretch-l` | `var(--wa-space-l) var(--wa-space-2xl)` | <div class="spacing-demo" style="display: inline-block; padding: var(--wa-space-stretch-l);"></div> |
|
||||
| `--wa-space-stretch-xl` | `var(--wa-space-xl) var(--wa-space-3xl)` | <div class="spacing-demo" style="display: inline-block; padding: var(--wa-space-stretch-xl);"></div> |
|
||||
|
||||
## Squish Tokens
|
||||
|
||||
TODO
|
||||
|
||||
| Token | Value | Example |
|
||||
| ---------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| `--wa-space-squish-xs` | `var(--wa-space-xs) var(--wa-space-3xs)` | <div class="spacing-demo" style="display: inline-block; padding: var(--wa-space-squish-xs);"></div> |
|
||||
| `--wa-space-squish-s` | `var(--wa-space-s) var(--wa-space-2xs)` | <div class="spacing-demo" style="display: inline-block; padding: var(--wa-space-squish-s);"></div> |
|
||||
| `--wa-space-squish-m` | `var(--wa-space-m) var(--wa-space-xs)` | <div class="spacing-demo" style="display: inline-block; padding: var(--wa-space-squish-m);"></div> |
|
||||
| `--wa-space-squish-l` | `var(--wa-space-l) var(--wa-space-s)` | <div class="spacing-demo" style="display: inline-block; padding: var(--wa-space-squish-l);"></div> |
|
||||
| `--wa-space-squish-xl` | `var(--wa-space-xl) var(--wa-space-m)` | <div class="spacing-demo" style="display: inline-block; padding: var(--wa-space-squish-xl);"></div> |
|
||||
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
BIN
docs/public/assets/images/houston.webp
Normal file
|
After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
1
docs/public/assets/images/sprite.svg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
docs/src/assets/houston.webp
Normal file
|
After Width: | Height: | Size: 96 KiB |
125
docs/src/components/Docs.astro
Normal file
@@ -0,0 +1,125 @@
|
||||
---
|
||||
---
|
||||
|
||||
<script>
|
||||
// double check we're marking links external
|
||||
;(() => {
|
||||
document.querySelectorAll("a[href^='http']").forEach((el) => {
|
||||
const anchor = el as HTMLAnchorElement
|
||||
|
||||
if (anchor.hostname !== window.location.hostname) {
|
||||
anchor.setAttribute("rel", "nofollow noreferrer noopener")
|
||||
anchor.setAttribute("target", "_blank")
|
||||
anchor.classList.add("external-link")
|
||||
}
|
||||
})
|
||||
})()
|
||||
// Add anchor headings
|
||||
;(() => {
|
||||
// This should probably be a proper plugin, but it requires adding the anchor heading directly in "ComponentLayout.astro" and
|
||||
// writing a RemarkPlugin.
|
||||
function updateAnchorHeadings () {
|
||||
document.querySelectorAll(":is(h1,h2,h3,h4,h5,h6)[id]").forEach((headingEl) => {
|
||||
if (headingEl.querySelector("a")) return
|
||||
|
||||
headingEl.classList.add("anchor-heading")
|
||||
|
||||
const anchor = document.createElement("a")
|
||||
const visuallyHidden = document.createElement("wa-visually-hidden")
|
||||
visuallyHidden.innerText = `Direct link to ${headingEl.textContent}`
|
||||
|
||||
anchor.setAttribute("href", `#${encodeURIComponent(headingEl.id)}`)
|
||||
anchor.append(visuallyHidden)
|
||||
|
||||
headingEl.append(anchor)
|
||||
})
|
||||
}
|
||||
|
||||
document.addEventListener("turbo:load", updateAnchorHeadings)
|
||||
})()
|
||||
|
||||
;(() => {
|
||||
function setTurboFalse () {
|
||||
// We don't control this markup, but we can programmatically add turbo false. The reason is it borks layouts otherwise.
|
||||
document.querySelectorAll("a[href*='/experimental/themer']").forEach((el) => {
|
||||
el.setAttribute("data-turbo", "false")
|
||||
})
|
||||
}
|
||||
|
||||
setTurboFalse()
|
||||
document.addEventListener("turbo:load", setTurboFalse)
|
||||
})()
|
||||
//
|
||||
// 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();
|
||||
});
|
||||
})();
|
||||
|
||||
//
|
||||
// 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}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
//
|
||||
// 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);
|
||||
})();
|
||||
</script>
|
||||
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
69
docs/src/components/PageWidget.astro
Normal file
@@ -0,0 +1,69 @@
|
||||
<style>
|
||||
.layout-widget {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
background-color: white;
|
||||
bottom: 4rem;
|
||||
left: 4rem;
|
||||
}
|
||||
.layout-widget:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<wa-dropdown id="js-layout-widget" class="layout-widget" stay-open-on-select>
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-menu></wa-menu>
|
||||
</wa-dropdown>
|
||||
|
||||
<script type="module">
|
||||
const layoutWidget = document.querySelector("#js-layout-widget")
|
||||
const docFrag = new DocumentFragment()
|
||||
function makeMenuItem (type, slot) {
|
||||
const menuItem = Object.assign(document.createElement("wa-menu-item"), {
|
||||
type: "checkbox",
|
||||
textContent: `${type} ${slot}`
|
||||
})
|
||||
menuItem.setAttribute("value", `${type}-${slot}`)
|
||||
return menuItem
|
||||
}
|
||||
document.querySelectorAll("wa-page > [slot]").forEach((el) => {
|
||||
const slot = el.getAttribute("slot");
|
||||
docFrag.append(makeMenuItem("toggle", slot), makeMenuItem("overflow", slot), document.createElement("wa-divider"))
|
||||
})
|
||||
docFrag.append(makeMenuItem("toggle", "main"), makeMenuItem("overflow", "main"))
|
||||
layoutWidget.querySelector("wa-menu").append(docFrag)
|
||||
function capitalize(string) {
|
||||
return string.split(/\s+/).map((str) => str[0].toUppercase() + str.slice(1)).join(" ")
|
||||
}
|
||||
function handleSelect (e) {
|
||||
const item = e.detail.item
|
||||
const val = item.getAttribute("value")
|
||||
if (val === "footer-0") {
|
||||
}
|
||||
const slot = val.split("-").slice(1).join("-")
|
||||
let el
|
||||
if (slot === "main") {
|
||||
el = document.querySelector(`main`)
|
||||
} else {
|
||||
el = document.querySelector(`wa-page > [slot='${slot}']`)
|
||||
}
|
||||
if (val.startsWith("overflow")) {
|
||||
if (item.checked) {
|
||||
el.textContent = "lorem ".repeat(1_000)
|
||||
return
|
||||
}
|
||||
el.textContent = slot
|
||||
return
|
||||
}
|
||||
if (val.startsWith("toggle")) {
|
||||
if (item.checked) {
|
||||
el.setAttribute("hidden", "")
|
||||
return
|
||||
}
|
||||
el.removeAttribute("hidden")
|
||||
return
|
||||
}
|
||||
}
|
||||
layoutWidget.addEventListener("wa-select", handleSelect);
|
||||
</script>
|
||||
35
docs/src/components/Playground.astro
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
import PageLayout from "../layouts/PageLayout.astro"
|
||||
import Themer from "./Themer.astro"
|
||||
|
||||
const { html, css, js, title, description } = Astro.props
|
||||
|
||||
---
|
||||
|
||||
<script>
|
||||
import "light-pen"
|
||||
</script>
|
||||
|
||||
<PageLayout frontmatter={{
|
||||
description,
|
||||
title
|
||||
}}>
|
||||
<div style="display:grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr);">
|
||||
<Themer />
|
||||
|
||||
<light-pen style="height: 100%;" resize-position="30" preserve-whitespace>
|
||||
<script type="text/plain" slot="html" set:html={
|
||||
`<link id="theme-stylesheet" type="text/css" href="/dist/themes/default.css" rel="stylesheet">
|
||||
<link id="applied-stylesheet" type="text/css" href="/dist/themes/applied.css" rel="stylesheet">
|
||||
|
||||
<script type="module" defer>import "/dist/webawesome.js"</script>
|
||||
|
||||
${html}`}>
|
||||
</script>
|
||||
<script type="text/plain" slot="css" set:html={`${css}`}>
|
||||
</script>
|
||||
<script type="text/plain" slot="js" set:html={''}>
|
||||
</script>
|
||||
</light-pen>
|
||||
</div>
|
||||
</PageLayout>
|
||||
389
docs/src/components/Search.astro
Normal file
@@ -0,0 +1,389 @@
|
||||
---
|
||||
---
|
||||
|
||||
<script>
|
||||
;(() => {
|
||||
// Append the search dialog to the body
|
||||
const siteSearch = document.createElement('div');
|
||||
const scrollbarWidth = Math.abs(window.innerWidth - document.documentElement.clientWidth);
|
||||
|
||||
siteSearch.classList.add('search');
|
||||
siteSearch.innerHTML = `
|
||||
<div class="search__overlay"></div>
|
||||
<dialog id="search-dialog" class="search__dialog">
|
||||
<div class="search__content">
|
||||
<div class="search__header">
|
||||
<div id="search-combobox" class="search__input-wrapper">
|
||||
<wa-icon name="search"></wa-icon>
|
||||
<input
|
||||
id="search-input"
|
||||
class="search__input"
|
||||
type="search"
|
||||
placeholder="Search"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
enterkeyhint="go"
|
||||
spellcheck="false"
|
||||
maxlength="100"
|
||||
role="combobox"
|
||||
aria-autocomplete="list"
|
||||
aria-expanded="true"
|
||||
aria-controls="search-listbox"
|
||||
aria-haspopup="listbox"
|
||||
aria-activedescendant
|
||||
>
|
||||
<button type="button" class="search__clear-button" aria-label="Clear entry" tabindex="-1" hidden>
|
||||
<wa-icon name="circle-xmark" variant="regular"></wa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search__body">
|
||||
<ul
|
||||
id="search-listbox"
|
||||
class="search__results"
|
||||
role="listbox"
|
||||
aria-label="Search results"
|
||||
></ul>
|
||||
<div class="search__empty">No matching pages</div>
|
||||
</div>
|
||||
<footer class="search__footer">
|
||||
<small><kbd><wa-icon label="Up" name="arrow-up"></wa-icon></kbd> <kbd><wa-icon label="Down" name="arrow-down"></wa-icon></kbd> Navigate</small>
|
||||
<small><kbd><wa-icon label="Enter" name="arrow-turn-down-left"></wa-icon></kbd> Select</small>
|
||||
<small><kbd>Esc</kbd> Close</small>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
`;
|
||||
|
||||
const overlay = siteSearch.querySelector('.search__overlay');
|
||||
const dialog = siteSearch.querySelector('.search__dialog');
|
||||
const input = siteSearch.querySelector('.search__input');
|
||||
const clearButton = siteSearch.querySelector('.search__clear-button');
|
||||
const results = siteSearch.querySelector('.search__results');
|
||||
const version = document.documentElement.getAttribute('data-wa-version');
|
||||
const key = `search_${version}`;
|
||||
const searchDebounce = 50;
|
||||
const animationDuration = 150;
|
||||
let isShowing = false;
|
||||
let searchTimeout;
|
||||
let searchIndex;
|
||||
let map;
|
||||
|
||||
const loadSearchIndex = new Promise(resolve => {
|
||||
const cache = localStorage.getItem(key);
|
||||
const wait = 'requestIdleCallback' in window ? requestIdleCallback : requestAnimationFrame;
|
||||
|
||||
// Cleanup older search indices (everything before this version)
|
||||
try {
|
||||
const items = { ...localStorage };
|
||||
|
||||
Object.keys(items).forEach(k => {
|
||||
if (key > k) {
|
||||
localStorage.removeItem(k);
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
// Look for a cached index
|
||||
try {
|
||||
if (cache) {
|
||||
const data = JSON.parse(cache);
|
||||
|
||||
searchIndex = window.lunr.Index.load(data.searchIndex);
|
||||
map = data.map;
|
||||
|
||||
return resolve();
|
||||
}
|
||||
} catch {
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
// Wait until idle to fetch the index
|
||||
wait(() => {
|
||||
fetch('/assets/search.json')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (!window.lunr) {
|
||||
console.error('The Lunr search client has not yet been loaded.');
|
||||
}
|
||||
|
||||
searchIndex = window.lunr.Index.load(data.searchIndex);
|
||||
map = data.map;
|
||||
|
||||
// Cache the search index for this version
|
||||
if (version) {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(data));
|
||||
} catch (err) {
|
||||
console.warn(`Unable to cache the search index: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function show() {
|
||||
isShowing = true;
|
||||
document.body.append(siteSearch);
|
||||
document.body.classList.add('search-visible');
|
||||
document.body.style.setProperty('--docs-search-scroll-lock-size', `${scrollbarWidth}px`);
|
||||
clearButton.hidden = true;
|
||||
requestAnimationFrame(() => input.focus());
|
||||
updateResults();
|
||||
|
||||
dialog.showModal();
|
||||
|
||||
await Promise.all([
|
||||
dialog.animate(
|
||||
[
|
||||
{ opacity: 0, transform: 'scale(.9)', transformOrigin: 'top' },
|
||||
{ opacity: 1, transform: 'scale(1)', transformOrigin: 'top' }
|
||||
],
|
||||
{ duration: animationDuration }
|
||||
).finished,
|
||||
overlay.animate([{ opacity: 0 }, { opacity: 1 }], { duration: animationDuration }).finished
|
||||
]);
|
||||
|
||||
dialog.addEventListener('mousedown', handleMouseDown);
|
||||
dialog.addEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
|
||||
async function hide() {
|
||||
isShowing = false;
|
||||
|
||||
await Promise.all([
|
||||
dialog.animate(
|
||||
[
|
||||
{ opacity: 1, transform: 'scale(1)', transformOrigin: 'top' },
|
||||
{ opacity: 0, transform: 'scale(.9)', transformOrigin: 'top' }
|
||||
],
|
||||
{ duration: animationDuration }
|
||||
).finished,
|
||||
overlay.animate([{ opacity: 1 }, { opacity: 0 }], { duration: animationDuration }).finished
|
||||
]);
|
||||
|
||||
dialog.close();
|
||||
|
||||
input.blur(); // otherwise Safari will scroll to the bottom of the page on close
|
||||
input.value = '';
|
||||
document.body.classList.remove('search-visible');
|
||||
document.body.style.removeProperty('--docs-search-scroll-lock-size');
|
||||
siteSearch.remove();
|
||||
updateResults();
|
||||
|
||||
dialog.removeEventListener('mousedown', handleMouseDown);
|
||||
dialog.removeEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
|
||||
function handleInput() {
|
||||
clearButton.hidden = input.value === '';
|
||||
|
||||
// Debounce search queries
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => updateResults(input.value), searchDebounce);
|
||||
}
|
||||
|
||||
function handleClear() {
|
||||
clearButton.hidden = true;
|
||||
input.value = '';
|
||||
input.focus();
|
||||
updateResults();
|
||||
}
|
||||
|
||||
function handleMouseDown(event) {
|
||||
if (!event.target.closest('.search__content')) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(event) {
|
||||
// Close when pressing escape
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault(); // prevent <dialog> from closing immediately so it can animate
|
||||
event.stopImmediatePropagation();
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle keyboard selections
|
||||
if (['ArrowDown', 'ArrowUp', 'Home', 'End', 'Enter'].includes(event.key)) {
|
||||
event.preventDefault();
|
||||
|
||||
const currentEl = results.querySelector('[data-selected="true"]');
|
||||
const items = [...results.querySelectorAll('li')];
|
||||
const index = items.indexOf(currentEl);
|
||||
let nextEl;
|
||||
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowUp':
|
||||
nextEl = items[Math.max(0, index - 1)];
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
nextEl = items[Math.min(items.length - 1, index + 1)];
|
||||
break;
|
||||
case 'Home':
|
||||
nextEl = items[0];
|
||||
break;
|
||||
case 'End':
|
||||
nextEl = items[items.length - 1];
|
||||
break;
|
||||
case 'Enter':
|
||||
currentEl?.querySelector('a')?.click();
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the selected item
|
||||
items.forEach(item => {
|
||||
if (item === nextEl) {
|
||||
input.setAttribute('aria-activedescendant', item.id);
|
||||
item.setAttribute('data-selected', 'true');
|
||||
nextEl.scrollIntoView({ block: 'nearest' });
|
||||
} else {
|
||||
item.setAttribute('data-selected', 'false');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updateResults(query = '') {
|
||||
try {
|
||||
await loadSearchIndex;
|
||||
|
||||
const hasQuery = query.length > 0;
|
||||
const searchTerms = query
|
||||
.split(' ')
|
||||
.map((term, index, arr) => {
|
||||
// Search API: https://lunrjs.com/guides/searching.html
|
||||
if (index === arr.length - 1) {
|
||||
// The last term is not mandatory and 1x fuzzy. We also duplicate it with a wildcard to match partial words
|
||||
// as the user types.
|
||||
return `${term}~1 ${term}*`;
|
||||
} else {
|
||||
// All other terms are mandatory and 1x fuzzy
|
||||
return `+${term}~1`;
|
||||
}
|
||||
})
|
||||
.join(' ');
|
||||
const matches = hasQuery ? searchIndex.search(searchTerms) : [];
|
||||
const hasResults = hasQuery && matches.length > 0;
|
||||
|
||||
siteSearch.classList.toggle('search--has-results', hasQuery && hasResults);
|
||||
siteSearch.classList.toggle('search--no-results', hasQuery && !hasResults);
|
||||
|
||||
input.setAttribute('aria-activedescendant', '');
|
||||
results.innerHTML = '';
|
||||
|
||||
matches.forEach((match, index) => {
|
||||
const page = map[match.ref];
|
||||
const li = document.createElement('li');
|
||||
const a = document.createElement('a');
|
||||
const displayTitle = page.title ?? '';
|
||||
const displayDescription = page.description ?? '';
|
||||
const displayUrl = page.url.replace(/^\//, '').replace(/\/$/, '');
|
||||
let icon = 'file-text';
|
||||
|
||||
a.setAttribute('role', 'option');
|
||||
a.setAttribute('id', `search-result-item-${match.ref}`);
|
||||
|
||||
if (page.url.includes('getting-started/')) {
|
||||
icon = 'lightbulb';
|
||||
}
|
||||
if (page.url.includes('resources/')) {
|
||||
icon = 'book';
|
||||
}
|
||||
if (page.url.includes('components/')) {
|
||||
icon = 'puzzle-piece';
|
||||
}
|
||||
if (page.url.includes('tokens/')) {
|
||||
icon = 'swatchbook';
|
||||
}
|
||||
if (page.url.includes('utilities/')) {
|
||||
icon = 'wrench';
|
||||
}
|
||||
if (page.url.includes('tutorials/')) {
|
||||
icon = 'gamepad';
|
||||
}
|
||||
|
||||
li.classList.add('search__result');
|
||||
li.setAttribute('role', 'option');
|
||||
li.setAttribute('id', `search-result-item-${match.ref}`);
|
||||
li.setAttribute('data-selected', index === 0 ? 'true' : 'false');
|
||||
|
||||
a.href = page.url;
|
||||
a.innerHTML = `
|
||||
<div class="search__result-icon" aria-hidden="true">
|
||||
<wa-icon name="${icon}"></wa-icon>
|
||||
</div>
|
||||
<div class="search__result__details">
|
||||
<div class="search__result-title"></div>
|
||||
<div class="search__result-description"></div>
|
||||
<div class="search__result-url"></div>
|
||||
</div>
|
||||
`;
|
||||
a.querySelector('.search__result-title').textContent = displayTitle;
|
||||
a.querySelector('.search__result-description').textContent = displayDescription;
|
||||
a.querySelector('.search__result-url').textContent = displayUrl;
|
||||
|
||||
li.appendChild(a);
|
||||
results.appendChild(li);
|
||||
});
|
||||
} catch {
|
||||
// Ignore query errors as the user types
|
||||
}
|
||||
}
|
||||
|
||||
// Show the search dialog when clicking on data-plugin="search"
|
||||
document.addEventListener('click', event => {
|
||||
const searchButton = event.target.closest('[data-plugin="search"]');
|
||||
if (searchButton) {
|
||||
show();
|
||||
}
|
||||
});
|
||||
|
||||
// Show the search dialog when slash (or CMD+K) is pressed and focus is not inside a form element
|
||||
document.addEventListener('keydown', event => {
|
||||
if (
|
||||
!isShowing &&
|
||||
(event.key === '/' || (event.key === 'k' && (event.metaKey || event.ctrlKey))) &&
|
||||
!event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))
|
||||
) {
|
||||
event.preventDefault();
|
||||
show();
|
||||
}
|
||||
});
|
||||
|
||||
// Purge cache when we press CMD+CTRL+R
|
||||
document.addEventListener('keydown', event => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key === 'r') {
|
||||
localStorage.clear();
|
||||
}
|
||||
});
|
||||
|
||||
input.addEventListener('input', handleInput);
|
||||
clearButton.addEventListener('click', handleClear);
|
||||
|
||||
// Close when a result is selected
|
||||
results.addEventListener('click', event => {
|
||||
if (event.target.closest('a')) {
|
||||
hide();
|
||||
}
|
||||
});
|
||||
|
||||
// We're using Turbo, so when a user searches for something, visits a result, and presses the back button, the search
|
||||
// UI will still be visible but not interactive. This removes the search UI when Turbo renders a page so they don't
|
||||
// get trapped.
|
||||
window.addEventListener('turbo:render', () => {
|
||||
document.body.classList.remove('search-visible');
|
||||
document.querySelectorAll('.search__overlay, .search__dialog').forEach(el => el.remove());
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
1025
docs/src/components/Themer.astro
Normal file
36
docs/src/components/Turbo.astro
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
---
|
||||
|
||||
<script>
|
||||
import * as Turbo from '@hotwired/turbo';
|
||||
|
||||
;(() => {
|
||||
if (!window.scrollPositions) {
|
||||
window.scrollPositions = {};
|
||||
}
|
||||
|
||||
const preservedScrollQuery = '[id][data-preserve-scroll], [id="starlight__sidebar"]'
|
||||
|
||||
function preserveScroll() {
|
||||
document.querySelectorAll(preservedScrollQuery).forEach(element => {
|
||||
scrollPositions[element.id] = element.scrollTop;
|
||||
});
|
||||
}
|
||||
|
||||
function restoreScroll(event) {
|
||||
document.querySelectorAll(preservedScrollQuery).forEach(element => {
|
||||
element.scrollTop = scrollPositions[element.id];
|
||||
});
|
||||
|
||||
if (event.detail && event.detail.newBody) {
|
||||
event.detail.newBody.querySelectorAll(preservedScrollQuery).forEach(element => {
|
||||
element.scrollTop = scrollPositions[element.id];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('turbo:before-cache', preserveScroll);
|
||||
window.addEventListener('turbo:before-render', restoreScroll);
|
||||
window.addEventListener('turbo:render', restoreScroll);
|
||||
})();
|
||||
</script>
|
||||
56
docs/src/components/overrides/Head.astro
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
import type { Props } from '@astrojs/starlight/props';
|
||||
import { default as AstroHead } from '@astrojs/starlight/components/Head.astro';
|
||||
import Turbo from "../Turbo.astro"
|
||||
import Docs from "../Docs.astro"
|
||||
import Search from "../Search.astro"
|
||||
|
||||
import '../../styles/global.css'
|
||||
import '../../styles/syntax-highlight.css'
|
||||
import '../../styles/code-previews.css'
|
||||
import { customElementsManifest } from '../../js/cem';
|
||||
|
||||
const version = customElementsManifest().package.version
|
||||
---
|
||||
|
||||
|
||||
<script>
|
||||
import "../../../../dist/webawesome.js"
|
||||
import "../../js/code-previews.js"
|
||||
import "../../js/theme.js"
|
||||
</script>
|
||||
|
||||
<style is:inline>
|
||||
/* Override starlight selector for auto injecting margin since it causes a lot of issues. */
|
||||
body .sl-markdown-content :not(a, strong, em, del, span, input, code, h1, h2, h3, h4, h5, h6) + :not(a, strong, em, del, span, input, code, h1, h2, h3, h4, h5, h6, :where(.not-content *)) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/** Applied styles overrides for docs. */
|
||||
body details {
|
||||
background-color: transparent;
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
body details summary::before {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<>
|
||||
<Turbo />
|
||||
<Docs />
|
||||
{/* <Search /> */}
|
||||
|
||||
<meta name="wa-version" content={version} />
|
||||
<meta name="turbo-cache-control" content="no-preview">
|
||||
|
||||
<link id="theme-stylesheet" rel="stylesheet" href="/dist/themes/default.css" />
|
||||
|
||||
<link rel="stylesheet" href="/dist/themes/applied.css" />
|
||||
<link rel="stylesheet" href="/dist/themes/forms.css" />
|
||||
|
||||
<AstroHead {...Astro.props}>
|
||||
<slot />
|
||||
</AstroHead>
|
||||
</>
|
||||
482
docs/src/components/overrides/Search.astro
Normal file
@@ -0,0 +1,482 @@
|
||||
---
|
||||
import '@pagefind/default-ui/css/ui.css';
|
||||
import { Icon } from '@astrojs/starlight/components';
|
||||
import type { Props } from '@astrojs/starlight/props';
|
||||
|
||||
const { labels } = Astro.props;
|
||||
|
||||
const pagefindTranslations = {
|
||||
placeholder: labels['search.label'],
|
||||
...Object.fromEntries(
|
||||
Object.entries(labels)
|
||||
.filter(([key]) => key.startsWith('pagefind.'))
|
||||
.map(([key, value]) => [key.replace('pagefind.', ''), value])
|
||||
),
|
||||
};
|
||||
|
||||
---
|
||||
|
||||
<site-search
|
||||
data-translations={JSON.stringify(pagefindTranslations)}
|
||||
data-strip-trailing-slash={false}
|
||||
>
|
||||
<button data-open-modal disabled>
|
||||
{
|
||||
/* The span is `aria-hidden` because it is not shown on small screens. Instead, the icon label is used for accessibility purposes. */
|
||||
}
|
||||
<Icon name="magnifier" label={labels['search.label']} />
|
||||
<span class="sl-hidden md:sl-block" aria-hidden="true">{labels['search.label']}</span>
|
||||
<Icon
|
||||
name="forward-slash"
|
||||
class="sl-hidden md:sl-block"
|
||||
label={labels['search.shortcutLabel']}
|
||||
/>
|
||||
</button>
|
||||
|
||||
<dialog style="padding:0" aria-label={labels['search.label']}>
|
||||
<div class="dialog-frame sl-flex">
|
||||
{
|
||||
/* TODO: Make the layout of this button flexible to accommodate different word lengths. Currently hard-coded for English: “Cancel” */
|
||||
}
|
||||
<button data-close-modal class="sl-flex md:sl-hidden">
|
||||
{labels['search.cancelLabel']}
|
||||
</button>
|
||||
{
|
||||
<div class="search-container">
|
||||
<div id="starlight__search" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</dialog>
|
||||
</site-search>
|
||||
|
||||
<script>
|
||||
class SiteSearch extends HTMLElement {
|
||||
translations = {}
|
||||
stripTrailingSlash = (path: string) => path.replace(/(.)\/(#.*)?$/, '$1$2');
|
||||
|
||||
connectedCallback () {
|
||||
this.translations = {};
|
||||
try {
|
||||
this.translations = JSON.parse(this.dataset.translations || '{}');
|
||||
} catch {}
|
||||
|
||||
|
||||
this.openBtn.addEventListener('click', this.openModal);
|
||||
this.openBtn.disabled = false;
|
||||
this.closeBtn.addEventListener('click', this.closeModal);
|
||||
this.dialog.addEventListener('close', this.handleClose)
|
||||
|
||||
// Listen for `/` and `cmd + k` keyboard shortcuts.
|
||||
window.addEventListener('keydown', this.handleKeyDown);
|
||||
|
||||
window.addEventListener('DOMContentLoaded', this.loadPageFind);
|
||||
window.addEventListener('turbo:load', this.loadPageFind);
|
||||
|
||||
}
|
||||
|
||||
disconnectedCallback () {
|
||||
this.openBtn.removeEventListener('click', this.openModal);
|
||||
this.closeBtn.removeEventListener('click', this.closeModal);
|
||||
this.dialog.removeEventListener('close', this.handleClose)
|
||||
|
||||
// Listen for `/` and `cmd + k` keyboard shortcuts.
|
||||
window.removeEventListener('keydown', this.handleKeyDown);
|
||||
window.removeEventListener('DOMContentLoaded', this.loadPageFind);
|
||||
window.removeEventListener('turbo:load', this.loadPageFind);
|
||||
}
|
||||
|
||||
/** Close the modal if a user clicks on a link or outside of the modal. */
|
||||
onClick = (event: MouseEvent) => {
|
||||
const isLink = 'href' in (event.target || {});
|
||||
if (
|
||||
isLink ||
|
||||
(document.body.contains(event.target as Node) &&
|
||||
!this.dialogFrame.contains(event.target as Node))
|
||||
) {
|
||||
this.closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
closeModal = () => this.dialog.close();
|
||||
|
||||
openModal = (event?: MouseEvent) => {
|
||||
this.dialog.showModal();
|
||||
document.body.toggleAttribute('data-search-modal-open', true);
|
||||
this.querySelector('input')?.focus();
|
||||
event?.stopPropagation();
|
||||
window.addEventListener('click', this.onClick);
|
||||
};
|
||||
|
||||
|
||||
get openBtn () {
|
||||
return this.querySelector<HTMLButtonElement>('button[data-open-modal]')!;
|
||||
}
|
||||
|
||||
get closeBtn () {
|
||||
return this.querySelector<HTMLButtonElement>('button[data-close-modal]')!;
|
||||
}
|
||||
|
||||
get dialog () {
|
||||
return this.querySelector('dialog')!;
|
||||
}
|
||||
|
||||
get dialogFrame () {
|
||||
return this.querySelector('.dialog-frame')!;
|
||||
}
|
||||
|
||||
handleClose = () => {
|
||||
document.body.toggleAttribute('data-search-modal-open', false);
|
||||
window.removeEventListener('click', this.onClick);
|
||||
};
|
||||
|
||||
handleKeyDown = (e: KeyboardEvent) => {
|
||||
const isInput =
|
||||
document.activeElement instanceof HTMLElement &&
|
||||
(['input', 'select', 'textarea'].includes(document.activeElement.tagName.toLowerCase()) ||
|
||||
document.activeElement.isContentEditable);
|
||||
if (e.metaKey === true && e.key === 'k') {
|
||||
this.dialog.open ? this.closeModal() : this.openModal();
|
||||
e.preventDefault();
|
||||
} else if (e.key === '/' && !this.dialog.open && !isInput) {
|
||||
this.openModal();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
loadPageFind = () => {
|
||||
const onIdle = window.requestIdleCallback || ((cb) => setTimeout(cb, 1));
|
||||
onIdle(async () => {
|
||||
if (import.meta.env.DEV) {
|
||||
// Generate a fake search in dev by calling a JSON endpoint that generates the search.
|
||||
await fetch(import.meta.env.BASE_URL.replace(/\/$/, '') + '/pagefind-dev.json')
|
||||
}
|
||||
|
||||
const search = document.querySelector("#starlight__search")
|
||||
|
||||
// Clear out search between page loads.
|
||||
if (search) {
|
||||
search.firstElementChild?.remove()
|
||||
}
|
||||
|
||||
// @ts-expect-error — Missing types for @pagefind/default-ui package.
|
||||
const { PagefindUI } = await import('@pagefind/default-ui');
|
||||
new PagefindUI({
|
||||
element: '#starlight__search',
|
||||
baseUrl: import.meta.env.BASE_URL,
|
||||
bundlePath: import.meta.env.BASE_URL.replace(/\/$/, '') + '/pagefind/',
|
||||
showImages: false,
|
||||
translations: this.translations,
|
||||
showSubResults: true,
|
||||
processResult: (result: { url: string; sub_results: Array<{ url: string }> }) => {
|
||||
result.url = this.formatURL(result.url);
|
||||
result.sub_results = result.sub_results.map((sub_result) => {
|
||||
sub_result.url = this.formatURL(sub_result.url);
|
||||
return sub_result;
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get formatURL () {
|
||||
return this.shouldStrip ? this.stripTrailingSlash : (path: string) => path;
|
||||
}
|
||||
|
||||
get shouldStrip () {
|
||||
return this.dataset.stripTrailingSlash != null;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('site-search', SiteSearch);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
site-search {
|
||||
display: contents;
|
||||
}
|
||||
button[data-open-modal] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
color: var(--sl-color-gray-1);
|
||||
cursor: pointer;
|
||||
height: 2.5rem;
|
||||
font-size: var(--sl-text-xl);
|
||||
}
|
||||
|
||||
@media (min-width: 50rem) {
|
||||
button[data-open-modal] {
|
||||
border: 1px solid var(--sl-color-gray-5);
|
||||
border-radius: 0.5rem;
|
||||
padding-inline-start: 0.75rem;
|
||||
padding-inline-end: 1rem;
|
||||
background-color: var(--sl-color-black);
|
||||
color: var(--sl-color-gray-2);
|
||||
font-size: var(--sl-text-sm);
|
||||
width: 100%;
|
||||
max-width: 22rem;
|
||||
}
|
||||
button[data-open-modal]:hover {
|
||||
border-color: var(--sl-color-gray-2);
|
||||
color: var(--sl-color-white);
|
||||
}
|
||||
|
||||
button[data-open-modal] > :last-child {
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
}
|
||||
|
||||
dialog {
|
||||
margin: 0;
|
||||
background-color: var(--sl-color-gray-6);
|
||||
border: 1px solid var(--sl-color-gray-5);
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
box-shadow: var(--sl-shadow-lg);
|
||||
}
|
||||
dialog[open] {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
dialog::backdrop {
|
||||
background-color: var(--sl-color-backdrop-overlay);
|
||||
-webkit-backdrop-filter: blur(0.25rem);
|
||||
backdrop-filter: blur(0.25rem);
|
||||
}
|
||||
|
||||
.dialog-frame {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
button[data-close-modal] {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
align-items: center;
|
||||
align-self: flex-end;
|
||||
height: calc(64px * var(--pagefind-ui-scale));
|
||||
padding: 0.25rem;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
color: var(--sl-color-text-accent);
|
||||
}
|
||||
|
||||
#starlight__search {
|
||||
--pagefind-ui-primary: var(--sl-color-accent-light);
|
||||
--pagefind-ui-text: var(--sl-color-gray-2);
|
||||
--pagefind-ui-font: var(--__sl-font);
|
||||
--pagefind-ui-background: var(--sl-color-black);
|
||||
--pagefind-ui-border: var(--sl-color-gray-5);
|
||||
--pagefind-ui-border-width: 1px;
|
||||
--sl-search-cancel-space: 5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 50rem) {
|
||||
#starlight__search {
|
||||
--sl-search-cancel-space: 0px;
|
||||
}
|
||||
|
||||
dialog {
|
||||
margin: 4rem auto auto;
|
||||
border-radius: 0.5rem;
|
||||
width: 90%;
|
||||
max-width: 40rem;
|
||||
height: max-content;
|
||||
min-height: 15rem;
|
||||
max-height: calc(100% - 8rem);
|
||||
}
|
||||
|
||||
.dialog-frame {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style is:global>
|
||||
[data-search-modal-open] {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#starlight__search {
|
||||
--sl-search-result-spacing: calc(1.25rem * var(--pagefind-ui-scale));
|
||||
--sl-search-result-pad-inline-start: calc(3.75rem * var(--pagefind-ui-scale));
|
||||
--sl-search-result-pad-inline-end: calc(1.25rem * var(--pagefind-ui-scale));
|
||||
--sl-search-result-pad-block: calc(0.9375rem * var(--pagefind-ui-scale));
|
||||
--sl-search-result-nested-pad-block: calc(0.625rem * var(--pagefind-ui-scale));
|
||||
--sl-search-corners: calc(0.3125rem * var(--pagefind-ui-scale));
|
||||
--sl-search-page-icon-size: calc(1.875rem * var(--pagefind-ui-scale));
|
||||
--sl-search-page-icon-inline-start: calc(
|
||||
(var(--sl-search-result-pad-inline-start) - var(--sl-search-page-icon-size)) / 2
|
||||
);
|
||||
--sl-search-tree-diagram-size: calc(2.5rem * var(--pagefind-ui-scale));
|
||||
--sl-search-tree-diagram-inline-start: calc(
|
||||
(var(--sl-search-result-pad-inline-start) - var(--sl-search-tree-diagram-size)) / 2
|
||||
);
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__form::before {
|
||||
--pagefind-ui-text: var(--sl-color-gray-1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__search-input {
|
||||
color: var(--sl-color-white);
|
||||
font-weight: 400;
|
||||
width: calc(100% - var(--sl-search-cancel-space));
|
||||
}
|
||||
|
||||
#starlight__search input:focus {
|
||||
--pagefind-ui-border: var(--sl-color-accent);
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__search-clear {
|
||||
inset-inline-end: var(--sl-search-cancel-space);
|
||||
width: calc(60px * var(--pagefind-ui-scale));
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
overflow: hidden;
|
||||
}
|
||||
#starlight__search .pagefind-ui__search-clear:focus {
|
||||
outline: 1px solid var(--sl-color-accent);
|
||||
}
|
||||
#starlight__search .pagefind-ui__search-clear::before {
|
||||
content: '';
|
||||
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m13.41 12 6.3-6.29a1 1 0 1 0-1.42-1.42L12 10.59l-6.29-6.3a1 1 0 0 0-1.42 1.42l6.3 6.29-6.3 6.29a1 1 0 0 0 .33 1.64 1 1 0 0 0 1.09-.22l6.29-6.3 6.29 6.3a1 1 0 0 0 1.64-.33 1 1 0 0 0-.22-1.09L13.41 12Z'/%3E%3C/svg%3E")
|
||||
center / 50% no-repeat;
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m13.41 12 6.3-6.29a1 1 0 1 0-1.42-1.42L12 10.59l-6.29-6.3a1 1 0 0 0-1.42 1.42l6.3 6.29-6.3 6.29a1 1 0 0 0 .33 1.64 1 1 0 0 0 1.09-.22l6.29-6.3 6.29 6.3a1 1 0 0 0 1.64-.33 1 1 0 0 0-.22-1.09L13.41 12Z'/%3E%3C/svg%3E")
|
||||
center / 50% no-repeat;
|
||||
background-color: var(--sl-color-text-accent);
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__results > * + * {
|
||||
margin-top: var(--sl-search-result-spacing);
|
||||
}
|
||||
#starlight__search .pagefind-ui__result {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-nested {
|
||||
position: relative;
|
||||
padding: var(--sl-search-result-nested-pad-block) var(--sl-search-result-pad-inline-end);
|
||||
padding-inline-start: var(--sl-search-result-pad-inline-start);
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)),
|
||||
#starlight__search .pagefind-ui__result-nested {
|
||||
position: relative;
|
||||
background-color: var(--sl-color-black);
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)):hover,
|
||||
#starlight__search
|
||||
.pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)):focus-within,
|
||||
#starlight__search .pagefind-ui__result-nested:hover,
|
||||
#starlight__search .pagefind-ui__result-nested:focus-within {
|
||||
outline: 1px solid var(--sl-color-accent-high);
|
||||
}
|
||||
|
||||
#starlight__search
|
||||
.pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)):focus-within,
|
||||
#starlight__search .pagefind-ui__result-nested:focus-within {
|
||||
background-color: var(--sl-color-accent-low);
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-thumb,
|
||||
#starlight__search .pagefind-ui__result-inner {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-inner > :first-child {
|
||||
border-radius: var(--sl-search-corners) var(--sl-search-corners) 0 0;
|
||||
}
|
||||
#starlight__search .pagefind-ui__result-inner > :last-child {
|
||||
border-radius: 0 0 var(--sl-search-corners) var(--sl-search-corners);
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-inner > .pagefind-ui__result-title {
|
||||
padding: var(--sl-search-result-pad-block) var(--sl-search-result-pad-inline-end);
|
||||
padding-inline-start: var(--sl-search-result-pad-inline-start);
|
||||
}
|
||||
#starlight__search .pagefind-ui__result-inner > .pagefind-ui__result-title::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
inset-inline-start: var(--sl-search-page-icon-inline-start);
|
||||
width: var(--sl-search-page-icon-size);
|
||||
background: var(--sl-color-gray-3);
|
||||
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 24 24'%3E%3Cpath d='M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3V8l-6-6a1 1 0 0 0-1 0H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V9Zm-6-4 3 3h-2a1 1 0 0 1-1-1V5Zm4 14a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z'/%3E%3C/svg%3E")
|
||||
center no-repeat;
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 24 24'%3E%3Cpath d='M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3V8l-6-6a1 1 0 0 0-1 0H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V9Zm-6-4 3 3h-2a1 1 0 0 1-1-1V5Zm4 14a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z'/%3E%3C/svg%3E")
|
||||
center no-repeat;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-inner {
|
||||
align-items: stretch;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-link {
|
||||
position: unset;
|
||||
--pagefind-ui-text: var(--sl-color-white);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-link:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-nested .pagefind-ui__result-link::before {
|
||||
content: unset;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-nested::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
inset-inline-start: var(--sl-search-tree-diagram-inline-start);
|
||||
width: var(--sl-search-tree-diagram-size);
|
||||
background: var(--sl-color-gray-4);
|
||||
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' viewBox='0 0 16 1000' preserveAspectRatio='xMinYMin slice'%3E%3Cpath d='M8 0v1000m6-988H8'/%3E%3C/svg%3E")
|
||||
0% 0% / 100% no-repeat;
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' viewBox='0 0 16 1000' preserveAspectRatio='xMinYMin slice'%3E%3Cpath d='M8 0v1000m6-988H8'/%3E%3C/svg%3E")
|
||||
0% 0% / 100% no-repeat;
|
||||
}
|
||||
#starlight__search .pagefind-ui__result-nested:last-child::before {
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 16 16'%3E%3Cpath d='M8 0v12m6 0H8'/%3E%3C/svg%3E");
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 16 16'%3E%3Cpath d='M8 0v12m6 0H8'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* Flip page and tree icons around the vertical axis when in an RTL layout. */
|
||||
[dir='rtl'] .pagefind-ui__result-title::before,
|
||||
[dir='rtl'] .pagefind-ui__result-nested::before {
|
||||
transform: matrix(-1, 0, 0, 1, 0, 0);
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-link::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-excerpt {
|
||||
font-size: calc(1rem * var(--pagefind-ui-scale));
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
#starlight__search mark {
|
||||
color: var(--sl-color-gray-2);
|
||||
background-color: transparent;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
63
docs/src/components/overrides/TableOfContents.astro
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
import { default as StarlightTOC } from '@astrojs/starlight/components/TableOfContents.astro';
|
||||
import type { Props } from "@astrojs/starlight/props"
|
||||
const { labels, toc, slug, entry, id } = Astro.props;
|
||||
|
||||
import { getComponentFromFileName } from "../../js/cem.js"
|
||||
|
||||
if (slug.includes("components/")) {
|
||||
const component = getComponentFromFileName(slug + ".md")
|
||||
|
||||
const {
|
||||
hasSlots,
|
||||
hasProperties,
|
||||
hasEvents,
|
||||
hasMethods,
|
||||
hasCssProperties,
|
||||
hasCssParts,
|
||||
hasAnimations,
|
||||
hasDependencies,
|
||||
} = component
|
||||
|
||||
toc.items.push({
|
||||
text: "Importing",
|
||||
slug: "importing",
|
||||
depth: 0,
|
||||
children: []
|
||||
})
|
||||
|
||||
const strToText = {
|
||||
CssProperties: "Custom Properties",
|
||||
CssParts: "Parts",
|
||||
}
|
||||
|
||||
const strToId = {
|
||||
CssProperties: "custom-properties",
|
||||
CssParts: "parts",
|
||||
}
|
||||
|
||||
;[
|
||||
"Slots",
|
||||
"Properties",
|
||||
"Events",
|
||||
"Methods",
|
||||
"CssProperties",
|
||||
"CssParts",
|
||||
"Animations",
|
||||
"Dependencies",
|
||||
].forEach((str) => {
|
||||
if (component[`has${str}`]) {
|
||||
toc.items.push({
|
||||
text: strToText[str] || str,
|
||||
slug: strToId[str] || str.toLowerCase(),
|
||||
depth: 0,
|
||||
children: []
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
---
|
||||
|
||||
<StarlightTOC {...Astro.props} />
|
||||
|
||||
16
docs/src/content/config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
import { docsSchema, i18nSchema } from '@astrojs/starlight/schema';
|
||||
|
||||
export const collections = {
|
||||
docs: defineCollection({
|
||||
schema: docsSchema({
|
||||
extend: z.object({
|
||||
// Add a new field to the schema.
|
||||
category: z
|
||||
.enum(['components', 'experimental', 'frameworks', 'getting-started', 'resources', 'tokens', 'tutorials'])
|
||||
.optional()
|
||||
})
|
||||
})
|
||||
}),
|
||||
i18n: defineCollection({ type: 'data', schema: i18nSchema() })
|
||||
};
|
||||
28
docs/src/content/docs/404.md
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Page Not Found
|
||||
description: "The page you were looking for couldn't be found."
|
||||
tableOfContents: false
|
||||
pageTitle: false
|
||||
---
|
||||
|
||||
<div style="text-align: center;">
|
||||
|
||||
# Page Not Found
|
||||
|
||||

|
||||
|
||||
The page you were looking for couldn't be found.
|
||||
|
||||
Press <kbd>/</kbd> to search, or [head back to the homepage](/).
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.content-panel + .content-panel {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.content-panel:first-of-type {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -1,13 +1,12 @@
|
||||
---
|
||||
meta:
|
||||
title: Alert
|
||||
description: Alerts are used to display important messages inline or as toast notifications.
|
||||
layout: component
|
||||
title: Alert
|
||||
description: Alerts are used to display important messages inline or as toast notifications.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
```html:preview
|
||||
<wa-alert open>
|
||||
<wa-icon slot="icon" name="info-circle"></wa-icon>
|
||||
<wa-icon slot="icon" name="circle-info" variant="regular"></wa-icon>
|
||||
This is a standard alert. You can customize its content and even the icon.
|
||||
</wa-alert>
|
||||
```
|
||||
@@ -18,13 +17,13 @@ import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
const App = () => (
|
||||
<WaAlert open>
|
||||
<WaIcon slot="icon" name="info-circle" />
|
||||
<WaIcon slot="icon" name="circle-info" variant="regular" />
|
||||
This is a standard alert. You can customize its content and even the icon.
|
||||
</WaAlert>
|
||||
);
|
||||
```
|
||||
|
||||
:::warning
|
||||
:::caution
|
||||
Alerts will not be visible if the `open` attribute is not present.
|
||||
:::
|
||||
|
||||
@@ -36,7 +35,7 @@ Set the `variant` attribute to change the alert's variant.
|
||||
|
||||
```html:preview
|
||||
<wa-alert variant="brand" open>
|
||||
<wa-icon slot="icon" name="info-circle"></wa-icon>
|
||||
<wa-icon slot="icon" name="circle-info" variant="regular"></wa-icon>
|
||||
<strong>This is super informative</strong><br />
|
||||
You can tell by how pretty the alert is.
|
||||
</wa-alert>
|
||||
@@ -44,7 +43,7 @@ Set the `variant` attribute to change the alert's variant.
|
||||
<br />
|
||||
|
||||
<wa-alert variant="success" open>
|
||||
<wa-icon slot="icon" name="check2-circle"></wa-icon>
|
||||
<wa-icon slot="icon" name="circle-check" variant="regular"></wa-icon>
|
||||
<strong>Your changes have been saved</strong><br />
|
||||
You can safely exit the app now.
|
||||
</wa-alert>
|
||||
@@ -52,7 +51,7 @@ Set the `variant` attribute to change the alert's variant.
|
||||
<br />
|
||||
|
||||
<wa-alert variant="neutral" open>
|
||||
<wa-icon slot="icon" name="gear"></wa-icon>
|
||||
<wa-icon slot="icon" name="gear" variant="regular"></wa-icon>
|
||||
<strong>Your settings have been updated</strong><br />
|
||||
Settings will take effect on next login.
|
||||
</wa-alert>
|
||||
@@ -60,7 +59,7 @@ Set the `variant` attribute to change the alert's variant.
|
||||
<br />
|
||||
|
||||
<wa-alert variant="warning" open>
|
||||
<wa-icon slot="icon" name="exclamation-triangle"></wa-icon>
|
||||
<wa-icon slot="icon" name="triangle-exclamation" variant="regular"></wa-icon>
|
||||
<strong>Your session has ended</strong><br />
|
||||
Please login again to continue.
|
||||
</wa-alert>
|
||||
@@ -68,7 +67,7 @@ Set the `variant` attribute to change the alert's variant.
|
||||
<br />
|
||||
|
||||
<wa-alert variant="danger" open>
|
||||
<wa-icon slot="icon" name="exclamation-octagon"></wa-icon>
|
||||
<wa-icon slot="icon" name="circle-exclamation" variant="regular"></wa-icon>
|
||||
<strong>Your account has been deleted</strong><br />
|
||||
We're very sorry to see you go!
|
||||
</wa-alert>
|
||||
@@ -81,7 +80,7 @@ import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
const App = () => (
|
||||
<>
|
||||
<WaAlert variant="brand" open>
|
||||
<WaIcon slot="icon" name="info-circle" />
|
||||
<WaIcon slot="icon" name="circle-info" variant="regular" />
|
||||
<strong>This is super informative</strong>
|
||||
<br />
|
||||
You can tell by how pretty the alert is.
|
||||
@@ -90,7 +89,7 @@ const App = () => (
|
||||
<br />
|
||||
|
||||
<WaAlert variant="success" open>
|
||||
<WaIcon slot="icon" name="check2-circle" />
|
||||
<WaIcon slot="icon" name="circle-check" variant="regular" />
|
||||
<strong>Your changes have been saved</strong>
|
||||
<br />
|
||||
You can safely exit the app now.
|
||||
@@ -99,7 +98,7 @@ const App = () => (
|
||||
<br />
|
||||
|
||||
<WaAlert variant="neutral" open>
|
||||
<WaIcon slot="icon" name="gear" />
|
||||
<WaIcon slot="icon" name="gear" variant="regular" />
|
||||
<strong>Your settings have been updated</strong>
|
||||
<br />
|
||||
Settings will take effect on next login.
|
||||
@@ -108,7 +107,7 @@ const App = () => (
|
||||
<br />
|
||||
|
||||
<WaAlert variant="warning" open>
|
||||
<WaIcon slot="icon" name="exclamation-triangle" />
|
||||
<WaIcon slot="icon" name="triangle-exclamation" variant="regular" />
|
||||
<strong>Your session has ended</strong>
|
||||
<br />
|
||||
Please login again to continue.
|
||||
@@ -117,7 +116,7 @@ const App = () => (
|
||||
<br />
|
||||
|
||||
<WaAlert variant="danger" open>
|
||||
<WaIcon slot="icon" name="exclamation-octagon" />
|
||||
<WaIcon slot="icon" name="circle-exclamation" variant="regular" />
|
||||
<strong>Your account has been deleted</strong>
|
||||
<br />
|
||||
We're very sorry to see you go!
|
||||
@@ -132,7 +131,7 @@ Add the `closable` attribute to show a close button that will hide the alert.
|
||||
|
||||
```html:preview
|
||||
<wa-alert variant="brand" open closable class="alert-closable">
|
||||
<wa-icon slot="icon" name="info-circle"></wa-icon>
|
||||
<wa-icon slot="icon" name="circle-info" variant="regular"></wa-icon>
|
||||
You can close this alert any time!
|
||||
</wa-alert>
|
||||
|
||||
@@ -159,7 +158,7 @@ const App = () => {
|
||||
|
||||
return (
|
||||
<WaAlert open={open} closable onWaAfterHide={handleHide}>
|
||||
<WaIcon slot="icon" name="info-circle" />
|
||||
<WaIcon slot="icon" name="circle-info" variant="regular" />
|
||||
You can close this alert any time!
|
||||
</WaAlert>
|
||||
);
|
||||
@@ -193,7 +192,7 @@ Set the `duration` attribute to automatically hide an alert after a period of ti
|
||||
<wa-button variant="brand">Show Alert</wa-button>
|
||||
|
||||
<wa-alert variant="brand" duration="3000" closable>
|
||||
<wa-icon slot="icon" name="info-circle"></wa-icon>
|
||||
<wa-icon slot="icon" name="circle-info" variant="regular"></wa-icon>
|
||||
This alert will automatically hide itself after three seconds, unless you interact with it.
|
||||
</wa-alert>
|
||||
</div>
|
||||
@@ -236,7 +235,7 @@ const App = () => {
|
||||
</WaButton>
|
||||
|
||||
<WaAlert variant="brand" duration="3000" open={open} closable onWaAfterHide={() => setOpen(false)}>
|
||||
<WaIcon slot="icon" name="info-circle" />
|
||||
<WaIcon slot="icon" name="circle-info" variant="regular" />
|
||||
This alert will automatically hide itself after three seconds, unless you interact with it.
|
||||
</WaAlert>
|
||||
</div>
|
||||
@@ -262,31 +261,31 @@ You should always use the `closable` attribute so users can dismiss the notifica
|
||||
<wa-button variant="danger">Danger</wa-button>
|
||||
|
||||
<wa-alert variant="brand" duration="3000" closable>
|
||||
<wa-icon slot="icon" name="info-circle"></wa-icon>
|
||||
<wa-icon slot="icon" name="circle-info" variant="regular"></wa-icon>
|
||||
<strong>This is super informative</strong><br />
|
||||
You can tell by how pretty the alert is.
|
||||
</wa-alert>
|
||||
|
||||
<wa-alert variant="success" duration="3000" closable>
|
||||
<wa-icon slot="icon" name="check2-circle"></wa-icon>
|
||||
<wa-icon slot="icon" name="circle-check" variant="regular"></wa-icon>
|
||||
<strong>Your changes have been saved</strong><br />
|
||||
You can safely exit the app now.
|
||||
</wa-alert>
|
||||
|
||||
<wa-alert variant="neutral" duration="3000" closable>
|
||||
<wa-icon slot="icon" name="gear"></wa-icon>
|
||||
<wa-icon slot="icon" name="gear" variant="regular"></wa-icon>
|
||||
<strong>Your settings have been updated</strong><br />
|
||||
Settings will take effect on next login.
|
||||
</wa-alert>
|
||||
|
||||
<wa-alert variant="warning" duration="3000" closable>
|
||||
<wa-icon slot="icon" name="exclamation-triangle"></wa-icon>
|
||||
<wa-icon slot="icon" name="triangle-exclamation" variant="regular"></wa-icon>
|
||||
<strong>Your session has ended</strong><br />
|
||||
Please login again to continue.
|
||||
</wa-alert>
|
||||
|
||||
<wa-alert variant="danger" duration="3000" closable>
|
||||
<wa-icon slot="icon" name="exclamation-octagon"></wa-icon>
|
||||
<wa-icon slot="icon" name="circle-exclamation" variant="regular"></wa-icon>
|
||||
<strong>Your account has been deleted</strong><br />
|
||||
We're very sorry to see you go!
|
||||
</wa-alert>
|
||||
@@ -344,35 +343,35 @@ const App = () => {
|
||||
</WaButton>
|
||||
|
||||
<WaAlert ref={brand} variant="brand" duration="3000" closable>
|
||||
<WaIcon slot="icon" name="info-circle" />
|
||||
<WaIcon slot="icon" name="circle-info" variant="regular" />
|
||||
<strong>This is super informative</strong>
|
||||
<br />
|
||||
You can tell by how pretty the alert is.
|
||||
</WaAlert>
|
||||
|
||||
<WaAlert ref={success} variant="success" duration="3000" closable>
|
||||
<WaIcon slot="icon" name="check2-circle" />
|
||||
<WaIcon slot="icon" name="circle-check" variant="regular" />
|
||||
<strong>Your changes have been saved</strong>
|
||||
<br />
|
||||
You can safely exit the app now.
|
||||
</WaAlert>
|
||||
|
||||
<WaAlert ref={neutral} variant="neutral" duration="3000" closable>
|
||||
<WaIcon slot="icon" name="gear" />
|
||||
<WaIcon slot="icon" name="gear" variant="regular" />
|
||||
<strong>Your settings have been updated</strong>
|
||||
<br />
|
||||
Settings will take effect on next login.
|
||||
</WaAlert>
|
||||
|
||||
<WaAlert ref={warning} variant="warning" duration="3000" closable>
|
||||
<WaIcon slot="icon" name="exclamation-triangle" />
|
||||
<WaIcon slot="icon" name="triangle-exclamation" variant="regular" />
|
||||
<strong>Your session has ended</strong>
|
||||
<br />
|
||||
Please login again to continue.
|
||||
</WaAlert>
|
||||
|
||||
<WaAlert ref={danger} variant="danger" duration="3000" closable>
|
||||
<WaIcon slot="icon" name="exclamation-octagon" />
|
||||
<WaIcon slot="icon" name="circle-exclamation" variant="regular" />
|
||||
<strong>Your account has been deleted</strong>
|
||||
<br />
|
||||
We're very sorry to see you go!
|
||||
@@ -404,13 +403,13 @@ For convenience, you can create a utility that emits toast notifications with a
|
||||
}
|
||||
|
||||
// Custom function to emit toast notifications
|
||||
function notify(message, variant = 'brand', icon = 'info-circle', duration = 3000) {
|
||||
function notify(message, variant = 'brand', icon = 'circle-info', duration = 3000) {
|
||||
const alert = Object.assign(document.createElement('wa-alert'), {
|
||||
variant,
|
||||
closable: true,
|
||||
duration: duration,
|
||||
innerHTML: `
|
||||
<wa-icon name="${icon}" slot="icon"></wa-icon>
|
||||
<wa-icon name="${icon}" variant="regular" slot="icon"></wa-icon>
|
||||
${escapeHtml(message)}
|
||||
`
|
||||
});
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Animated Image
|
||||
description: A component for displaying animated GIFs and WEBPs that play and pause on interaction.
|
||||
layout: component
|
||||
title: Animated Image
|
||||
description: A component for displaying animated GIFs and WEBPs that play and pause on interaction.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
```html:preview
|
||||
@@ -61,8 +60,6 @@ To set a custom size, apply a width and/or height to the host element.
|
||||
</wa-animated-image>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';
|
||||
|
||||
@@ -75,8 +72,6 @@ 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.
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Animation
|
||||
description: Animate elements declaratively with nearly 100 baked-in presets, or roll your own with custom keyframes.
|
||||
layout: component
|
||||
title: Animation
|
||||
description: Animate elements declaratively with nearly 100 baked-in presets, or roll your own with custom keyframes.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
To animate an element, wrap it in `<wa-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.
|
||||
@@ -20,7 +19,7 @@ To animate an element, wrap it in `<wa-animation>` and set an animation `name`.
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: var(--wa-color-brand-fill-vivid);
|
||||
background-color: var(--wa-color-brand-spot);
|
||||
margin: 1.5rem;
|
||||
}
|
||||
</style>
|
||||
@@ -34,7 +33,7 @@ const css = `
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: var(--wa-color-brand-fill-vivid);
|
||||
background-color: var(--wa-color-brand-spot);
|
||||
margin: 1.5rem;
|
||||
}
|
||||
`;
|
||||
@@ -80,7 +79,8 @@ This example demonstrates all of the baked-in animations and easings. Animations
|
||||
<div class="controls">
|
||||
<wa-select label="Animation" value="bounce"></wa-select>
|
||||
<wa-select label="Easing" value="linear"></wa-select>
|
||||
<wa-input label="Playback Rate" type="number" min="0" max="2" step=".25" value="1"></wa-input>
|
||||
<wa-input label="Playback Rate" type="number" min="0" max="2" step=".25" value="1">
|
||||
</wa-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -120,7 +120,7 @@ This example demonstrates all of the baked-in animations and easings. Animations
|
||||
.animation-sandbox .box {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: var(--wa-color-brand-fill-vivid);
|
||||
background-color: var(--wa-color-brand-spot);
|
||||
}
|
||||
|
||||
.animation-sandbox .controls {
|
||||
@@ -134,6 +134,10 @@ This example demonstrates all of the baked-in animations and easings. Animations
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
|
||||
```
|
||||
|
||||
### Using Intersection Observer
|
||||
|
||||
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.
|
||||
@@ -166,7 +170,7 @@ Use an [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: var(--wa-color-brand-fill-vivid);
|
||||
background-color: var(--wa-color-brand-spot);
|
||||
}
|
||||
</style>
|
||||
```
|
||||
@@ -184,7 +188,7 @@ const css = `
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: var(--wa-color-brand-fill-vivid);
|
||||
background-color: var(--wa-color-brand-spot);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -256,7 +260,7 @@ Supply your own [keyframe formats](https://developer.mozilla.org/en-US/docs/Web/
|
||||
.animation-keyframes .box {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: var(--wa-color-brand-fill-vivid);
|
||||
background-color: var(--wa-color-brand-spot);
|
||||
}
|
||||
</style>
|
||||
```
|
||||
@@ -268,7 +272,7 @@ const css = `
|
||||
.animation-keyframes .box {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: var(--wa-color-brand-fill-vivid);
|
||||
background-color: var(--wa-color-brand-spot);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Avatar
|
||||
description: Avatars are used to represent a person or object.
|
||||
layout: component
|
||||
title: Avatar
|
||||
description: Avatars are used to represent a person or object.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
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.
|
||||
@@ -57,13 +56,13 @@ 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
|
||||
<wa-avatar initials="SL" label="Avatar with initials: SL"></wa-avatar>
|
||||
<wa-avatar initials="WA" label="Avatar with initials: SL"></wa-avatar>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import WaAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
||||
|
||||
const App = () => <WaAvatar initials="SL" label="Avatar with initials: SL" />;
|
||||
const App = () => <WaAvatar initials="WA" label="Avatar with initials: SL" />;
|
||||
```
|
||||
|
||||
### Custom Icons
|
||||
@@ -72,15 +71,15 @@ When no image or initials are set, an icon will be shown. The default avatar sho
|
||||
|
||||
```html:preview
|
||||
<wa-avatar label="Avatar with an image icon">
|
||||
<wa-icon slot="icon" name="image"></wa-icon>
|
||||
<wa-icon slot="icon" name="image" variant="solid"></wa-icon>
|
||||
</wa-avatar>
|
||||
|
||||
<wa-avatar label="Avatar with an archive icon">
|
||||
<wa-icon slot="icon" name="archive"></wa-icon>
|
||||
<wa-icon slot="icon" name="archive" variant="solid"></wa-icon>
|
||||
</wa-avatar>
|
||||
|
||||
<wa-avatar label="Avatar with a briefcase icon">
|
||||
<wa-icon slot="icon" name="briefcase"></wa-icon>
|
||||
<wa-icon slot="icon" name="briefcase" variant="solid"></wa-icon>
|
||||
</wa-avatar>
|
||||
```
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Badge
|
||||
description: Badges are used to draw attention and display statuses or counts.
|
||||
layout: component
|
||||
title: Badge
|
||||
description: Badges are used to draw attention and display statuses or counts.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
```html:preview
|
||||
@@ -154,8 +153,6 @@ One of the most common use cases for badges is attaching them to buttons. To mak
|
||||
</wa-button>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaBadge from '@shoelace-style/shoelace/dist/react/badge';
|
||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
@@ -184,8 +181,6 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### With Menu Items
|
||||
|
||||
When including badges in menu items, use the `suffix` slot to make sure they're aligned correctly.
|
||||
@@ -198,8 +193,6 @@ When including badges in menu items, use the `suffix` slot to make sure they're
|
||||
</wa-menu>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaBadge from '@shoelace-style/shoelace/dist/react/badge';
|
||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
@@ -227,5 +220,3 @@ const App = () => (
|
||||
</WaMenu>
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
@@ -1,14 +1,13 @@
|
||||
---
|
||||
meta:
|
||||
title: Breadcrumb Item
|
||||
description: Breadcrumb Items are used inside breadcrumbs to represent different links.
|
||||
layout: component
|
||||
title: Breadcrumb Item
|
||||
description: Breadcrumb Items are used inside breadcrumbs to represent different links.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
```html:preview
|
||||
<wa-breadcrumb>
|
||||
<wa-breadcrumb-item>
|
||||
<wa-icon slot="prefix" name="house"></wa-icon>
|
||||
<wa-icon slot="prefix" name="house" variant="solid"></wa-icon>
|
||||
Home
|
||||
</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Clothing</wa-breadcrumb-item>
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Breadcrumb
|
||||
description: Breadcrumbs provide a group of links so users can easily navigate a website's hierarchy.
|
||||
layout: component
|
||||
title: Breadcrumb
|
||||
description: Breadcrumbs provide a group of links so users can easily navigate a website's hierarchy.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
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.
|
||||
@@ -73,7 +72,7 @@ Use the `separator` slot to change the separator that goes between breadcrumb it
|
||||
|
||||
```html:preview
|
||||
<wa-breadcrumb>
|
||||
<wa-icon name="dot" slot="separator"></wa-icon>
|
||||
<wa-icon slot="separator" name="angles-right" variant="solid"></wa-icon>
|
||||
<wa-breadcrumb-item>First</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Second</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Third</wa-breadcrumb-item>
|
||||
@@ -82,7 +81,7 @@ Use the `separator` slot to change the separator that goes between breadcrumb it
|
||||
<br />
|
||||
|
||||
<wa-breadcrumb>
|
||||
<wa-icon name="arrow-right" slot="separator"></wa-icon>
|
||||
<wa-icon slot="separator" name="arrow-right" variant="solid"></wa-icon>
|
||||
<wa-breadcrumb-item>First</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Second</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Third</wa-breadcrumb-item>
|
||||
@@ -106,7 +105,7 @@ import WaBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-ite
|
||||
const App = () => (
|
||||
<>
|
||||
<WaBreadcrumb>
|
||||
<wa-icon name="dot" slot="separator" />
|
||||
<wa-icon slot="separator" name="angles-right" variant="solid" />
|
||||
<WaBreadcrumbItem>First</WaBreadcrumbItem>
|
||||
<WaBreadcrumbItem>Second</WaBreadcrumbItem>
|
||||
<WaBreadcrumbItem>Third</WaBreadcrumbItem>
|
||||
@@ -115,7 +114,7 @@ const App = () => (
|
||||
<br />
|
||||
|
||||
<WaBreadcrumb>
|
||||
<wa-icon name="arrow-right" slot="separator" />
|
||||
<wa-icon slot="separator" name="arrow-right" variant="solid" />
|
||||
<WaBreadcrumbItem>First</WaBreadcrumbItem>
|
||||
<WaBreadcrumbItem>Second</WaBreadcrumbItem>
|
||||
<WaBreadcrumbItem>Third</WaBreadcrumbItem>
|
||||
@@ -140,7 +139,7 @@ Use the `prefix` slot to add content before any breadcrumb item.
|
||||
```html:preview
|
||||
<wa-breadcrumb>
|
||||
<wa-breadcrumb-item>
|
||||
<wa-icon slot="prefix" name="house"></wa-icon>
|
||||
<wa-icon slot="prefix" name="house" variant="solid"></wa-icon>
|
||||
Home
|
||||
</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Articles</wa-breadcrumb-item>
|
||||
@@ -156,7 +155,7 @@ import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
const App = () => (
|
||||
<WaBreadcrumb>
|
||||
<WaBreadcrumbItem>
|
||||
<WaIcon slot="prefix" name="house" />
|
||||
<WaIcon slot="prefix" name="house" variant="solid" />
|
||||
Home
|
||||
</WaBreadcrumbItem>
|
||||
<WaBreadcrumbItem>Articles</WaBreadcrumbItem>
|
||||
@@ -175,7 +174,7 @@ Use the `suffix` slot to add content after any breadcrumb item.
|
||||
<wa-breadcrumb-item>Policies</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>
|
||||
Security
|
||||
<wa-icon slot="suffix" name="shield-lock"></wa-icon>
|
||||
<wa-icon slot="suffix" name="shield" variant="solid"></wa-icon>
|
||||
</wa-breadcrumb-item>
|
||||
</wa-breadcrumb>
|
||||
```
|
||||
@@ -191,7 +190,7 @@ const App = () => (
|
||||
<WaBreadcrumbItem>Policies</WaBreadcrumbItem>
|
||||
<WaBreadcrumbItem>
|
||||
Security
|
||||
<WaIcon slot="suffix" name="shield-lock"></WaIcon>
|
||||
<WaIcon slot="suffix" name="shield" variant="solid"></WaIcon>
|
||||
</WaBreadcrumbItem>
|
||||
</WaBreadcrumb>
|
||||
);
|
||||
@@ -209,8 +208,8 @@ Dropdown menus can be placed in a prefix or suffix slot to provide additional op
|
||||
<wa-breadcrumb-item>
|
||||
Web Design
|
||||
<wa-dropdown slot="suffix">
|
||||
<wa-button slot="trigger" size="small" circle>
|
||||
<wa-icon label="More options" name="three-dots"></wa-icon>
|
||||
<wa-button slot="trigger" size="small" pill>
|
||||
<wa-icon label="More options" name="ellipsis" variant="solid"></wa-icon>
|
||||
</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item type="checkbox" checked>Web Design</wa-menu-item>
|
||||
@@ -241,8 +240,8 @@ const App = () => (
|
||||
<WaBreadcrumbItem>
|
||||
Web Design
|
||||
<WaDropdown slot="suffix">
|
||||
<WaButton slot="trigger" size="small" circle>
|
||||
<WaIcon label="More options" name="three-dots"></WaIcon>
|
||||
<WaButton slot="trigger" size="small" pill>
|
||||
<WaIcon label="More options" name="ellipsis"></WaIcon>
|
||||
</WaButton>
|
||||
<WaMenu>
|
||||
<WaMenuItem type="checkbox" checked>
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Button Group
|
||||
description: Button groups can be used to group related buttons into sections.
|
||||
layout: component
|
||||
title: Button Group
|
||||
description: Button groups can be used to group related buttons into sections.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
```html:preview
|
||||
@@ -401,34 +400,34 @@ Create interactive toolbars with button groups.
|
||||
<div class="button-group-toolbar">
|
||||
<wa-button-group label="History">
|
||||
<wa-tooltip content="Undo">
|
||||
<wa-button><wa-icon name="arrow-counterclockwise" label="Undo"></wa-icon></wa-button>
|
||||
<wa-button><wa-icon name="undo" variant="solid" label="Undo"></wa-icon></wa-button>
|
||||
</wa-tooltip>
|
||||
<wa-tooltip content="Redo">
|
||||
<wa-button><wa-icon name="arrow-clockwise" label="Redo"></wa-icon></wa-button>
|
||||
<wa-button><wa-icon name="redo" variant="solid" label="Redo"></wa-icon></wa-button>
|
||||
</wa-tooltip>
|
||||
</wa-button-group>
|
||||
|
||||
<wa-button-group label="Formatting">
|
||||
<wa-tooltip content="Bold">
|
||||
<wa-button><wa-icon name="type-bold" label="Bold"></wa-icon></wa-button>
|
||||
<wa-button><wa-icon name="bold" variant="solid" label="Bold"></wa-icon></wa-button>
|
||||
</wa-tooltip>
|
||||
<wa-tooltip content="Italic">
|
||||
<wa-button><wa-icon name="type-italic" label="Italic"></wa-icon></wa-button>
|
||||
<wa-button><wa-icon name="italic" variant="solid" label="Italic"></wa-icon></wa-button>
|
||||
</wa-tooltip>
|
||||
<wa-tooltip content="Underline">
|
||||
<wa-button><wa-icon name="type-underline" label="Underline"></wa-icon></wa-button>
|
||||
<wa-button><wa-icon name="underline" variant="solid" label="Underline"></wa-icon></wa-button>
|
||||
</wa-tooltip>
|
||||
</wa-button-group>
|
||||
|
||||
<wa-button-group label="Alignment">
|
||||
<wa-tooltip content="Align Left">
|
||||
<wa-button><wa-icon name="justify-left" label="Align Left"></wa-icon></wa-button>
|
||||
<wa-button><wa-icon name="align-left" variant="solid" label="Align Left"></wa-icon></wa-button>
|
||||
</wa-tooltip>
|
||||
<wa-tooltip content="Align Center">
|
||||
<wa-button><wa-icon name="justify" label="Align Center"></wa-icon></wa-button>
|
||||
<wa-button><wa-icon name="align-center" variant="solid" label="Align Center"></wa-icon></wa-button>
|
||||
</wa-tooltip>
|
||||
<wa-tooltip content="Align Right">
|
||||
<wa-button><wa-icon name="justify-right" label="Align Right"></wa-icon></wa-button>
|
||||
<wa-button><wa-icon name="align-right" variant="solid" label="Align Right"></wa-icon></wa-button>
|
||||
</wa-tooltip>
|
||||
</wa-button-group>
|
||||
</div>
|
||||
@@ -458,12 +457,12 @@ const App = () => (
|
||||
<WaButtonGroup label="History">
|
||||
<WaTooltip content="Undo">
|
||||
<WaButton>
|
||||
<WaIcon name="arrow-counterclockwise"></WaIcon>
|
||||
<WaIcon name="undo" variant="solid"></WaIcon>
|
||||
</WaButton>
|
||||
</WaTooltip>
|
||||
<WaTooltip content="Redo">
|
||||
<WaButton>
|
||||
<WaIcon name="arrow-clockwise"></WaIcon>
|
||||
<WaIcon name="redo" variant="solid"></WaIcon>
|
||||
</WaButton>
|
||||
</WaTooltip>
|
||||
</WaButtonGroup>
|
||||
@@ -471,17 +470,17 @@ const App = () => (
|
||||
<WaButtonGroup label="Formatting">
|
||||
<WaTooltip content="Bold">
|
||||
<WaButton>
|
||||
<WaIcon name="type-bold"></WaIcon>
|
||||
<WaIcon name="bold" variant="solid"></WaIcon>
|
||||
</WaButton>
|
||||
</WaTooltip>
|
||||
<WaTooltip content="Italic">
|
||||
<WaButton>
|
||||
<WaIcon name="type-italic"></WaIcon>
|
||||
<WaIcon name="italic" variant="solid"></WaIcon>
|
||||
</WaButton>
|
||||
</WaTooltip>
|
||||
<WaTooltip content="Underline">
|
||||
<WaButton>
|
||||
<WaIcon name="type-underline"></WaIcon>
|
||||
<WaIcon name="underline" variant="solid"></WaIcon>
|
||||
</WaButton>
|
||||
</WaTooltip>
|
||||
</WaButtonGroup>
|
||||
@@ -489,17 +488,17 @@ const App = () => (
|
||||
<WaButtonGroup label="Alignment">
|
||||
<WaTooltip content="Align Left">
|
||||
<WaButton>
|
||||
<WaIcon name="justify-left"></WaIcon>
|
||||
<WaIcon name="align-left" variant="solid"></WaIcon>
|
||||
</WaButton>
|
||||
</WaTooltip>
|
||||
<WaTooltip content="Align Center">
|
||||
<WaButton>
|
||||
<WaIcon name="justify"></WaIcon>
|
||||
<WaIcon name="align-center" variant="solid"></WaIcon>
|
||||
</WaButton>
|
||||
</WaTooltip>
|
||||
<WaTooltip content="Align Right">
|
||||
<WaButton>
|
||||
<WaIcon name="justify-right"></WaIcon>
|
||||
<WaIcon name="align-right" variant="solid"></WaIcon>
|
||||
</WaButton>
|
||||
</WaTooltip>
|
||||
</WaButtonGroup>
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Button
|
||||
description: Buttons represent actions that are available to the user.
|
||||
layout: component
|
||||
title: Button
|
||||
description: Buttons represent actions that are available to the user.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
```html:preview
|
||||
@@ -201,8 +200,6 @@ As expected, buttons can be given a custom width by setting the `width` attribut
|
||||
<wa-button size="large" style="width: 100%;">Large</wa-button>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
|
||||
@@ -221,62 +218,60 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Prefix and Suffix Icons
|
||||
|
||||
Use the `prefix` and `suffix` slots to add icons.
|
||||
|
||||
```html:preview
|
||||
<wa-button size="small">
|
||||
<wa-icon slot="prefix" name="gear"></wa-icon>
|
||||
<wa-icon slot="prefix" name="gear" variant="solid"></wa-icon>
|
||||
Settings
|
||||
</wa-button>
|
||||
|
||||
<wa-button size="small">
|
||||
<wa-icon slot="suffix" name="arrow-counterclockwise"></wa-icon>
|
||||
<wa-icon slot="suffix" name="undo" variant="solid"></wa-icon>
|
||||
Refresh
|
||||
</wa-button>
|
||||
|
||||
<wa-button size="small">
|
||||
<wa-icon slot="prefix" name="link-45deg"></wa-icon>
|
||||
<wa-icon slot="suffix" name="box-arrow-up-right"></wa-icon>
|
||||
<wa-icon slot="prefix" name="link" variant="solid"></wa-icon>
|
||||
<wa-icon slot="suffix" name="arrow-up-right-from-square" variant="solid"></wa-icon>
|
||||
Open
|
||||
</wa-button>
|
||||
|
||||
<br /><br />
|
||||
|
||||
<wa-button>
|
||||
<wa-icon slot="prefix" name="gear"></wa-icon>
|
||||
<wa-icon slot="prefix" name="gear" variant="solid"></wa-icon>
|
||||
Settings
|
||||
</wa-button>
|
||||
|
||||
<wa-button>
|
||||
<wa-icon slot="suffix" name="arrow-counterclockwise"></wa-icon>
|
||||
<wa-icon slot="suffix" name="undo" variant="solid"></wa-icon>
|
||||
Refresh
|
||||
</wa-button>
|
||||
|
||||
<wa-button>
|
||||
<wa-icon slot="prefix" name="link-45deg"></wa-icon>
|
||||
<wa-icon slot="suffix" name="box-arrow-up-right"></wa-icon>
|
||||
<wa-icon slot="prefix" name="link" variant="solid"></wa-icon>
|
||||
<wa-icon slot="suffix" name="arrow-up-right-from-square" variant="solid"></wa-icon>
|
||||
Open
|
||||
</wa-button>
|
||||
|
||||
<br /><br />
|
||||
|
||||
<wa-button size="large">
|
||||
<wa-icon slot="prefix" name="gear"></wa-icon>
|
||||
<wa-icon slot="prefix" name="gear" variant="solid"></wa-icon>
|
||||
Settings
|
||||
</wa-button>
|
||||
|
||||
<wa-button size="large">
|
||||
<wa-icon slot="suffix" name="arrow-counterclockwise"></wa-icon>
|
||||
<wa-icon slot="suffix" name="undo" variant="solid"></wa-icon>
|
||||
Refresh
|
||||
</wa-button>
|
||||
|
||||
<wa-button size="large">
|
||||
<wa-icon slot="prefix" name="link-45deg"></wa-icon>
|
||||
<wa-icon slot="suffix" name="box-arrow-up-right"></wa-icon>
|
||||
<wa-icon slot="prefix" name="link" variant="solid"></wa-icon>
|
||||
<wa-icon slot="suffix" name="arrow-up-right-from-square" variant="solid"></wa-icon>
|
||||
Open
|
||||
</wa-button>
|
||||
```
|
||||
@@ -288,18 +283,18 @@ import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
const App = () => (
|
||||
<>
|
||||
<WaButton size="small">
|
||||
<WaIcon slot="prefix" name="gear"></WaIcon>
|
||||
<WaIcon slot="prefix" name="gear" variant="solid"></WaIcon>
|
||||
Settings
|
||||
</WaButton>
|
||||
|
||||
<WaButton size="small">
|
||||
<WaIcon slot="suffix" name="arrow-counterclockwise"></WaIcon>
|
||||
<WaIcon slot="suffix" name="undo" variant="solid"></WaIcon>
|
||||
Refresh
|
||||
</WaButton>
|
||||
|
||||
<WaButton size="small">
|
||||
<WaIcon slot="prefix" name="link-45deg"></WaIcon>
|
||||
<WaIcon slot="suffix" name="box-arrow-up-right"></WaIcon>
|
||||
<WaIcon slot="prefix" name="link" variant="solid"></WaIcon>
|
||||
<WaIcon slot="suffix" name="arrow-up-right-from-square" variant="solid"></WaIcon>
|
||||
Open
|
||||
</WaButton>
|
||||
|
||||
@@ -307,18 +302,18 @@ const App = () => (
|
||||
<br />
|
||||
|
||||
<WaButton>
|
||||
<WaIcon slot="prefix" name="gear"></WaIcon>
|
||||
<WaIcon slot="prefix" name="gear" variant="solid"></WaIcon>
|
||||
Settings
|
||||
</WaButton>
|
||||
|
||||
<WaButton>
|
||||
<WaIcon slot="suffix" name="arrow-counterclockwise"></WaIcon>
|
||||
<WaIcon slot="suffix" name="undo" variant="solid"></WaIcon>
|
||||
Refresh
|
||||
</WaButton>
|
||||
|
||||
<WaButton>
|
||||
<WaIcon slot="prefix" name="link-45deg"></WaIcon>
|
||||
<WaIcon slot="suffix" name="box-arrow-up-right"></WaIcon>
|
||||
<WaIcon slot="prefix" name="link" variant="solid"></WaIcon>
|
||||
<WaIcon slot="suffix" name="arrow-up-right-from-square" variant="solid"></WaIcon>
|
||||
Open
|
||||
</WaButton>
|
||||
|
||||
@@ -326,18 +321,18 @@ const App = () => (
|
||||
<br />
|
||||
|
||||
<WaButton size="large">
|
||||
<WaIcon slot="prefix" name="gear"></WaIcon>
|
||||
<WaIcon slot="prefix" name="gear" variant="solid"></WaIcon>
|
||||
Settings
|
||||
</WaButton>
|
||||
|
||||
<WaButton size="large">
|
||||
<WaIcon slot="suffix" name="arrow-counterclockwise"></WaIcon>
|
||||
<WaIcon slot="suffix" name="undo" variant="solid"></WaIcon>
|
||||
Refresh
|
||||
</WaButton>
|
||||
|
||||
<WaButton size="large">
|
||||
<WaIcon slot="prefix" name="link-45deg"></WaIcon>
|
||||
<WaIcon slot="suffix" name="box-arrow-up-right"></WaIcon>
|
||||
<WaIcon slot="prefix" name="link" variant="solid"></WaIcon>
|
||||
<WaIcon slot="suffix" name="arrow-up-right-from-square" variant="solid"></WaIcon>
|
||||
Open
|
||||
</WaButton>
|
||||
</>
|
||||
@@ -374,7 +369,7 @@ const App = () => (
|
||||
|
||||
### Loading
|
||||
|
||||
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.
|
||||
Use the `loading` attribute to make a button busy. The width will remain the same as before, preventing adjacent elements from moving around.
|
||||
|
||||
```html:preview
|
||||
<wa-button variant="brand" loading>Brand</wa-button>
|
||||
@@ -459,7 +454,7 @@ This example demonstrates how to style buttons using a custom class. This is the
|
||||
wa-button.pink::part(base) {
|
||||
border-radius: 6px;
|
||||
border: solid 2px;
|
||||
background-color: #ff1493;
|
||||
background: #ff1493;
|
||||
border-top-color: #ff7ac1;
|
||||
border-left-color: #ff7ac1;
|
||||
border-bottom-color: #ad005c;
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Card
|
||||
description: Cards can be used to group related subjects in a container.
|
||||
layout: component
|
||||
title: Card
|
||||
description: Cards can be used to group related subjects in a container.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
```html:preview
|
||||
@@ -133,7 +132,7 @@ Headers can be used to display titles and more.
|
||||
<wa-card class="card-header">
|
||||
<div slot="header">
|
||||
Header Title
|
||||
<wa-icon-button name="gear" label="Settings"></wa-icon-button>
|
||||
<wa-icon-button name="gear" variant="solid" label="Settings"></wa-icon-button>
|
||||
</div>
|
||||
|
||||
This card has a header. You can put all sorts of things in it!
|
||||
@@ -189,7 +188,7 @@ const App = () => (
|
||||
<WaCard className="card-header">
|
||||
<div slot="header">
|
||||
Header Title
|
||||
<WaIconButton name="gear"></WaIconButton>
|
||||
<WaIconButton name="gear" variant="solid"></WaIconButton>
|
||||
</div>
|
||||
This card has a header. You can put all sorts of things in it!
|
||||
</WaCard>
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Carousel Item
|
||||
description: A carousel item represent a slide within a carousel.
|
||||
layout: component
|
||||
title: Carousel Item
|
||||
description: A carousel item represent a slide within a carousel.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
```html:preview
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Carousel
|
||||
description: Carousels display an arbitrary number of content slides along a horizontal or vertical axis.
|
||||
layout: component
|
||||
title: Carousel
|
||||
description: Carousels display an arbitrary number of content slides along a horizontal or vertical axis.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
```html:preview
|
||||
@@ -527,8 +526,6 @@ The `slides-per-page` attribute makes it possible to display multiple slides at
|
||||
</wa-carousel>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaCarousel from '@shoelace-style/shoelace/dist/react/carousel';
|
||||
import WaCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';
|
||||
@@ -545,8 +542,6 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Adding and Removing Slides
|
||||
|
||||
The content of the carousel can be changed by adding or removing carousel items. The carousel will update itself automatically.
|
||||
@@ -619,8 +614,6 @@ The content of the carousel can be changed by adding or removing carousel items.
|
||||
</script>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import WaCarousel from '@shoelace-style/shoelace/dist/react/carousel';
|
||||
@@ -680,8 +673,6 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Vertical Scrolling
|
||||
|
||||
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.
|
||||
@@ -803,7 +794,7 @@ const App = () => (
|
||||
|
||||
### Aspect Ratio
|
||||
|
||||
Use the `--aspect-ratio` custom property to customize the size of the carousel's viewport.
|
||||
Use the `--aspect-ratio` custom property to customize the size of the carousel's viewport from the default value of 16/9.
|
||||
|
||||
```html:preview
|
||||
<wa-carousel class="aspect-ratio" navigation pagination style="--aspect-ratio: 3/2;">
|
||||
@@ -859,8 +850,6 @@ Use the `--aspect-ratio` custom property to customize the size of the carousel's
|
||||
</script>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import WaCarousel from '@shoelace-style/shoelace/dist/react/carousel';
|
||||
@@ -926,8 +915,6 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Scroll Hint
|
||||
|
||||
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.
|
||||
@@ -967,8 +954,6 @@ Use the `--scroll-hint` custom property to add inline padding in horizontal caro
|
||||
</wa-carousel>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import WaCarousel from '@shoelace-style/shoelace/dist/react/carousel';
|
||||
@@ -1014,8 +999,6 @@ 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.
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Checkbox
|
||||
description: Checkboxes allow the user to toggle an option on or off.
|
||||
layout: component
|
||||
title: Checkbox
|
||||
description: Checkboxes allow the user to toggle an option on or off.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
```html:preview
|
||||
@@ -89,6 +88,18 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### Help Text
|
||||
|
||||
Add descriptive help text to a switch with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.
|
||||
|
||||
```html:preview
|
||||
<wa-checkbox help-text="What should the user know about the checkbox?">Label</wa-checkbox>
|
||||
```
|
||||
|
||||
````jsx:react
|
||||
import WaCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
|
||||
const App = () => <WaCheckbox help-text="What should the user know about the switch?">Label</WaCheckbox>;
|
||||
|
||||
### Custom Validity
|
||||
|
||||
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.
|
||||
@@ -123,9 +134,7 @@ Use the `setCustomValidity()` method to set a custom validation message. This wi
|
||||
});
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
````
|
||||
|
||||
```jsx:react
|
||||
import { useEffect, useRef } from 'react';
|
||||
@@ -162,5 +171,3 @@ const App = () => {
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Color Picker
|
||||
description: Color pickers allow the user to select a color.
|
||||
layout: component
|
||||
title: Color Picker
|
||||
description: Color pickers allow the user to select a color.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
```html:preview
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Copy Button
|
||||
description: Copies data to the clipboard when the user clicks the button.
|
||||
layout: component
|
||||
title: Copy Button
|
||||
description: Copies data to the clipboard when the user clicks the button.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
```html:preview
|
||||
@@ -51,9 +50,9 @@ Use the `copy-icon`, `success-icon`, and `error-icon` slots to customize the ico
|
||||
|
||||
```html:preview
|
||||
<wa-copy-button value="Copied from a custom button">
|
||||
<wa-icon slot="copy-icon" name="clipboard"></wa-icon>
|
||||
<wa-icon slot="success-icon" name="clipboard-check"></wa-icon>
|
||||
<wa-icon slot="error-icon" name="clipboard-x"></wa-icon>
|
||||
<wa-icon slot="copy-icon" name="clipboard" variant="regular"></wa-icon>
|
||||
<wa-icon slot="success-icon" name="thumbs-up" variant="solid"></wa-icon>
|
||||
<wa-icon slot="error-icon" name="xmark" variant="solid"></wa-icon>
|
||||
</wa-copy-button>
|
||||
```
|
||||
|
||||
@@ -64,9 +63,9 @@ import { WaIcon } from '@shoelace-style/shoelace/dist/react/icon';
|
||||
const App = () => (
|
||||
<>
|
||||
<WaCopyButton value="Copied from a custom button">
|
||||
<WaIcon slot="copy-icon" name="clipboard" />
|
||||
<WaIcon slot="success-icon" name="clipboard-check" />
|
||||
<WaIcon slot="error-icon" name="clipboard-x" />
|
||||
<WaIcon slot="copy-icon" name="clipboard" variant="regular" />
|
||||
<WaIcon slot="success-icon" name="check" variant="solid" />
|
||||
<WaIcon slot="error-icon" name="xmark" variant="solid" />
|
||||
</WaCopyButton>
|
||||
</>
|
||||
);
|
||||
@@ -179,9 +178,9 @@ You can customize the button to your liking with CSS.
|
||||
|
||||
```html:preview
|
||||
<wa-copy-button value="I'm so stylish" class="custom-styles">
|
||||
<wa-icon slot="copy-icon" name="asterisk"></wa-icon>
|
||||
<wa-icon slot="success-icon" name="check-lg"></wa-icon>
|
||||
<wa-icon slot="error-icon" name="x-lg"></wa-icon>
|
||||
<wa-icon slot="copy-icon" name="clipboard"></wa-icon>
|
||||
<wa-icon slot="success-icon" name="thumbs-up"></wa-icon>
|
||||
<wa-icon slot="error-icon" name="thumbs-down"></wa-icon>
|
||||
</wa-copy-button>
|
||||
|
||||
<style>
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Details
|
||||
description: Details show a brief summary and expand to show additional content.
|
||||
layout: component
|
||||
title: Details
|
||||
description: Details show a brief summary and expand to show additional content.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
<!-- cspell:dictionaries lorem-ipsum -->
|
||||
@@ -55,8 +54,8 @@ Use the `expand-icon` and `collapse-icon` slots to change the expand and collaps
|
||||
|
||||
```html:preview
|
||||
<wa-details summary="Toggle Me" class="custom-icons">
|
||||
<wa-icon name="plus-square" slot="expand-icon"></wa-icon>
|
||||
<wa-icon name="dash-square" slot="collapse-icon"></wa-icon>
|
||||
<wa-icon name="square-plus" slot="expand-icon" variant="regular"></wa-icon>
|
||||
<wa-icon name="square-minus" slot="collapse-icon" variant="regular"></wa-icon>
|
||||
|
||||
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.
|
||||
@@ -84,8 +83,8 @@ const css = `
|
||||
const App = () => (
|
||||
<>
|
||||
<WaDetails summary="Toggle Me" class="custom-icon">
|
||||
<WaIcon name="plus-square" slot="expand-icon" />
|
||||
<WaIcon name="dash-square" slot="collapse-icon" />
|
||||
<WaIcon name="square-plus" slot="expand-icon" />
|
||||
<WaIcon name="square-minus" slot="collapse-icon" />
|
||||
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.
|
||||
@@ -123,7 +122,9 @@ Details are designed to function independently, but you can simulate a group or
|
||||
|
||||
// Close all other details when one is shown
|
||||
container.addEventListener('wa-show', event => {
|
||||
[...container.querySelectorAll('wa-details')].map(details => (details.open = event.target === details));
|
||||
if (event.target.localName === 'wa-details') {
|
||||
[...container.querySelectorAll('wa-details')].map(details => (details.open = event.target === details));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Dialog
|
||||
description: 'Dialogs, sometimes called "modals", appear above the page and require the user''s immediate attention.'
|
||||
layout: component
|
||||
title: Dialog
|
||||
description: 'Dialogs, sometimes called "modals", appear above the page and require the user''s immediate attention.'
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
<!-- cspell:dictionaries lorem-ipsum -->
|
||||
@@ -72,8 +71,6 @@ Use the `--width` custom property to set the dialog's width.
|
||||
</script>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
@@ -97,15 +94,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
|
||||
<wa-dialog label="Dialog" class="dialog-scrolling">
|
||||
<div style="height: 150vh; border: dashed 2px var(--wa-color-surface-outline); padding: 0 1rem;">
|
||||
<div style="height: 150vh; border: dashed 2px var(--wa-color-surface-border); padding: 0 1rem;">
|
||||
<p>Scroll down and give it a try! 👇</p>
|
||||
</div>
|
||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
||||
@@ -123,8 +118,6 @@ By design, a dialog's height will never exceed that of the viewport. As such, di
|
||||
</script>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
@@ -139,7 +132,7 @@ const App = () => {
|
||||
<div
|
||||
style={{
|
||||
height: '150vh',
|
||||
border: 'dashed 2px var(--wa-color-surface-outline)',
|
||||
border: 'dashed 2px var(--wa-color-surface-border)',
|
||||
padding: '0 1rem'
|
||||
}}
|
||||
>
|
||||
@@ -157,15 +150,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
|
||||
<wa-dialog label="Dialog" class="dialog-header-actions">
|
||||
<wa-icon-button class="new-window" slot="header-actions" name="box-arrow-up-right"></wa-icon-button>
|
||||
<wa-icon-button class="new-window" slot="header-actions" name="arrow-up-right-from-square" variant="solid"></wa-icon-button>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
||||
</wa-dialog>
|
||||
@@ -199,7 +190,7 @@ const App = () => {
|
||||
<WaIconButton
|
||||
class="new-window"
|
||||
slot="header-actions"
|
||||
name="box-arrow-up-right"
|
||||
name="arrow-up-right-from-square"
|
||||
onClick={() => window.open(location.href)}
|
||||
/>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Divider
|
||||
description: Dividers are used to visually separate or group elements.
|
||||
layout: component
|
||||
title: Divider
|
||||
description: Dividers are used to visually separate or group elements.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
```html:preview
|
||||
@@ -25,16 +24,12 @@ Use the `--width` custom property to change the width of the divider.
|
||||
<wa-divider style="--width: 4px;"></wa-divider>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||
|
||||
const App = () => <WaDivider style={{ '--width': '4px' }} />;
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Color
|
||||
|
||||
Use the `--color` custom property to change the color of the divider.
|
||||
@@ -43,16 +38,12 @@ Use the `--color` custom property to change the color of the divider.
|
||||
<wa-divider style="--color: tomato;"></wa-divider>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||
|
||||
const App = () => <WaDivider style={{ '--color': 'tomato' }} />;
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Spacing
|
||||
|
||||
Use the `--spacing` custom property to change the amount of space between the divider and it's neighboring elements.
|
||||
@@ -65,22 +56,6 @@ Use the `--spacing` custom property to change the amount of space between the di
|
||||
</div>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
Above
|
||||
<WaDivider style={{ '--spacing': '2rem' }} />
|
||||
Below
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
{% 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.
|
||||
@@ -95,8 +70,6 @@ Add the `vertical` attribute to draw the divider in a vertical orientation. The
|
||||
</div>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||
|
||||
@@ -117,8 +90,6 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Menu Dividers
|
||||
|
||||
Use dividers in [menus](/components/menu) to visually group menu items.
|
||||
@@ -135,8 +106,6 @@ Use dividers in [menus](/components/menu) to visually group menu items.
|
||||
</wa-menu>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||
import WaMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||
@@ -154,5 +123,3 @@ const App = () => (
|
||||
</WaMenu>
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Drawer
|
||||
description: Drawers slide in from a container to expose additional options and information.
|
||||
layout: component
|
||||
title: Drawer
|
||||
description: Drawers slide in from a container to expose additional options and information.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
<!-- cspell:dictionaries lorem-ipsum -->
|
||||
@@ -193,7 +192,7 @@ Unlike normal drawers, contained drawers are not modal. This means they do not s
|
||||
|
||||
```html:preview
|
||||
<div
|
||||
style="position: relative; border: solid 2px var(--wa-color-surface-outline); height: 300px; padding: 1rem; margin-bottom: 1rem;"
|
||||
style="position: relative; border: solid 2px var(--wa-color-surface-border); height: 300px; padding: 1rem; margin-bottom: 1rem;"
|
||||
>
|
||||
The drawer will be contained to this box. This content won't shift or be affected in any way when the drawer opens.
|
||||
|
||||
@@ -215,8 +214,6 @@ Unlike normal drawers, contained drawers are not modal. This means they do not s
|
||||
</script>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
@@ -230,7 +227,7 @@ const App = () => {
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
border: 'solid 2px var(--wa-color-surface-outline)',
|
||||
border: 'solid 2px var(--wa-color-surface-border)',
|
||||
height: '300px',
|
||||
padding: '1rem',
|
||||
marginBottom: '1rem'
|
||||
@@ -259,8 +256,6 @@ 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`.
|
||||
@@ -283,8 +278,6 @@ Use the `--size` custom property to set the drawer's size. This will be applied
|
||||
</script>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
@@ -308,15 +301,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
|
||||
<wa-drawer label="Drawer" class="drawer-scrolling">
|
||||
<div style="height: 150vh; border: dashed 2px var(--wa-color-surface-outline); padding: 0 1rem;">
|
||||
<div style="height: 150vh; border: dashed 2px var(--wa-color-surface-border); padding: 0 1rem;">
|
||||
<p>Scroll down and give it a try! 👇</p>
|
||||
</div>
|
||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
||||
@@ -334,8 +325,6 @@ By design, a drawer's height will never exceed 100% of its container. As such, d
|
||||
</script>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
@@ -350,7 +339,7 @@ const App = () => {
|
||||
<div
|
||||
style={{
|
||||
height: '150vh',
|
||||
border: 'dashed 2px var(--wa-color-surface-outline)',
|
||||
border: 'dashed 2px var(--wa-color-surface-border)',
|
||||
padding: '0 1rem'
|
||||
}}
|
||||
>
|
||||
@@ -367,15 +356,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
|
||||
<wa-drawer label="Drawer" class="drawer-header-actions">
|
||||
<wa-icon-button class="new-window" slot="header-actions" name="box-arrow-up-right"></wa-icon-button>
|
||||
<wa-icon-button class="new-window" slot="header-actions" name="arrow-up-right-from-square" variant="solid"></wa-icon-button>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
||||
</wa-drawer>
|
||||
@@ -406,7 +393,7 @@ const App = () => {
|
||||
return (
|
||||
<>
|
||||
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)}>
|
||||
<WaIconButton slot="header-actions" name="box-arrow-up-right" onClick={() => window.open(location.href)} />
|
||||
<WaIconButton slot="header-actions" name="arrow-up-right-from-square" onClick={() => window.open(location.href)} />
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
|
||||
Close
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Dropdown
|
||||
description: 'Dropdowns expose additional content that "drops down" in a panel.'
|
||||
layout: component
|
||||
title: Dropdown
|
||||
description: 'Dropdowns expose additional content that "drops down" in a panel.'
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
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.
|
||||
@@ -22,11 +21,11 @@ Dropdowns are designed to work well with [menus](/components/menu) to provide a
|
||||
<wa-divider></wa-divider>
|
||||
<wa-menu-item>
|
||||
Prefix
|
||||
<wa-icon slot="prefix" name="gift"></wa-icon>
|
||||
<wa-icon slot="prefix" name="gift" variant="solid"></wa-icon>
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
Suffix Icon
|
||||
<wa-icon slot="suffix" name="heart"></wa-icon>
|
||||
<wa-icon slot="suffix" name="heart" variant="solid"></wa-icon>
|
||||
</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
@@ -355,7 +354,7 @@ import WaMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||
|
||||
const css = `
|
||||
.dropdown-hoist {
|
||||
border: solid 2px var(--wa-color-surface-outline);
|
||||
border: solid 2px var(--wa-color-surface-border);
|
||||
padding: var(--wa-space-m);
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -396,7 +395,7 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
:::warning
|
||||
:::caution
|
||||
As a UX best practice, avoid using more than one level of submenu when possible.
|
||||
:::
|
||||
|
||||
@@ -428,7 +427,7 @@ Dropdown panels will be clipped if they're inside a container that has `overflow
|
||||
<style>
|
||||
.dropdown-hoist {
|
||||
position: relative;
|
||||
border: solid 2px var(--wa-color-surface-outline);
|
||||
border: solid 2px var(--wa-color-surface-border);
|
||||
padding: var(--wa-space-m);
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -444,7 +443,7 @@ import WaMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||
|
||||
const css = `
|
||||
.dropdown-hoist {
|
||||
border: solid 2px var(--wa-color-surface-outline);
|
||||
border: solid 2px var(--wa-color-surface-border);
|
||||
padding: var(--wa-space-m);
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Format Bytes
|
||||
description: Formats a number as a human readable bytes value.
|
||||
layout: component
|
||||
title: Format Bytes
|
||||
description: Formats a number as a human readable bytes value.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
```html:preview
|
||||
@@ -20,8 +19,6 @@ layout: component
|
||||
</script>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
@@ -48,8 +45,6 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
## Examples
|
||||
|
||||
### Formatting Bytes
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Format Date
|
||||
description: Formats a date/time using the specified locale and options.
|
||||
layout: component
|
||||
title: Format Date
|
||||
description: Formats a date/time using the specified locale and options.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
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.
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Format Number
|
||||
description: Formats a number using the specified locale and options.
|
||||
layout: component
|
||||
title: Format Number
|
||||
description: Formats a number using the specified locale and options.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
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.
|
||||
@@ -23,8 +22,6 @@ Localization is handled by the browser's [`Intl.NumberFormat` API](https://devel
|
||||
</script>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import WaFormatNumber from '@shoelace-style/shoelace/dist/react/format-number';
|
||||
@@ -50,8 +47,6 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
## Examples
|
||||
|
||||
### Percentages
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Icon Button
|
||||
description: Icons buttons are simple, icon-only buttons that can be used for actions and in toolbars.
|
||||
layout: component
|
||||
title: Icon Button
|
||||
description: Icons buttons are simple, icon-only buttons that can be used for actions and in toolbars.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
For a full list of icons that come bundled with Web Awesome, refer to the [icon component](/components/icon).
|
||||
@@ -24,36 +23,32 @@ const App = () => <WaIconButton name="gear" label="Settings" />;
|
||||
Icon buttons inherit their parent element's `font-size`.
|
||||
|
||||
```html:preview
|
||||
<wa-icon-button name="pencil" label="Edit" style="font-size: 1.5rem;"></wa-icon-button>
|
||||
<wa-icon-button name="pencil" label="Edit" style="font-size: 2rem;"></wa-icon-button>
|
||||
<wa-icon-button name="pencil" label="Edit" style="font-size: 2.5rem;"></wa-icon-button>
|
||||
<wa-icon-button name="pen-to-square" variant="solid" label="Edit" style="font-size: 1.5rem;"></wa-icon-button>
|
||||
<wa-icon-button name="pen-to-square" variant="solid" label="Edit" style="font-size: 2rem;"></wa-icon-button>
|
||||
<wa-icon-button name="pen-to-square" variant="solid" label="Edit" style="font-size: 2.5rem;"></wa-icon-button>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<WaIconButton name="pencil" label="Edit" style={{ fontSize: '1.5rem' }} />
|
||||
<WaIconButton name="pencil" label="Edit" style={{ fontSize: '2rem' }} />
|
||||
<WaIconButton name="pencil" label="Edit" style={{ fontSize: '2.5rem' }} />
|
||||
<WaIconButton name="pen-to-square" variant="solid" label="Edit" style={{ fontSize: '1.5rem' }} />
|
||||
<WaIconButton name="pen-to-square" variant="solid" label="Edit" style={{ fontSize: '2rem' }} />
|
||||
<WaIconButton name="pen-to-square" variant="solid" label="Edit" style={{ fontSize: '2.5rem' }} />
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
{% 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
|
||||
<div class="icon-button-color">
|
||||
<wa-icon-button name="type-bold" label="Bold"></wa-icon-button>
|
||||
<wa-icon-button name="type-italic" label="Italic"></wa-icon-button>
|
||||
<wa-icon-button name="type-underline" label="Underline"></wa-icon-button>
|
||||
<wa-icon-button name="bold" variant="solid" label="Bold"></wa-icon-button>
|
||||
<wa-icon-button name="italic" variant="solid" label="Italic"></wa-icon-button>
|
||||
<wa-icon-button name="underline" variant="solid" label="Underline"></wa-icon-button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@@ -93,9 +88,9 @@ const css = `
|
||||
const App = () => (
|
||||
<>
|
||||
<div className="icon-button-color">
|
||||
<WaIconButton name="type-bold" label="Bold" />
|
||||
<WaIconButton name="type-italic" label="Italic" />
|
||||
<WaIconButton name="type-underline" label="Underline" />
|
||||
<WaIconButton name="type-bold" variant="solid" label="Bold" />
|
||||
<WaIconButton name="type-italic" variant="solid" label="Italic" />
|
||||
<WaIconButton name="type-underline" variant="solid" label="Underline" />
|
||||
</div>
|
||||
|
||||
<style>{css}</style>
|
||||
@@ -108,13 +103,13 @@ const App = () => (
|
||||
Use the `href` attribute to convert the button to a link.
|
||||
|
||||
```html:preview
|
||||
<wa-icon-button name="gear" label="Settings" href="https://example.com" target="_blank"></wa-icon-button>
|
||||
<wa-icon-button name="gear" variant="solid" label="Settings" href="https://example.com" target="_blank"></wa-icon-button>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import WaIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
|
||||
|
||||
const App = () => <WaIconButton name="gear" label="Settings" href="https://example.com" target="_blank" />;
|
||||
const App = () => <WaIconButton name="gear" variant="solid" label="Settings" href="https://example.com" target="_blank" />;
|
||||
```
|
||||
|
||||
### Icon Button with Tooltip
|
||||
@@ -123,7 +118,7 @@ Wrap a tooltip around an icon button to provide contextual information to the us
|
||||
|
||||
```html:preview
|
||||
<wa-tooltip content="Settings">
|
||||
<wa-icon-button name="gear" label="Settings"></wa-icon-button>
|
||||
<wa-icon-button name="gear" variant="solid" label="Settings"></wa-icon-button>
|
||||
</wa-tooltip>
|
||||
```
|
||||
|
||||
@@ -133,7 +128,7 @@ import WaTooltip from '@shoelace-style/shoelace/dist/react/tooltip';
|
||||
|
||||
const App = () => (
|
||||
<WaTooltip content="Settings">
|
||||
<WaIconButton name="gear" label="Settings" />
|
||||
<WaIconButton name="gear" variant="solid" label="Settings" />
|
||||
</WaTooltip>
|
||||
);
|
||||
```
|
||||
@@ -143,11 +138,11 @@ const App = () => (
|
||||
Use the `disabled` attribute to disable the icon button.
|
||||
|
||||
```html:preview
|
||||
<wa-icon-button name="gear" label="Settings" disabled></wa-icon-button>
|
||||
<wa-icon-button name="gear" variant="solid" label="Settings" disabled></wa-icon-button>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import WaIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
|
||||
|
||||
const App = () => <WaIconButton name="gear" label="Settings" disabled />;
|
||||
const App = () => <WaIconButton name="gear" variant="solid" label="Settings" disabled />;
|
||||
```
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Icon
|
||||
description: Icons are symbols that can be used to represent various options within an application.
|
||||
layout: component
|
||||
title: Icon
|
||||
description: Icons are symbols that can be used to represent various options within an application.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
Web Awesome 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.
|
||||
@@ -19,23 +18,11 @@ All available icons in the `default` icon library are shown below. Click or tap
|
||||
<wa-icon name="icon-name-here"></wa-icon>
|
||||
```
|
||||
|
||||
<div class="icon-search">
|
||||
<div class="icon-search-controls">
|
||||
<wa-input placeholder="Search Icons" clearable>
|
||||
<wa-icon slot="prefix" name="search"></wa-icon>
|
||||
</wa-input>
|
||||
<wa-select value="outline">
|
||||
<wa-option value="outline">Outlined</wa-option>
|
||||
<wa-option value="fill">Filled</wa-option>
|
||||
<wa-option value="all">All icons</wa-option>
|
||||
</wa-select>
|
||||
</div>
|
||||
<div class="icon-list"></div>
|
||||
<input type="text" class="icon-copy-input" aria-hidden="true" tabindex="-1">
|
||||
</div>
|
||||
|
||||
## Examples
|
||||
|
||||
TODO - show how to use `family` and `variant` attributes.
|
||||
TODO - show how to use FA pro via `data-webawesome-kit="..."`
|
||||
|
||||
### Colors
|
||||
|
||||
Icons inherit their color from the current text color. Thus, you can set the `color` property on the `<wa-icon>` element or an ancestor to change the color.
|
||||
@@ -44,31 +31,29 @@ Icons inherit their color from the current text color. Thus, you can set the `co
|
||||
<div style="color: #4a90e2;">
|
||||
<wa-icon name="exclamation-triangle"></wa-icon>
|
||||
<wa-icon name="archive"></wa-icon>
|
||||
<wa-icon name="battery-charging"></wa-icon>
|
||||
<wa-icon name="battery-three-quarters"></wa-icon>
|
||||
<wa-icon name="bell"></wa-icon>
|
||||
</div>
|
||||
<div style="color: #9013fe;">
|
||||
<wa-icon name="clock"></wa-icon>
|
||||
<wa-icon name="cloud"></wa-icon>
|
||||
<wa-icon name="download"></wa-icon>
|
||||
<wa-icon name="file-earmark"></wa-icon>
|
||||
<wa-icon name="file"></wa-icon>
|
||||
</div>
|
||||
<div style="color: #417505;">
|
||||
<wa-icon name="flag"></wa-icon>
|
||||
<wa-icon name="heart"></wa-icon>
|
||||
<wa-icon name="image"></wa-icon>
|
||||
<wa-icon name="lightning"></wa-icon>
|
||||
<wa-icon name="bolt-lightning"></wa-icon>
|
||||
</div>
|
||||
<div style="color: #f5a623;">
|
||||
<wa-icon name="mic"></wa-icon>
|
||||
<wa-icon name="microphone"></wa-icon>
|
||||
<wa-icon name="search"></wa-icon>
|
||||
<wa-icon name="star"></wa-icon>
|
||||
<wa-icon name="trash"></wa-icon>
|
||||
</div>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
@@ -77,23 +62,23 @@ const App = () => (
|
||||
<div style={{ color: '#4a90e2' }}>
|
||||
<WaIcon name="exclamation-triangle"></WaIcon>
|
||||
<WaIcon name="archive"></WaIcon>
|
||||
<WaIcon name="battery-charging"></WaIcon>
|
||||
<WaIcon name="battery-three-quarters"></WaIcon>
|
||||
<WaIcon name="bell"></WaIcon>
|
||||
</div>
|
||||
<div style={{ color: '#9013fe' }}>
|
||||
<WaIcon name="clock"></WaIcon>
|
||||
<WaIcon name="cloud"></WaIcon>
|
||||
<WaIcon name="download"></WaIcon>
|
||||
<WaIcon name="file-earmark"></WaIcon>
|
||||
<WaIcon name="file"></WaIcon>
|
||||
</div>
|
||||
<div style={{ color: '#417505' }}>
|
||||
<WaIcon name="flag"></WaIcon>
|
||||
<WaIcon name="heart"></WaIcon>
|
||||
<WaIcon name="image"></WaIcon>
|
||||
<WaIcon name="lightning"></WaIcon>
|
||||
<WaIcon name="bolt-lightning"></WaIcon>
|
||||
</div>
|
||||
<div style={{ color: '#f5a623' }}>
|
||||
<WaIcon name="mic"></WaIcon>
|
||||
<WaIcon name="microphone"></WaIcon>
|
||||
<WaIcon name="search"></WaIcon>
|
||||
<WaIcon name="star"></WaIcon>
|
||||
<WaIcon name="trash"></WaIcon>
|
||||
@@ -102,8 +87,6 @@ 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.
|
||||
@@ -112,25 +95,23 @@ Icons are sized relative to the current font size. To change their size, set the
|
||||
<div style="font-size: 32px;">
|
||||
<wa-icon name="exclamation-triangle"></wa-icon>
|
||||
<wa-icon name="archive"></wa-icon>
|
||||
<wa-icon name="battery-charging"></wa-icon>
|
||||
<wa-icon name="battery-three-quarters"></wa-icon>
|
||||
<wa-icon name="bell"></wa-icon>
|
||||
<wa-icon name="clock"></wa-icon>
|
||||
<wa-icon name="cloud"></wa-icon>
|
||||
<wa-icon name="download"></wa-icon>
|
||||
<wa-icon name="file-earmark"></wa-icon>
|
||||
<wa-icon name="file"></wa-icon>
|
||||
<wa-icon name="flag"></wa-icon>
|
||||
<wa-icon name="heart"></wa-icon>
|
||||
<wa-icon name="image"></wa-icon>
|
||||
<wa-icon name="lightning"></wa-icon>
|
||||
<wa-icon name="mic"></wa-icon>
|
||||
<wa-icon name="bolt-lightning"></wa-icon>
|
||||
<wa-icon name="microphone"></wa-icon>
|
||||
<wa-icon name="search"></wa-icon>
|
||||
<wa-icon name="star"></wa-icon>
|
||||
<wa-icon name="trash"></wa-icon>
|
||||
</div>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
@@ -138,17 +119,17 @@ const App = () => (
|
||||
<div style={{ fontSize: '32px' }}>
|
||||
<WaIcon name="exclamation-triangle" />
|
||||
<WaIcon name="archive" />
|
||||
<WaIcon name="battery-charging" />
|
||||
<WaIcon name="battery-three-quarters" />
|
||||
<WaIcon name="bell" />
|
||||
<WaIcon name="clock" />
|
||||
<WaIcon name="cloud" />
|
||||
<WaIcon name="download" />
|
||||
<WaIcon name="file-earmark" />
|
||||
<WaIcon name="file" />
|
||||
<WaIcon name="flag" />
|
||||
<WaIcon name="heart" />
|
||||
<WaIcon name="image" />
|
||||
<WaIcon name="lightning" />
|
||||
<WaIcon name="mic" />
|
||||
<WaIcon name="bolt-lightning" />
|
||||
<WaIcon name="microphone" />
|
||||
<WaIcon name="search" />
|
||||
<WaIcon name="star" />
|
||||
<WaIcon name="trash" />
|
||||
@@ -156,20 +137,18 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Labels
|
||||
|
||||
For non-decorative icons, use the `label` attribute to announce it to assistive devices.
|
||||
|
||||
```html:preview
|
||||
<wa-icon name="star-fill" label="Add to favorites"></wa-icon>
|
||||
<wa-icon name="star" label="Add to favorites"></wa-icon>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
const App = () => <WaIcon name="star-fill" label="Add to favorites" />;
|
||||
const App = () => <WaIcon name="star" label="Add to favorites" />;
|
||||
```
|
||||
|
||||
### Custom Icons
|
||||
@@ -180,16 +159,12 @@ Custom icons can be loaded individually with the `src` attribute. Only SVGs on a
|
||||
<wa-icon src="https://shoelace.style/assets/images/shoe.svg" style="font-size: 8rem;"></wa-icon>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
const App = () => <WaIcon src="https://shoelace.style/assets/images/shoe.svg" style={{ fontSize: '8rem' }}></WaIcon>;
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
## Icon Libraries
|
||||
|
||||
You can register additional icons to use with the `<wa-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.
|
||||
@@ -642,9 +617,7 @@ As always, make sure to benchmark these changes. When using HTTP/2, it may in fa
|
||||
|
||||
:::danger
|
||||
When using sprite sheets, the `wa-load` and `wa-error` events will not fire.
|
||||
:::
|
||||
|
||||
:::danger
|
||||
For security reasons, browsers may apply the same-origin policy on `<use>` elements located in the `<wa-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.
|
||||
:::
|
||||
|
||||
@@ -680,190 +653,3 @@ If you want to change the icons Web Awesome uses internally, you can register an
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
<!-- Supporting scripts and styles for the search utility -->
|
||||
<script>
|
||||
function wrapWithTooltip(item) {
|
||||
const tooltip = document.createElement('wa-tooltip');
|
||||
tooltip.content = item.getAttribute('data-name');
|
||||
|
||||
// Close open tooltips
|
||||
document.querySelectorAll('.icon-list wa-tooltip[open]').forEach(tooltip => tooltip.hide());
|
||||
|
||||
// Wrap it with a tooltip and trick it into showing up
|
||||
item.parentNode.insertBefore(tooltip, item);
|
||||
tooltip.appendChild(item);
|
||||
requestAnimationFrame(() => tooltip.dispatchEvent(new MouseEvent('mouseover')));
|
||||
}
|
||||
|
||||
fetch('/dist/assets/icons/icons.json')
|
||||
.then(res => res.json())
|
||||
.then(icons => {
|
||||
const container = document.querySelector('.icon-search');
|
||||
const input = container.querySelector('wa-input');
|
||||
const select = container.querySelector('wa-select');
|
||||
const copyInput = container.querySelector('.icon-copy-input');
|
||||
const loader = container.querySelector('.icon-loader');
|
||||
const list = container.querySelector('.icon-list');
|
||||
const queue = [];
|
||||
let inputTimeout;
|
||||
|
||||
// Generate icons
|
||||
icons.map(i => {
|
||||
const item = document.createElement('div');
|
||||
item.classList.add('icon-list-item');
|
||||
item.setAttribute('data-name', i.name);
|
||||
item.setAttribute('data-terms', [i.name, i.title, ...(i.tags || []), ...(i.categories || [])].join(' '));
|
||||
item.innerHTML = `
|
||||
<svg width="1em" height="1em" fill="currentColor">
|
||||
<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
|
||||
// to improve this page's performance. See: https://github.com/shoelace-style/shoelace/issues/1122
|
||||
item.addEventListener('mouseover', () => wrapWithTooltip(item), { once: true });
|
||||
|
||||
// Copy on click
|
||||
item.addEventListener('click', () => {
|
||||
const tooltip = item.closest('wa-tooltip');
|
||||
copyInput.value = i.name;
|
||||
copyInput.select();
|
||||
document.execCommand('copy');
|
||||
|
||||
if (tooltip) {
|
||||
tooltip.content = 'Copied!';
|
||||
setTimeout(() => tooltip.content = i.name, 1000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Filter as the user types
|
||||
input.addEventListener('wa-input', () => {
|
||||
clearTimeout(inputTimeout);
|
||||
inputTimeout = setTimeout(() => {
|
||||
[...list.querySelectorAll('.icon-list-item')].map(item => {
|
||||
const filter = input.value.toLowerCase();
|
||||
if (filter === '') {
|
||||
item.hidden = false;
|
||||
} else {
|
||||
const terms = item.getAttribute('data-terms').toLowerCase();
|
||||
item.hidden = terms.indexOf(filter) < 0;
|
||||
}
|
||||
});
|
||||
}, 250);
|
||||
});
|
||||
|
||||
// Sort by type and remember preference
|
||||
const iconType = sessionStorage.getItem('wa-icon:type') || 'outline';
|
||||
select.value = iconType;
|
||||
list.setAttribute('data-type', select.value);
|
||||
select.addEventListener('wa-change', () => {
|
||||
list.setAttribute('data-type', select.value);
|
||||
sessionStorage.setItem('wa-icon:type', select.value);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.icon-search {
|
||||
border: solid 1px var(--wa-color-surface-outline);
|
||||
border-radius: var(--wa-corners-1x);
|
||||
padding: var(--wa-space-m);
|
||||
}
|
||||
|
||||
.icon-search [hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.icon-search-controls {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.icon-search-controls wa-input {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.icon-search-controls wa-select {
|
||||
width: 10rem;
|
||||
flex: 0 0 auto;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.icon-loader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 30vh;
|
||||
}
|
||||
|
||||
.icon-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
position: relative;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.icon-loader[hidden],
|
||||
.icon-list[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.icon-list-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--wa-corners-1x);
|
||||
font-size: 24px;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
margin: 0 auto;
|
||||
cursor: copy;
|
||||
transition: var(--wa-transition-fast) all;
|
||||
}
|
||||
|
||||
.icon-list-item:hover {
|
||||
background-color: var(--wa-color-brand-fill-muted);
|
||||
color: var(--wa-color-brand-text-on-muted);
|
||||
}
|
||||
|
||||
.icon-list[data-type="outline"] .icon-list-item[data-name$="-fill"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.icon-list[data-type="fill"] .icon-list-item:not([data-name$="-fill"]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.icon-copy-input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.icon-list {
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
}
|
||||
|
||||
.icon-list-item {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.icon-search-controls {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.icon-search-controls wa-select {
|
||||
width: auto;
|
||||
margin: 1rem 0 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
.icon-list {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
meta:
|
||||
title: Image Comparer
|
||||
description: Compare visual differences between similar photos with a sliding panel.
|
||||
layout: component
|
||||
title: Image Comparer
|
||||
description: Compare visual differences between similar photos with a sliding panel.
|
||||
layout: ../../../layouts/ComponentLayout.astro
|
||||
---
|
||||
|
||||
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.)
|
||||