-
+
- {{ meta.title }} +
diff --git a/.gitignore b/.gitignore
index b6f26d43d..3d9a6f9fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
.DS_Store
.cache
+_site
docs/dist
docs/search.json
dist
diff --git a/README.md b/README.md
index 621663108..4c16605ae 100644
--- a/README.md
+++ b/README.md
@@ -51,9 +51,7 @@ Once you've cloned the repo, run the following command.
npm start
```
-This will spin up the Shoelace dev server. After the initial build, a browser will open automatically. There is currently no hot module reloading (HMR), as browser's don't provide a way to reregister custom elements, but most changes to the source will reload the browser automatically.
-
-The documentation is powered by Docsify, which uses raw markdown files to generate pages. As such, no static files are built for the docs.
+This will spin up the dev server. After the initial build, a browser will open automatically. There is currently no hot module reloading (HMR), as browser's don't provide a way to reregister custom elements, but most changes to the source will reload the browser automatically.
### Building
diff --git a/cspell.json b/cspell.json
index e511c3dd4..aa386632c 100644
--- a/cspell.json
+++ b/cspell.json
@@ -81,6 +81,7 @@
"labelledby",
"Laravel",
"LaViska",
+ "linkify",
"listbox",
"listitem",
"litelement",
@@ -105,6 +106,7 @@
"ParamagicDev",
"peta",
"petabit",
+ "prismjs",
"progressbar",
"radiogroup",
"Railsbyte",
@@ -127,6 +129,7 @@
"Segoe",
"semibold",
"slotchange",
+ "smartquotes",
"spacebar",
"stylesheet",
"Tabbable",
diff --git a/docs/_includes/component.njk b/docs/_includes/component.njk
new file mode 100644
index 000000000..8d65f7f85
--- /dev/null
+++ b/docs/_includes/component.njk
@@ -0,0 +1,347 @@
+{% extends "default.njk" %}
+
+{# Find the component based on the `tag` front matter #}
+{% set component = getComponent('sl-' + page.fileSlug) %}
+
+{% block content %}
+ {# Determine the badge variant #}
+ {% if component.status == 'stable' %}
+ {% set badgeVariant = 'primary' %}
+ {% elseif component.status == 'experimental' %}
+ {% set badgeVariant = 'warning' %}
+ {% elseif component.status == 'planned' %}
+ {% set badgeVariant = 'neutral' %}
+ {% elseif component.status == 'deprecated' %}
+ {% set badgeVariant = 'danger' %}
+ {% else %}
+ {% set badgeVariant = 'neutral' %}
+ {% endif %}
+
+ {# Header #}
+ {{ component.name | classNameToComponentName }}
+
+ <{{ component.tagName }}> | {{ component.name }}
+
+ {% if component.summary %} + {{ component.summary | markdownInline | safe }} + {% endif %} +
+ + {# Markdown content #} + {{ content | safe }} + + {# Importing #} ++ 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 cherry pick this component. +
+ ++ To import this component from the CDN + using a script tag: +
+<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@{{ meta.version }}/dist/{{ component.path }}"></script>
+ + To import this component from the CDN + using a JavaScript import: +
+import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@{{ meta.version }}/dist/{{ component.path }}';
+ + To import this component using a bundler: +
+import '@shoelace-style/shoelace/dist/{{ component.path }}';
+ + To import this component as a React component: +
+import { {{ component.name }} } from '@shoelace-style/shoelace/dist/react';
+ | Name | +Description | +
|---|---|
+ {% if slot.name %}
+ {{ slot.name }}
+ {% else %}
+ (default)
+ {% endif %}
+ |
+ {{ slot.description | markdownInline | safe }} | +
Learn more about using slots.
+ {% endif %} + + {# Properties #} + {% if component.properties.length %} +| Name | +Description | +Reflects | +Type | +Default | +
|---|---|---|---|---|
+ {{ prop.name }}
+ {% if prop.attribute != prop.name %}
+ +
+ {{ prop.attribute }}
+
+
+ |
+ + {{ prop.description | markdownInline | safe }} + | +
+ {% if prop.reflects %}
+ |
+
+ {% if prop.type.text %}
+ {{ prop.type.text | markdownInline | safe }}
+ {% else %}
+ -
+ {% endif %}
+ |
+
+ {% if prop.default %}
+ {{ prop.default | markdownInline | safe }}
+ {% else %}
+ -
+ {% endif %}
+ |
+
updateComplete |
+ + A read-only promise that resolves when the component has + finished updating. + | ++ | + | + |
Learn more about attributes and properties.
+ {% endif %} + + {# Events #} + {% if component.events.length %} +| Name | +React Event | +Description | +Event Detail | +
|---|---|---|---|
{{ event.name }} |
+ {{ event.reactName }} |
+ {{ event.description | markdownInline | safe }} | +
+ {% if event.type.text %}
+ {{ event.type.text }}
+ {% else %}
+ -
+ {% endif %}
+ |
+
Learn more about events.
+ {% endif %} + + {# Methods #} + {% if component.methods.length %} +| Name | +Description | +Arguments | +
|---|---|---|
{{ method.name }}() |
+ {{ method.description | markdownInline | safe }} | +
+ {% if method.parameters.length %}
+
+ {% for param in method.parameters %}
+ {{ param.name }}: {{ param.type.text }}{% if not loop.last %},{% endif %}
+ {% endfor %}
+
+ {% else %}
+ -
+ {% endif %}
+ |
+
Learn more about methods.
+ {% endif %} + + {# Custom Properties #} + {% if component.cssProperties.length %} +| Name | +Description | +Default | +
|---|---|---|
{{ cssProperty.name }} |
+ {{ cssProperty.description | markdownInline | safe }} | +{{ cssProperty.default }} | +
Learn more about customizing CSS custom properties.
+ {% endif %} + + {# CSS Parts #} + {% if component.cssParts.length %} +| Name | +Description | +
|---|---|
{{ cssPart.name }} |
+ {{ cssPart.description | markdownInline | safe }} | +
Learn more about customizing CSS parts.
+ {% endif %} + + {# Animations #} + {% if component.animations.length %} +| Name | +Description | +
|---|---|
{{ animation.name }} |
+ {{ animation.description | markdownInline | safe }} | +
Learn more about customizing animations.
+ {% endif %} + + {# Dependencies #} + {% if component.dependencies.length %} +This component automatically imports the following dependencies.
+ +<{{ dependency }}>`. For example, a code field
+ * tagged with "html:preview" will be rendered as ``.
+ *
+ * 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;
+};
diff --git a/docs/_utilities/markdown.cjs b/docs/_utilities/markdown.cjs
new file mode 100644
index 000000000..4a73e8f39
--- /dev/null
+++ b/docs/_utilities/markdown.cjs
@@ -0,0 +1,67 @@
+const MarkdownIt = require('markdown-it');
+const markdownItContainer = require('markdown-it-container');
+const markdownItIns = require('markdown-it-ins');
+const markdownItKbd = require('markdown-it-kbd');
+const markdownItMark = require('markdown-it-mark');
+const markdownItReplaceIt = require('markdown-it-replace-it');
+
+const markdown = MarkdownIt({
+ html: true,
+ xhtmlOut: false,
+ breaks: false,
+ langPrefix: 'language-',
+ linkify: false,
+ typographer: false
+});
+
+// Third-party plugins
+markdown.use(markdownItContainer);
+markdown.use(markdownItIns);
+markdown.use(markdownItKbd);
+markdown.use(markdownItMark);
+markdown.use(markdownItReplaceIt);
+
+// Callouts
+['tip', 'warning', 'danger'].forEach(type => {
+ markdown.use(markdownItContainer, type, {
+ render: function (tokens, idx) {
+ if (tokens[idx].nesting === 1) {
+ return ``;
+ }
+ return '\n';
+ }
+ });
+});
+
+// Asides
+markdown.use(markdownItContainer, 'aside', {
+ render: function (tokens, idx) {
+ if (tokens[idx].nesting === 1) {
+ return `\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 `\n${markdown.utils.escapeHtml(m[1])}
\n`;
+ }
+ return '\n';
+ }
+});
+
+// Replace [#1234] with a link to GitHub issues
+markdownItReplaceIt.replacements.push({
+ name: 'github-issues',
+ re: /\[#([0-9]+)\]/gs,
+ sub: '#$1',
+ html: true,
+ default: true
+});
+
+module.exports = markdown;
diff --git a/docs/_utilities/prettier.cjs b/docs/_utilities/prettier.cjs
new file mode 100644
index 000000000..d58e7ada2
--- /dev/null
+++ b/docs/_utilities/prettier.cjs
@@ -0,0 +1,26 @@
+const { format } = require('prettier');
+
+/** Formats markup using prettier. */
+module.exports = function (content, options) {
+ options = {
+ arrowParens: 'avoid',
+ bracketSpacing: true,
+ htmlWhitespaceSensitivity: 'css',
+ insertPragma: false,
+ bracketSameLine: false,
+ jsxSingleQuote: false,
+ parser: 'html',
+ printWidth: 120,
+ proseWrap: 'preserve',
+ quoteProps: 'as-needed',
+ requirePragma: false,
+ semi: true,
+ singleQuote: true,
+ tabWidth: 2,
+ trailingComma: 'none',
+ useTabs: false,
+ ...options
+ };
+
+ return format(content, options);
+}
diff --git a/docs/_utilities/scrolling-tables.cjs b/docs/_utilities/scrolling-tables.cjs
new file mode 100644
index 000000000..148248dbe
--- /dev/null
+++ b/docs/_utilities/scrolling-tables.cjs
@@ -0,0 +1,21 @@
+/**
+ * Turns headings into clickable, deep linkable anchors. The provided doc should be a document object provided by JSDOM.
+ * The same document will be returned with the appropriate DOM manipulations.
+ */
+module.exports = function (doc, options) {
+ const tables = [...doc.querySelectorAll('table')];
+
+ options = {
+ className: 'table-scroll', // the class name to add to the table's container
+ ...options
+ };
+
+ tables.forEach(table => {
+ const div = doc.createElement('div');
+ div.classList.add(options.className);
+ table.insertAdjacentElement('beforebegin', div);
+ div.append(table);
+ });
+
+ return doc;
+};
diff --git a/docs/_utilities/strings.cjs b/docs/_utilities/strings.cjs
new file mode 100644
index 000000000..6831d66d9
--- /dev/null
+++ b/docs/_utilities/strings.cjs
@@ -0,0 +1,16 @@
+const slugify = require('slugify');
+
+/** Creates a slug from an arbitrary string of text. */
+module.exports.createSlug = function (text) {
+ return slugify(String(text), {
+ remove: /[^\w|\s]/g,
+ lower: true
+ });
+};
+
+/** Determines whether or not a link is external. */
+module.exports.isExternalLink = function (link) {
+ // We use the "internal" hostname when initializing JSDOM so we know that those are local links
+ if (!link.hostname || link.hostname === 'internal') return false;
+ return true;
+};
diff --git a/docs/_utilities/table-of-contents.cjs b/docs/_utilities/table-of-contents.cjs
new file mode 100644
index 000000000..1ac04fd31
--- /dev/null
+++ b/docs/_utilities/table-of-contents.cjs
@@ -0,0 +1,42 @@
+/**
+ * Generates an in-page table of contents based on headings.
+ */
+module.exports = function (doc, options) {
+ options = {
+ levels: ['h2'], // headings to include (they must have an id)
+ container: 'nav', // the container to append links to
+ listItem: true, // if true, links will be wrapped in