Compare commits
4 Commits
kj/pattern
...
react-impo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77f6991e59 | ||
|
|
0efb9b2f78 | ||
|
|
39677bf37d | ||
|
|
8535883152 |
2
.github/CODE_OF_CONDUCT.md
vendored
@@ -35,7 +35,7 @@ This Code of Conduct applies within all project spaces, and it also applies when
|
|||||||
|
|
||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at support@fontawesome.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at cory@abeautifulsite.net. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||||
|
|
||||||
|
|||||||
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
github: [claviska]
|
||||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,7 +1,4 @@
|
|||||||
contact_links:
|
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
|
- name: Help & Support
|
||||||
url: https://github.com/shoelace-style/shoelace/discussions/categories/help
|
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.
|
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
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
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?
|
||||||
4
.github/SECURITY.md
vendored
@@ -1,7 +1,7 @@
|
|||||||
# Reporting Security Issues
|
# Reporting Security Issues
|
||||||
|
|
||||||
We take security issues in Web Awesome very seriously and appreciate your efforts to disclose your findings responsibly.
|
We take security issues in Shoelace very seriously and appreciate your efforts to disclose your findings responsibly.
|
||||||
|
|
||||||
To report a security issue, email [support@fontawesome.com](mailto:support@fontawesome.com) and include "WEB AWESOME SECURITY" in the subject line.
|
To report a security issue, email [cory@abeautifulsite.net](mailto:cory@abeautifulsite.net) and include "SHOELACE SECURITY" in the subject line.
|
||||||
|
|
||||||
We'll respond as soon as possible and keep you updated throughout the process.
|
We'll respond as soon as possible and keep you updated throughout the process.
|
||||||
|
|||||||
4
.gitignore
vendored
@@ -1,12 +1,8 @@
|
|||||||
_site
|
_site
|
||||||
.cache
|
.cache
|
||||||
.DS_Store
|
.DS_Store
|
||||||
package.json
|
|
||||||
package-lock.json
|
|
||||||
dist
|
dist
|
||||||
docs/assets/images/sprite.svg
|
docs/assets/images/sprite.svg
|
||||||
docs/public/pagefind
|
|
||||||
node_modules
|
node_modules
|
||||||
src/react
|
src/react
|
||||||
cdn
|
cdn
|
||||||
.astro
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
*.hbs
|
*.hbs
|
||||||
*.mdx
|
|
||||||
.cache
|
.cache
|
||||||
.github
|
.github
|
||||||
cspell.json
|
cspell.json
|
||||||
@@ -8,7 +7,6 @@ docs/search.json
|
|||||||
src/components/icon/icons
|
src/components/icon/icons
|
||||||
src/react/index.ts
|
src/react/index.ts
|
||||||
node_modules
|
node_modules
|
||||||
package.json
|
|
||||||
package-lock.json
|
package-lock.json
|
||||||
tsconfig.json
|
tsconfig.json
|
||||||
cdn
|
cdn
|
||||||
|
|||||||
5
.vscode/settings.json
vendored
@@ -2,7 +2,6 @@
|
|||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "explicit"
|
"source.fixAll.eslint": true
|
||||||
},
|
}
|
||||||
"debug.enableStatusBarColor": false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Contributing to Web Awesome
|
# Contributing to Shoelace
|
||||||
|
|
||||||
Before contributing, please review the contributions guidelines at:
|
Before contributing, please review the contributions guidelines at:
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2023 Fonticons, Inc.
|
Copyright (c) 2020 A Beautiful Site, LLC
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
|||||||
38
README.md
@@ -1,4 +1,6 @@
|
|||||||
# Web Awesome
|
# Shoelace
|
||||||
|
|
||||||
|
A forward-thinking library of web components.
|
||||||
|
|
||||||
- Works with all frameworks 🧩
|
- Works with all frameworks 🧩
|
||||||
- Works with CDNs 🚛
|
- Works with CDNs 🚛
|
||||||
@@ -7,7 +9,7 @@
|
|||||||
- Built with accessibility in mind ♿️
|
- Built with accessibility in mind ♿️
|
||||||
- Open source 😸
|
- Open source 😸
|
||||||
|
|
||||||
Built by the folks behind [Font Awesome](https://fontawesome.com/).
|
Designed in New Hampshire by [Cory LaViska](https://twitter.com/claviska).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -19,15 +21,15 @@ Twitter: [@shoelace_style](https://twitter.com/shoelace_style)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Developers ✨
|
## Shoemakers 🥾
|
||||||
|
|
||||||
Developers can use this documentation to learn how to build Web Awesome from source. You will need Node >= 14.17 to build and run the project locally.
|
Shoemakers, or "Shoelace developers," can use this documentation to learn how to build Shoelace from source. You will need Node >= 14.17 to build and run the project locally.
|
||||||
|
|
||||||
**You don't need to do any of this to use Web Awesome!** This page is for people who want to contribute to the project, tinker with the source, or create a custom build of Web Awesome.
|
**You don't need to do any of this to use Shoelace!** This page is for people who want to contribute to the project, tinker with the source, or create a custom build of Shoelace.
|
||||||
|
|
||||||
If that's not what you're trying to do, the [documentation website](https://shoelace.style) is where you want to be.
|
If that's not what you're trying to do, the [documentation website](https://shoelace.style) is where you want to be.
|
||||||
|
|
||||||
### What are you using to build Web Awesome?
|
### What are you using to build Shoelace?
|
||||||
|
|
||||||
Components are built with [LitElement](https://lit-element.polymer-project.org/), a custom elements base class that provides an intuitive API and reactive data binding. The build is a custom script with bundling powered by [esbuild](https://esbuild.github.io/).
|
Components are built with [LitElement](https://lit-element.polymer-project.org/), a custom elements base class that provides an intuitive API and reactive data binding. The build is a custom script with bundling powered by [esbuild](https://esbuild.github.io/).
|
||||||
|
|
||||||
@@ -36,8 +38,8 @@ Components are built with [LitElement](https://lit-element.polymer-project.org/)
|
|||||||
Start by [forking the repo](https://github.com/shoelace-style/shoelace/fork) on GitHub, then clone it locally and install dependencies.
|
Start by [forking the repo](https://github.com/shoelace-style/shoelace/fork) on GitHub, then clone it locally and install dependencies.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/YOUR_GITHUB_USERNAME/webawesome
|
git clone https://github.com/YOUR_GITHUB_USERNAME/shoelace
|
||||||
cd webawesome
|
cd shoelace
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -61,18 +63,30 @@ npm run build
|
|||||||
|
|
||||||
### Creating New Components
|
### Creating New Components
|
||||||
|
|
||||||
To scaffold a new component, run the following command, replacing `wa-tag-name` with the desired tag name.
|
To scaffold a new component, run the following command, replacing `sl-tag-name` with the desired tag name.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run create wa-tag-name
|
npm run create sl-tag-name
|
||||||
```
|
```
|
||||||
|
|
||||||
This will generate a source file, a stylesheet, and a docs page for you. When you start the dev server, you'll find the new component in the "Components" section of the sidebar.
|
This will generate a source file, a stylesheet, and a docs page for you. When you start the dev server, you'll find the new component in the "Components" section of the sidebar.
|
||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
|
||||||
Web Awesome is an open source project and contributions are encouraged! If you're interesting in contributing, please review the [contribution guidelines](CONTRIBUTING.md) first.
|
Shoelace is an open source project and contributions are encouraged! If you're interesting in contributing, please review the [contribution guidelines](CONTRIBUTING.md) first.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Web Awesome is available under the terms of the MIT license.
|
Shoelace is designed in New Hampshire by [Cory LaViska](https://twitter.com/claviska). It’s available under the terms of the MIT license.
|
||||||
|
|
||||||
|
Designing, developing, and supporting this library requires a lot of time, effort, and skill. I’d like to keep it open source so everyone can use it, but that doesn’t provide me with any income.
|
||||||
|
|
||||||
|
**Therefore, if you’re using my software to make a profit,** I respectfully ask that you help [fund its development](https://github.com/sponsors/claviska) by becoming a sponsor. There are multiple tiers to choose from with benefits at every level, including prioritized support, bug fixes, feature requests, and advertising.
|
||||||
|
|
||||||
|
👇 Your support is very much appreciated! 👇
|
||||||
|
|
||||||
|
- [Become a sponsor](https://github.com/sponsors/claviska)
|
||||||
|
- [Star on GitHub](https://github.com/shoelace-style/shoelace/stargazers)
|
||||||
|
- [Follow on Twitter](https://twitter.com/shoelace_style)
|
||||||
|
|
||||||
|
Whether you're building Shoelace or building something _with_ Shoelace — have fun creating! 🥾
|
||||||
|
|||||||
17
cspell.json
@@ -21,6 +21,7 @@
|
|||||||
"cdndir",
|
"cdndir",
|
||||||
"chatbubble",
|
"chatbubble",
|
||||||
"checkmark",
|
"checkmark",
|
||||||
|
"claviska",
|
||||||
"Clippy",
|
"Clippy",
|
||||||
"codebases",
|
"codebases",
|
||||||
"codepen",
|
"codepen",
|
||||||
@@ -28,7 +29,6 @@
|
|||||||
"colour",
|
"colour",
|
||||||
"combobox",
|
"combobox",
|
||||||
"Commonmark",
|
"Commonmark",
|
||||||
"compat",
|
|
||||||
"Composability",
|
"Composability",
|
||||||
"Consolas",
|
"Consolas",
|
||||||
"contenteditable",
|
"contenteditable",
|
||||||
@@ -46,7 +46,6 @@
|
|||||||
"dropdowns",
|
"dropdowns",
|
||||||
"easings",
|
"easings",
|
||||||
"endraw",
|
"endraw",
|
||||||
"endregion",
|
|
||||||
"enterkeyhint",
|
"enterkeyhint",
|
||||||
"eqeqeq",
|
"eqeqeq",
|
||||||
"erroneou",
|
"erroneou",
|
||||||
@@ -85,11 +84,11 @@
|
|||||||
"Kool",
|
"Kool",
|
||||||
"labelledby",
|
"labelledby",
|
||||||
"Laravel",
|
"Laravel",
|
||||||
|
"LaViska",
|
||||||
"linkify",
|
"linkify",
|
||||||
"listbox",
|
"listbox",
|
||||||
"listitem",
|
"listitem",
|
||||||
"litelement",
|
"litelement",
|
||||||
"longform",
|
|
||||||
"lowercasing",
|
"lowercasing",
|
||||||
"Lucide",
|
"Lucide",
|
||||||
"maxlength",
|
"maxlength",
|
||||||
@@ -101,7 +100,6 @@
|
|||||||
"monospace",
|
"monospace",
|
||||||
"mousedown",
|
"mousedown",
|
||||||
"mousemove",
|
"mousemove",
|
||||||
"mouseout",
|
|
||||||
"mouseup",
|
"mouseup",
|
||||||
"multiselectable",
|
"multiselectable",
|
||||||
"nextjs",
|
"nextjs",
|
||||||
@@ -111,14 +109,10 @@
|
|||||||
"novalidate",
|
"novalidate",
|
||||||
"npmdir",
|
"npmdir",
|
||||||
"Numberish",
|
"Numberish",
|
||||||
"oklab",
|
|
||||||
"oklch",
|
|
||||||
"onscrollend",
|
|
||||||
"outdir",
|
"outdir",
|
||||||
"ParamagicDev",
|
"ParamagicDev",
|
||||||
"peta",
|
"peta",
|
||||||
"petabit",
|
"petabit",
|
||||||
"Preact",
|
|
||||||
"prismjs",
|
"prismjs",
|
||||||
"progressbar",
|
"progressbar",
|
||||||
"radiogroup",
|
"radiogroup",
|
||||||
@@ -166,18 +160,13 @@
|
|||||||
"unbundles",
|
"unbundles",
|
||||||
"unbundling",
|
"unbundling",
|
||||||
"unicons",
|
"unicons",
|
||||||
"unsanitized",
|
|
||||||
"unsupportive",
|
"unsupportive",
|
||||||
"valpha",
|
"valpha",
|
||||||
"valuenow",
|
"valuenow",
|
||||||
"valuetext",
|
"valuetext",
|
||||||
"viewports",
|
|
||||||
"Vuejs",
|
|
||||||
"WCAG",
|
|
||||||
"webawesome",
|
|
||||||
"WEBP",
|
"WEBP",
|
||||||
"Webpacker",
|
"Webpacker",
|
||||||
"xmark"
|
"wordmark"
|
||||||
],
|
],
|
||||||
"ignorePaths": [
|
"ignorePaths": [
|
||||||
"package.json",
|
"package.json",
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { customElementJetBrainsPlugin } from 'custom-element-jet-brains-integration';
|
|
||||||
import { customElementVsCodePlugin } from 'custom-element-vs-code-integration';
|
import { customElementVsCodePlugin } from 'custom-element-vs-code-integration';
|
||||||
import { customElementVuejsPlugin } from 'custom-element-vuejs-integration';
|
|
||||||
import { parse } from 'comment-parser';
|
import { parse } from 'comment-parser';
|
||||||
import { pascalCase } from 'pascal-case';
|
import { pascalCase } from 'pascal-case';
|
||||||
import commandLineArgs from 'command-line-args';
|
import commandLineArgs from 'command-line-args';
|
||||||
@@ -34,15 +32,14 @@ export default {
|
|||||||
plugins: [
|
plugins: [
|
||||||
// Append package data
|
// Append package data
|
||||||
{
|
{
|
||||||
name: 'wa-package-data',
|
name: 'shoelace-package-data',
|
||||||
packageLinkPhase({ customElementsManifest }) {
|
packageLinkPhase({ customElementsManifest }) {
|
||||||
customElementsManifest.package = { name, description, version, author, homepage, license };
|
customElementsManifest.package = { name, description, version, author, homepage, license };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Infer tag names because we no longer use @customElement decorators.
|
// Infer tag names because we no longer use @customElement decorators.
|
||||||
{
|
{
|
||||||
name: 'wa-infer-tag-names',
|
name: 'shoelace-infer-tag-names',
|
||||||
analyzePhase({ ts, node, moduleDoc }) {
|
analyzePhase({ ts, node, moduleDoc }) {
|
||||||
switch (node.kind) {
|
switch (node.kind) {
|
||||||
case ts.SyntaxKind.ClassDeclaration: {
|
case ts.SyntaxKind.ClassDeclaration: {
|
||||||
@@ -57,7 +54,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tagNameWithoutPrefix = path.basename(importPath, '.component.ts');
|
const tagNameWithoutPrefix = path.basename(importPath, '.component.ts');
|
||||||
const tagName = 'wa-' + tagNameWithoutPrefix;
|
const tagName = 'sl-' + tagNameWithoutPrefix;
|
||||||
|
|
||||||
classDoc.tagNameWithoutPrefix = tagNameWithoutPrefix;
|
classDoc.tagNameWithoutPrefix = tagNameWithoutPrefix;
|
||||||
classDoc.tagName = tagName;
|
classDoc.tagName = tagName;
|
||||||
@@ -68,10 +65,9 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Parse custom jsDoc tags
|
// Parse custom jsDoc tags
|
||||||
{
|
{
|
||||||
name: 'wa-custom-tags',
|
name: 'shoelace-custom-tags',
|
||||||
analyzePhase({ ts, node, moduleDoc }) {
|
analyzePhase({ ts, node, moduleDoc }) {
|
||||||
switch (node.kind) {
|
switch (node.kind) {
|
||||||
case ts.SyntaxKind.ClassDeclaration: {
|
case ts.SyntaxKind.ClassDeclaration: {
|
||||||
@@ -140,9 +136,8 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'wa-react-event-names',
|
name: 'shoelace-react-event-names',
|
||||||
analyzePhase({ ts, node, moduleDoc }) {
|
analyzePhase({ ts, node, moduleDoc }) {
|
||||||
switch (node.kind) {
|
switch (node.kind) {
|
||||||
case ts.SyntaxKind.ClassDeclaration: {
|
case ts.SyntaxKind.ClassDeclaration: {
|
||||||
@@ -159,9 +154,8 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'wa-translate-module-paths',
|
name: 'shoelace-translate-module-paths',
|
||||||
packageLinkPhase({ customElementsManifest }) {
|
packageLinkPhase({ customElementsManifest }) {
|
||||||
customElementsManifest?.modules?.forEach(mod => {
|
customElementsManifest?.modules?.forEach(mod => {
|
||||||
//
|
//
|
||||||
@@ -196,7 +190,6 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Generate custom VS Code data
|
// Generate custom VS Code data
|
||||||
customElementVsCodePlugin({
|
customElementVsCodePlugin({
|
||||||
outdir,
|
outdir,
|
||||||
@@ -204,27 +197,9 @@ export default {
|
|||||||
referencesTemplate: (_, tag) => [
|
referencesTemplate: (_, tag) => [
|
||||||
{
|
{
|
||||||
name: 'Documentation',
|
name: 'Documentation',
|
||||||
url: `https://shoelace.style/components/${tag.replace('wa-', '')}`
|
url: `https://shoelace.style/components/${tag.replace('sl-', '')}`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}),
|
|
||||||
|
|
||||||
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
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": ["astro-build.astro-vscode"],
|
|
||||||
"unwantedRecommendations": []
|
|
||||||
}
|
|
||||||
11
docs/.vscode/launch.json
vendored
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"command": "./node_modules/.bin/astro dev",
|
|
||||||
"name": "Development server",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "node-terminal"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
347
docs/_includes/component.njk
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
{% extends "default.njk" %}
|
||||||
|
|
||||||
|
{# Find the component based on the `tag` front matter #}
|
||||||
|
{% set component = getComponent('sl-' + page.fileSlug) %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{# Determine the badge variant #}
|
||||||
|
{% if component.status == 'stable' %}
|
||||||
|
{% set badgeVariant = 'primary' %}
|
||||||
|
{% elseif component.status == 'experimental' %}
|
||||||
|
{% set badgeVariant = 'warning' %}
|
||||||
|
{% elseif component.status == 'planned' %}
|
||||||
|
{% set badgeVariant = 'neutral' %}
|
||||||
|
{% elseif component.status == 'deprecated' %}
|
||||||
|
{% set badgeVariant = 'danger' %}
|
||||||
|
{% else %}
|
||||||
|
{% set badgeVariant = 'neutral' %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Header #}
|
||||||
|
<header class="component-header">
|
||||||
|
<h1>{{ component.name | classNameToComponentName }}</h1>
|
||||||
|
|
||||||
|
<div class="component-header__tag">
|
||||||
|
<code><{{ component.tagName }}> | {{ component.name }}</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="component-header__info">
|
||||||
|
<sl-badge variant="neutral" pill>
|
||||||
|
Since {{component.since or '?' }}
|
||||||
|
</sl-badge>
|
||||||
|
<sl-badge variant="{{ badgeVariant }}" pill style="text-transform: capitalize;">
|
||||||
|
{{ component.status }}
|
||||||
|
</sl-badge>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<p class="component-summary">
|
||||||
|
{% if component.summary %}
|
||||||
|
{{ component.summary | markdownInline | safe }}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{# Markdown content #}
|
||||||
|
{{ content | safe }}
|
||||||
|
|
||||||
|
{# Importing #}
|
||||||
|
<h2>Importing</h2>
|
||||||
|
<p>
|
||||||
|
If you're using the autoloader or the traditional loader, you can ignore this section. Otherwise, feel free to use
|
||||||
|
any of the following snippets to <a href="/getting-started/installation#cherry-picking">cherry pick</a> this component.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<sl-tab-group>
|
||||||
|
<sl-tab slot="nav" panel="script">Script</sl-tab>
|
||||||
|
<sl-tab slot="nav" panel="import">Import</sl-tab>
|
||||||
|
<sl-tab slot="nav" panel="bundler">Bundler</sl-tab>
|
||||||
|
<sl-tab slot="nav" panel="react">React</sl-tab>
|
||||||
|
|
||||||
|
<sl-tab-panel name="script">
|
||||||
|
<p>
|
||||||
|
To import this component from <a href="https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace">the CDN</a>
|
||||||
|
using a script tag:
|
||||||
|
</p>
|
||||||
|
<pre><code class="language-html"><script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@{{ meta.version }}/{{ meta.cdndir }}/{{ component.path }}"></script></code></pre>
|
||||||
|
</sl-tab-panel>
|
||||||
|
|
||||||
|
<sl-tab-panel name="import">
|
||||||
|
<p>
|
||||||
|
To import this component from <a href="https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace">the CDN</a>
|
||||||
|
using a JavaScript import:
|
||||||
|
</p>
|
||||||
|
<pre><code class="language-js">import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@{{ meta.version }}/{{ meta.cdndir }}/{{ component.path }}';</code></pre>
|
||||||
|
</sl-tab-panel>
|
||||||
|
|
||||||
|
<sl-tab-panel name="bundler">
|
||||||
|
<p>
|
||||||
|
To import this component using <a href="{{ rootUrl('/getting-started/installation#bundling') }}">a bundler</a>:
|
||||||
|
</p>
|
||||||
|
<pre><code class="language-js">import '@shoelace-style/shoelace/{{ meta.npmdir }}/{{ component.path }}';</code></pre>
|
||||||
|
</sl-tab-panel>
|
||||||
|
|
||||||
|
<sl-tab-panel name="react">
|
||||||
|
<p>
|
||||||
|
To import this component as a <a href="/frameworks/react">React component</a>:
|
||||||
|
</p>
|
||||||
|
<pre><code class="language-js">import {{ component.name }} from '@shoelace-style/shoelace/{{ meta.npmdir }}/react/{{ component.tagNameWithoutPrefix }}';</code></pre>
|
||||||
|
</sl-tab-panel>
|
||||||
|
</sl-tab-group>
|
||||||
|
|
||||||
|
{# Slots #}
|
||||||
|
{% if component.slots.length %}
|
||||||
|
<h2>Slots</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="table-name">Name</th>
|
||||||
|
<th class="table-description">Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for slot in component.slots %}
|
||||||
|
<tr>
|
||||||
|
<td class="nowrap">
|
||||||
|
{% if slot.name %}
|
||||||
|
<code>{{ slot.name }}</code>
|
||||||
|
{% else %}
|
||||||
|
(default)
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ slot.description | markdownInline | safe }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#slots') }}">using slots</a>.</em></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Properties #}
|
||||||
|
{% if component.properties.length %}
|
||||||
|
<h2>Properties</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="table-name">Name</th>
|
||||||
|
<th class="table-description">Description</th>
|
||||||
|
<th class="table-reflects">Reflects</th>
|
||||||
|
<th class="table-type">Type</th>
|
||||||
|
<th class="table-default">Default</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for prop in component.properties %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code class="nowrap">{{ prop.name }}</code>
|
||||||
|
{% if prop.attribute != prop.name %}
|
||||||
|
<br>
|
||||||
|
<sl-tooltip content="This attribute is different from its property">
|
||||||
|
<small>
|
||||||
|
<code class="nowrap">
|
||||||
|
{{ prop.attribute }}
|
||||||
|
</code>
|
||||||
|
</small>
|
||||||
|
</sl-tooltip>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ prop.description | markdownInline | safe }}
|
||||||
|
</td>
|
||||||
|
<td style="text-align: center;">
|
||||||
|
{% if prop.reflects %}
|
||||||
|
<sl-icon label="yes" name="check-lg"></sl-icon>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if prop.type.text %}
|
||||||
|
<code>{{ prop.type.text | markdownInline | safe }}</code>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if prop.default %}
|
||||||
|
<code>{{ prop.default | markdownInline | safe }}</code>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
<tr>
|
||||||
|
<td class="nowrap"><code>updateComplete</code></td>
|
||||||
|
<td>
|
||||||
|
A read-only promise that resolves when the component has
|
||||||
|
<a href="/getting-started/usage?#component-rendering-and-updating">finished updating</a>.
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#properties') }}">attributes and properties</a>.</em></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Events #}
|
||||||
|
{% if component.events.length %}
|
||||||
|
<h2>Events</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="table-name" data-flavor="html">Name</th>
|
||||||
|
<th class="table-name" data-flavor="react">React Event</th>
|
||||||
|
<th class="table-description">Description</th>
|
||||||
|
<th class="table-event-detail">Event Detail</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for event in component.events %}
|
||||||
|
<tr>
|
||||||
|
<td data-flavor="html"><code class="nowrap">{{ event.name }}</code></td>
|
||||||
|
<td data-flavor="react"><code class="nowrap">{{ event.reactName }}</code></td>
|
||||||
|
<td>{{ event.description | markdownInline | safe }}</td>
|
||||||
|
<td>
|
||||||
|
{% if event.type.text %}
|
||||||
|
<code>{{ event.type.text }}</code>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#events') }}">events</a>.</em></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Methods #}
|
||||||
|
{% if component.methods.length %}
|
||||||
|
<h2>Methods</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="table-name">Name</th>
|
||||||
|
<th class="table-description">Description</th>
|
||||||
|
<th class="table-arguments">Arguments</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for method in component.methods %}
|
||||||
|
<tr>
|
||||||
|
<td class="nowrap"><code>{{ method.name }}()</code></td>
|
||||||
|
<td>{{ method.description | markdownInline | safe }}</td>
|
||||||
|
<td>
|
||||||
|
{% if method.parameters.length %}
|
||||||
|
<code>
|
||||||
|
{% for param in method.parameters %}
|
||||||
|
{{ param.name }}: {{ param.type.text }}{% if not loop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</code>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#methods') }}">methods</a>.</em></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Custom Properties #}
|
||||||
|
{% if component.cssProperties.length %}
|
||||||
|
<h2>Custom Properties</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="table-name">Name</th>
|
||||||
|
<th class="table-description">Description</th>
|
||||||
|
<th class="table-default">Default</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for cssProperty in component.cssProperties %}
|
||||||
|
<tr>
|
||||||
|
<td class="nowrap"><code>{{ cssProperty.name }}</code></td>
|
||||||
|
<td>{{ cssProperty.description | markdownInline | safe }}</td>
|
||||||
|
<td>{{ cssProperty.default }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#custom-properties') }}">customizing CSS custom properties</a>.</em></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# CSS Parts #}
|
||||||
|
{% if component.cssParts.length %}
|
||||||
|
<h2>Parts</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="table-name">Name</th>
|
||||||
|
<th class="table-description">Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for cssPart in component.cssParts %}
|
||||||
|
<tr>
|
||||||
|
<td class="nowrap"><code>{{ cssPart.name }}</code></td>
|
||||||
|
<td>{{ cssPart.description | markdownInline | safe }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#component-parts') }}">customizing CSS parts</a>.</em></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Animations #}
|
||||||
|
{% if component.animations.length %}
|
||||||
|
<h2>Animations</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="table-name">Name</th>
|
||||||
|
<th class="table-description">Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for animation in component.animations %}
|
||||||
|
<tr>
|
||||||
|
<td class="nowrap"><code>{{ animation.name }}</code></td>
|
||||||
|
<td>{{ animation.description | markdownInline | safe }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#animations') }}">customizing animations</a>.</em></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Dependencies #}
|
||||||
|
{% if component.dependencies.length %}
|
||||||
|
<h2>Dependencies</h2>
|
||||||
|
|
||||||
|
<p>This component automatically imports the following dependencies.</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{% for dependency in component.dependencies %}
|
||||||
|
<li><code><{{ dependency }}></code></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
150
docs/_includes/default.njk
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html
|
||||||
|
lang="en"
|
||||||
|
data-layout="{{ layout }}"
|
||||||
|
data-shoelace-version="{{ meta.version }}"
|
||||||
|
>
|
||||||
|
<head>
|
||||||
|
{# Metadata #}
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="description" content="{{ meta.description }}" />
|
||||||
|
<title>{{ meta.title }}</title>
|
||||||
|
|
||||||
|
{# Opt out of Turbo caching #}
|
||||||
|
<meta name="turbo-cache-control">
|
||||||
|
|
||||||
|
{# Stylesheets #}
|
||||||
|
<link rel="stylesheet" href="{{ assetUrl('styles/docs.css') }}" />
|
||||||
|
<link rel="stylesheet" href="{{ assetUrl('styles/code-previews.css') }}" />
|
||||||
|
<link rel="stylesheet" href="{{ assetUrl('styles/search.css') }}" />
|
||||||
|
|
||||||
|
{# Favicons #}
|
||||||
|
<link rel="icon" href="{{ assetUrl('images/logo.svg') }}" type="image/x-icon" />
|
||||||
|
|
||||||
|
{# Twitter Cards #}
|
||||||
|
<meta name="twitter:card" content="summary" />
|
||||||
|
<meta name="twitter:creator" content="shoelace_style" />
|
||||||
|
<meta name="twitter:image" content="{{ assetUrl(meta.image, true) }}" />
|
||||||
|
|
||||||
|
{# OpenGraph #}
|
||||||
|
<meta property="og:url" content="{{ rootUrl(page.url, true) }}" />
|
||||||
|
<meta property="og:title" content="{{ meta.title }}" />
|
||||||
|
<meta property="og:description" content="{{ meta.description }}" />
|
||||||
|
<meta property="og:image" content="{{ assetUrl(meta.image, true) }}" />
|
||||||
|
|
||||||
|
{# Shoelace #}
|
||||||
|
<link rel="stylesheet" href="/dist/themes/light.css" />
|
||||||
|
<link rel="stylesheet" href="/dist/themes/dark.css" />
|
||||||
|
<script type="module" src="/dist/shoelace-autoloader.js"></script>
|
||||||
|
|
||||||
|
{# Set the initial theme and menu states here to prevent flashing #}
|
||||||
|
<script>
|
||||||
|
(() => {
|
||||||
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
const theme = localStorage.getItem('theme') || 'auto';
|
||||||
|
document.documentElement.classList.toggle('sl-theme-dark', theme === 'dark' || (theme === 'auto' && prefersDark));
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{# Turbo + Scroll positioning #}
|
||||||
|
<script src="{{ assetUrl('scripts/turbo.js') }}" type="module"></script>
|
||||||
|
<script src="{{ assetUrl('scripts/docs.js') }}" defer></script>
|
||||||
|
<script src="{{ assetUrl('scripts/code-previews.js') }}" defer></script>
|
||||||
|
<script src="{{ assetUrl('scripts/lunr.js') }}" defer></script>
|
||||||
|
<script src="{{ assetUrl('scripts/search.js') }}" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a id="skip-to-main" class="visually-hidden" href="#main-content" data-smooth-link="false">
|
||||||
|
Skip to main content
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{# Menu toggle #}
|
||||||
|
<button id="menu-toggle" type="button" aria-label="Menu">
|
||||||
|
<svg width="148" height="148" viewBox="0 0 148 148" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g stroke="currentColor" stroke-width="18" fill="none" fill-rule="evenodd" stroke-linecap="round">
|
||||||
|
<path d="M9.5 125.5h129M9.5 74.5h129M9.5 23.5h129"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{# Icon toolbar #}
|
||||||
|
<div id="icon-toolbar">
|
||||||
|
{# GitHub #}
|
||||||
|
<a href="https://github.com/shoelace-style/shoelace" title="View Shoelace on GitHub">
|
||||||
|
<sl-icon name="github"></sl-icon>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{# Twitter #}
|
||||||
|
<a href="https://twitter.com/shoelace_style" title="Follow Shoelace on Twitter">
|
||||||
|
<sl-icon name="twitter"></sl-icon>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{# Theme selector #}
|
||||||
|
<sl-dropdown id="theme-selector" placement="bottom-end" distance="3">
|
||||||
|
<sl-button slot="trigger" size="small" variant="text" caret title="Press \ to toggle">
|
||||||
|
<sl-icon class="only-light" name="sun-fill"></sl-icon>
|
||||||
|
<sl-icon class="only-dark" name="moon-fill"></sl-icon>
|
||||||
|
</sl-button>
|
||||||
|
<sl-menu>
|
||||||
|
<sl-menu-item type="checkbox" value="light">Light</sl-menu-item>
|
||||||
|
<sl-menu-item type="checkbox" value="dark">Dark</sl-menu-item>
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
<sl-menu-item type="checkbox" value="auto">System</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
</sl-dropdown>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<aside id="sidebar" data-preserve-scroll>
|
||||||
|
<header>
|
||||||
|
<a href="/">
|
||||||
|
<img src="{{ assetUrl('images/wordmark.svg') }}" alt="Shoelace" />
|
||||||
|
</a>
|
||||||
|
<div class="sidebar-version">
|
||||||
|
{{ meta.version }}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="sidebar-buttons">
|
||||||
|
<sl-button size="small" class="repo-button repo-button--github" href="https://github.com/shoelace-style/shoelace" target="_blank">
|
||||||
|
<sl-icon slot="prefix" name="github"></sl-icon> Code
|
||||||
|
</sl-button>
|
||||||
|
<sl-button size="small" class="repo-button repo-button--star" href="https://github.com/shoelace-style/shoelace/stargazers" target="_blank">
|
||||||
|
<sl-icon slot="prefix" name="star-fill"></sl-icon> Star
|
||||||
|
</sl-button>
|
||||||
|
<sl-button size="small" class="repo-button repo-button--twitter" href="https://twitter.com/shoelace_style" target="_blank">
|
||||||
|
<sl-icon slot="prefix" name="twitter"></sl-icon> Follow
|
||||||
|
</sl-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="search-box" type="button" title="Press / to search" aria-label="Search" data-plugin="search">
|
||||||
|
<sl-icon name="search"></sl-icon>
|
||||||
|
<span>Search</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
{% include 'sidebar.njk' %}
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
{# Content #}
|
||||||
|
<main>
|
||||||
|
<a id="main-content"></a>
|
||||||
|
<article id="content" class="content{% if toc %} content--with-toc{% endif %}">
|
||||||
|
{% if toc %}
|
||||||
|
<div class="content__toc">
|
||||||
|
<ul>
|
||||||
|
<li class="top"><a href="#">{{ meta.title }}</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="content__body">
|
||||||
|
{% block content %}
|
||||||
|
{{ content | safe }}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
65
docs/_includes/sidebar.njk
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<h2>Getting Started</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/">Home</a></li>
|
||||||
|
<li><a href="/getting-started/installation">Installation</a></li>
|
||||||
|
<li><a href="/getting-started/usage">Usage</a></li>
|
||||||
|
<li><a href="/getting-started/themes">Themes</a></li>
|
||||||
|
<li><a href="/getting-started/customizing">Customizing</a></li>
|
||||||
|
<li><a href="/getting-started/form-controls">Form Controls</a></li>
|
||||||
|
<li><a href="/getting-started/localization">Localization</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h2>Frameworks</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/frameworks/react">React</a></li>
|
||||||
|
<li><a href="/frameworks/vue">Vue</a></li>
|
||||||
|
<li><a href="/frameworks/angular">Angular</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h2>Resources</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/resources/community">Community</a></li>
|
||||||
|
<li><a href="https://github.com/shoelace-style/shoelace/discussions">Help & Support</a></li>
|
||||||
|
<li><a href="/resources/accessibility">Accessibility</a></li>
|
||||||
|
<li><a href="/resources/contributing">Contributing</a></li>
|
||||||
|
<li><a href="/resources/changelog">Changelog</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h2>Components</h2>
|
||||||
|
<ul>
|
||||||
|
{% for component in meta.components %}
|
||||||
|
<li>
|
||||||
|
<a href="/components/{{ component.tagName | removeSlPrefix }}">
|
||||||
|
{{ component.name | classNameToComponentName }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h2>Design Tokens</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/tokens/typography">Typography</a></li>
|
||||||
|
<li><a href="/tokens/color">Color</a></li>
|
||||||
|
<li><a href="/tokens/spacing">Spacing</a></li>
|
||||||
|
<li><a href="/tokens/elevation">Elevation</a></li>
|
||||||
|
<li><a href="/tokens/border-radius">Border Radius</a></li>
|
||||||
|
<li><a href="/tokens/transition">Transition</a></li>
|
||||||
|
<li><a href="/tokens/z-index">Z-index</a></li>
|
||||||
|
<li><a href="/tokens/more">More Tokens</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h2>Tutorials</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/tutorials/integrating-with-laravel">Integrating with Laravel</a></li>
|
||||||
|
<li><a href="/tutorials/integrating-with-nextjs">Integrating with NextJS</a></li>
|
||||||
|
<li><a href="/tutorials/integrating-with-rails">Integrating with Rails</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
35
docs/_utilities/active-links.cjs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
function normalizePathname(pathname) {
|
||||||
|
// Remove /index.html
|
||||||
|
if (pathname.endsWith('/index.html')) {
|
||||||
|
pathname = pathname.replace(/\/index\.html/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove trailing slashes
|
||||||
|
return pathname.replace(/\/$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a class name to links that are currently active.
|
||||||
|
*/
|
||||||
|
module.exports = function (doc, options) {
|
||||||
|
options = {
|
||||||
|
className: 'active-link', // the class to add to active links
|
||||||
|
pathname: undefined, // the current pathname to compare
|
||||||
|
within: 'body', // element containing the target links
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
const within = doc.querySelector(options.within);
|
||||||
|
|
||||||
|
if (!within) {
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
within.querySelectorAll('a').forEach(link => {
|
||||||
|
if (normalizePathname(options.pathname) === normalizePathname(link.pathname)) {
|
||||||
|
link.classList.add(options.className);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
};
|
||||||
64
docs/_utilities/anchor-headings.cjs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
const { createSlug } = require('./strings.cjs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns headings into clickable, deep linkable anchors. The provided doc should be a document object provided by JSDOM.
|
||||||
|
* The same document will be returned with the appropriate DOM manipulations.
|
||||||
|
*/
|
||||||
|
module.exports = function (doc, options) {
|
||||||
|
options = {
|
||||||
|
levels: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], // the headings to convert
|
||||||
|
className: 'anchor-heading', // the class name to add
|
||||||
|
within: 'body', // the element containing the target headings
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
const within = doc.querySelector(options.within);
|
||||||
|
|
||||||
|
if (!within) {
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
within.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(heading => {
|
||||||
|
const hasAnchor = heading.querySelector('a');
|
||||||
|
const anchor = doc.createElement('a');
|
||||||
|
let id = heading.textContent ?? '';
|
||||||
|
let suffix = 0;
|
||||||
|
|
||||||
|
// Skip heading levels we don't care about
|
||||||
|
if (!options.levels?.includes(heading.tagName.toLowerCase())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert dots to underscores
|
||||||
|
id = id.replace(/\./g, '_');
|
||||||
|
|
||||||
|
// Turn it into a slug
|
||||||
|
id = createSlug(id);
|
||||||
|
|
||||||
|
// Make sure it starts with a letter
|
||||||
|
if (!/^[a-z]/i.test(id)) {
|
||||||
|
id = `id_${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the id is unique
|
||||||
|
const originalId = id;
|
||||||
|
while (doc.getElementById(id) !== null) {
|
||||||
|
id = `${originalId}-${++suffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAnchor || !id) return;
|
||||||
|
|
||||||
|
heading.setAttribute('id', id);
|
||||||
|
anchor.setAttribute('href', `#${encodeURIComponent(id)}`);
|
||||||
|
anchor.setAttribute('aria-label', `Direct link to "${heading.textContent}"`);
|
||||||
|
|
||||||
|
if (options.className) {
|
||||||
|
heading.classList.add(options.className);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the anchor
|
||||||
|
heading.append(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
};
|
||||||
@@ -1,20 +1,17 @@
|
|||||||
|
const customElementsManifest = require('../../dist/custom-elements.json');
|
||||||
|
|
||||||
//
|
//
|
||||||
// Export it here so we can import it elsewhere and use the same version
|
// Export it here so we can import it elsewhere and use the same version
|
||||||
//
|
//
|
||||||
import * as path from 'node:path';
|
module.exports.customElementsManifest = customElementsManifest;
|
||||||
import * as fs from 'node:fs';
|
|
||||||
|
|
||||||
// We make it a function to lazy evaluate for re-renders
|
|
||||||
export const customElementsManifest = () =>
|
|
||||||
JSON.parse(fs.readFileSync(path.join(process.cwd(), '/../dist/custom-elements.json')), { encoding: 'utf-8' });
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Gets all components from custom-elements.json and returns them in a more documentation-friendly format.
|
// Gets all components from custom-elements.json and returns them in a more documentation-friendly format.
|
||||||
//
|
//
|
||||||
export function getAllComponents() {
|
module.exports.getAllComponents = function () {
|
||||||
const allComponents = [];
|
const allComponents = [];
|
||||||
|
|
||||||
customElementsManifest().modules?.forEach(module => {
|
customElementsManifest.modules?.forEach(module => {
|
||||||
module.declarations?.forEach(declaration => {
|
module.declarations?.forEach(declaration => {
|
||||||
if (declaration.customElement) {
|
if (declaration.customElement) {
|
||||||
// Generate the dist path based on the src path and attach it to the component
|
// Generate the dist path based on the src path and attach it to the component
|
||||||
@@ -71,31 +68,4 @@ export function getAllComponents() {
|
|||||||
if (a.name > b.name) return 1;
|
if (a.name > b.name) return 1;
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export function getComponent(tagName) {
|
|
||||||
const allComponents = getAllComponents();
|
|
||||||
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). ${allComponents}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
component.hasSlots = Boolean(component.slots?.length);
|
|
||||||
component.hasProperties = Boolean(component.properties?.length);
|
|
||||||
component.hasEvents = Boolean(component.events?.length);
|
|
||||||
component.hasMethods = Boolean(component.methods?.length);
|
|
||||||
component.hasCssProperties = Boolean(component.cssProperties?.length);
|
|
||||||
component.hasCssParts = Boolean(component.cssParts?.length);
|
|
||||||
component.hasAnimations = Boolean(component.animations?.length);
|
|
||||||
component.hasDependencies = Boolean(component.dependencies?.length);
|
|
||||||
return component;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getComponentFromFileName(filename) {
|
|
||||||
const { name } = path.parse(filename);
|
|
||||||
const tagName = 'wa-' + name;
|
|
||||||
return getComponent(tagName);
|
|
||||||
}
|
|
||||||
138
docs/_utilities/code-previews.cjs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
let count = 1;
|
||||||
|
|
||||||
|
function escapeHtml(str) {
|
||||||
|
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns code fields with the :preview suffix into interactive code previews.
|
||||||
|
*/
|
||||||
|
module.exports = function (doc, options) {
|
||||||
|
options = {
|
||||||
|
within: 'body', // the element containing the code fields to convert
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
const within = doc.querySelector(options.within);
|
||||||
|
if (!within) {
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
within.querySelectorAll('[class*=":preview"]').forEach(code => {
|
||||||
|
const pre = code.closest('pre');
|
||||||
|
if (!pre) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const adjacentPre = pre.nextElementSibling?.tagName.toLowerCase() === 'pre' ? pre.nextElementSibling : null;
|
||||||
|
const reactCode = adjacentPre?.querySelector('code[class$="react"]');
|
||||||
|
const sourceGroupId = `code-preview-source-group-${count}`;
|
||||||
|
const isExpanded = code.getAttribute('class').includes(':expanded');
|
||||||
|
const noCodePen = code.getAttribute('class').includes(':no-codepen');
|
||||||
|
|
||||||
|
count++;
|
||||||
|
|
||||||
|
const htmlButton = `
|
||||||
|
<button type="button"
|
||||||
|
title="Show HTML code"
|
||||||
|
class="code-preview__button code-preview__button--html"
|
||||||
|
>
|
||||||
|
HTML
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const reactButton = `
|
||||||
|
<button type="button" title="Show React code" class="code-preview__button code-preview__button--react">
|
||||||
|
React
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const codePenButton = `
|
||||||
|
<button type="button" class="code-preview__button code-preview__button--codepen" title="Edit on CodePen">
|
||||||
|
<svg
|
||||||
|
width="138"
|
||||||
|
height="26"
|
||||||
|
viewBox="0 0 138 26"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2.3"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M80 6h-9v14h9 M114 6h-9 v14h9 M111 13h-6 M77 13h-6 M122 20V6l11 14V6 M22 16.7L33 24l11-7.3V9.3L33 2L22 9.3V16.7z M44 16.7L33 9.3l-11 7.4 M22 9.3l11 7.3 l11-7.3 M33 2v7.3 M33 16.7V24 M88 14h6c2.2 0 4-1.8 4-4s-1.8-4-4-4h-6v14 M15 8c-1.3-1.3-3-2-5-2c-4 0-7 3-7 7s3 7 7 7 c2 0 3.7-0.8 5-2 M64 13c0 4-3 7-7 7h-5V6h5C61 6 64 9 64 13z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const codePreview = `
|
||||||
|
<div class="code-preview ${isExpanded ? 'code-preview--expanded' : ''}">
|
||||||
|
<div class="code-preview__preview">
|
||||||
|
${code.textContent}
|
||||||
|
<div class="code-preview__resizer">
|
||||||
|
<sl-icon name="grip-vertical"></sl-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="code-preview__source-group" id="${sourceGroupId}">
|
||||||
|
<div class="code-preview__source code-preview__source--html" ${reactCode ? 'data-flavor="html"' : ''}>
|
||||||
|
<pre><code class="language-html">${escapeHtml(code.textContent)}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${
|
||||||
|
reactCode
|
||||||
|
? `
|
||||||
|
<div class="code-preview__source code-preview__source--react" data-flavor="react">
|
||||||
|
<pre><code class="language-jsx">${escapeHtml(reactCode.textContent)}</code></pre>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="code-preview__buttons">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="code-preview__button code-preview__toggle"
|
||||||
|
aria-expanded="${isExpanded ? 'true' : 'false'}"
|
||||||
|
aria-controls="${sourceGroupId}"
|
||||||
|
>
|
||||||
|
Source
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
${reactCode ? ` ${htmlButton} ${reactButton} ` : ''}
|
||||||
|
|
||||||
|
${noCodePen ? '' : codePenButton}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
pre.insertAdjacentHTML('afterend', codePreview);
|
||||||
|
pre.remove();
|
||||||
|
|
||||||
|
if (adjacentPre) {
|
||||||
|
adjacentPre.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wrap code preview scripts in anonymous functions so they don't run in the global scope
|
||||||
|
doc.querySelectorAll('.code-preview__preview script').forEach(script => {
|
||||||
|
if (script.type === 'module') {
|
||||||
|
// Modules are already scoped
|
||||||
|
script.textContent = script.innerHTML;
|
||||||
|
} else {
|
||||||
|
// Wrap non-modules in an anonymous function so they don't run in the global scope
|
||||||
|
script.textContent = `(() => { ${script.innerHTML} })();`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
};
|
||||||
26
docs/_utilities/copy-code-buttons.cjs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Adds copy code buttons to code fields. The provided doc should be a document object provided by JSDOM. The same
|
||||||
|
* document will be returned with the appropriate DOM manipulations.
|
||||||
|
*/
|
||||||
|
module.exports = function (doc) {
|
||||||
|
doc.querySelectorAll('pre > code').forEach(code => {
|
||||||
|
const pre = code.closest('pre');
|
||||||
|
const button = doc.createElement('button');
|
||||||
|
button.setAttribute('type', 'button');
|
||||||
|
button.classList.add('copy-code-button');
|
||||||
|
button.setAttribute('aria-label', 'Copy');
|
||||||
|
button.innerHTML = `
|
||||||
|
<svg class="copy-code-button__copy-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-files" viewBox="0 0 16 16" part="svg">
|
||||||
|
<path d="M13 0H6a2 2 0 0 0-2 2 2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2 2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm0 13V4a2 2 0 0 0-2-2H5a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zM3 4a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4z"></path>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<svg class="copy-code-button__copied-icon" style="display: none;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check-lg" viewBox="0 0 16 16" part="svg">
|
||||||
|
<path d="M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425a.247.247 0 0 1 .02-.022Z"></path>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
|
||||||
|
pre.append(button);
|
||||||
|
});
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
};
|
||||||
41
docs/_utilities/external-links.cjs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
const { isExternalLink } = require('./strings.cjs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms external links to make them safer and optionally add a target. The provided doc should be a document
|
||||||
|
* object provided by JSDOM. The same document will be returned with the appropriate DOM manipulations.
|
||||||
|
*/
|
||||||
|
module.exports = function (doc, options) {
|
||||||
|
options = {
|
||||||
|
className: 'external-link', // the class name to add to links
|
||||||
|
noopener: true, // sets rel="noopener"
|
||||||
|
noreferrer: true, // sets rel="noreferrer"
|
||||||
|
ignore: () => false, // callback function to filter links that should be ignored
|
||||||
|
within: 'body', // element that contains the target links
|
||||||
|
target: '', // sets the target attribute
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
const within = doc.querySelector(options.within);
|
||||||
|
|
||||||
|
if (within) {
|
||||||
|
within.querySelectorAll('a').forEach(link => {
|
||||||
|
if (isExternalLink(link) && !options.ignore(link)) {
|
||||||
|
link.classList.add(options.className);
|
||||||
|
|
||||||
|
const rel = [];
|
||||||
|
if (options.noopener) rel.push('noopener');
|
||||||
|
if (options.noreferrer) rel.push('noreferrer');
|
||||||
|
|
||||||
|
if (rel.length) {
|
||||||
|
link.setAttribute('rel', rel.join(' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.target) {
|
||||||
|
link.setAttribute('target', options.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
};
|
||||||
63
docs/_utilities/highlight-code.cjs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
const Prism = require('prismjs');
|
||||||
|
const PrismLoader = require('prismjs/components/index.js');
|
||||||
|
|
||||||
|
PrismLoader('diff');
|
||||||
|
PrismLoader.silent = true;
|
||||||
|
|
||||||
|
/** Highlights a code string. */
|
||||||
|
function highlight(code, language) {
|
||||||
|
const alias = language.replace(/^diff-/, '');
|
||||||
|
const isDiff = /^diff-/i.test(language);
|
||||||
|
|
||||||
|
// Auto-load the target language
|
||||||
|
if (!Prism.languages[alias]) {
|
||||||
|
PrismLoader(alias);
|
||||||
|
|
||||||
|
if (!Prism.languages[alias]) {
|
||||||
|
throw new Error(`Unsupported language for code highlighting: "${language}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register diff-* languages to use the diff grammar
|
||||||
|
if (isDiff) {
|
||||||
|
Prism.languages[language] = Prism.languages.diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Prism.highlight(code, Prism.languages[language], language);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlights all code fields that have a language parameter. If the language has a colon in its name, the first chunk
|
||||||
|
* will be the language used and additional chunks will be applied as classes to the `<pre>`. For example, a code field
|
||||||
|
* tagged with "html:preview" will be rendered as `<pre class="language-html preview">`.
|
||||||
|
*
|
||||||
|
* The provided doc should be a document object provided by JSDOM. The same document will be returned with the
|
||||||
|
* appropriate DOM manipulations.
|
||||||
|
*/
|
||||||
|
module.exports = function (doc) {
|
||||||
|
doc.querySelectorAll('pre > code[class]').forEach(code => {
|
||||||
|
// Look for class="language-*" and split colons into separate classes
|
||||||
|
code.classList.forEach(className => {
|
||||||
|
if (className.startsWith('language-')) {
|
||||||
|
//
|
||||||
|
// We use certain suffixes to indicate code previews, expanded states, etc. The class might look something like
|
||||||
|
// this:
|
||||||
|
//
|
||||||
|
// class="language-html:preview:expanded"
|
||||||
|
//
|
||||||
|
// The language will always come first, so we need to drop the "language-" prefix and everything after the first
|
||||||
|
// color to get the highlighter language.
|
||||||
|
//
|
||||||
|
const language = className.replace(/^language-/, '').split(':')[0];
|
||||||
|
|
||||||
|
try {
|
||||||
|
code.innerHTML = highlight(code.textContent ?? '', language);
|
||||||
|
} catch (err) {
|
||||||
|
// Language not found, skip it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
};
|
||||||
67
docs/_utilities/markdown.cjs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
const MarkdownIt = require('markdown-it');
|
||||||
|
const markdownItContainer = require('markdown-it-container');
|
||||||
|
const markdownItIns = require('markdown-it-ins');
|
||||||
|
const markdownItKbd = require('markdown-it-kbd');
|
||||||
|
const markdownItMark = require('markdown-it-mark');
|
||||||
|
const markdownItReplaceIt = require('markdown-it-replace-it');
|
||||||
|
|
||||||
|
const markdown = MarkdownIt({
|
||||||
|
html: true,
|
||||||
|
xhtmlOut: false,
|
||||||
|
breaks: false,
|
||||||
|
langPrefix: 'language-',
|
||||||
|
linkify: false,
|
||||||
|
typographer: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Third-party plugins
|
||||||
|
markdown.use(markdownItContainer);
|
||||||
|
markdown.use(markdownItIns);
|
||||||
|
markdown.use(markdownItKbd);
|
||||||
|
markdown.use(markdownItMark);
|
||||||
|
markdown.use(markdownItReplaceIt);
|
||||||
|
|
||||||
|
// Callouts
|
||||||
|
['tip', 'warning', 'danger'].forEach(type => {
|
||||||
|
markdown.use(markdownItContainer, type, {
|
||||||
|
render: function (tokens, idx) {
|
||||||
|
if (tokens[idx].nesting === 1) {
|
||||||
|
return `<div role="alert" class="callout callout--${type}">`;
|
||||||
|
}
|
||||||
|
return '</div>\n';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Asides
|
||||||
|
markdown.use(markdownItContainer, 'aside', {
|
||||||
|
render: function (tokens, idx) {
|
||||||
|
if (tokens[idx].nesting === 1) {
|
||||||
|
return `<aside>`;
|
||||||
|
}
|
||||||
|
return '</aside>\n';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Details
|
||||||
|
markdown.use(markdownItContainer, 'details', {
|
||||||
|
validate: params => params.trim().match(/^details\s+(.*)$/),
|
||||||
|
render: (tokens, idx) => {
|
||||||
|
const m = tokens[idx].info.trim().match(/^details\s+(.*)$/);
|
||||||
|
if (tokens[idx].nesting === 1) {
|
||||||
|
return `<details>\n<summary><span>${markdown.utils.escapeHtml(m[1])}</span></summary>\n`;
|
||||||
|
}
|
||||||
|
return '</details>\n';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Replace [#1234] with a link to GitHub issues
|
||||||
|
markdownItReplaceIt.replacements.push({
|
||||||
|
name: 'github-issues',
|
||||||
|
re: /\[#([0-9]+)\]/gs,
|
||||||
|
sub: '<a href="https://github.com/shoelace-style/shoelace/issues/$1">#$1</a>',
|
||||||
|
html: true,
|
||||||
|
default: true
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = markdown;
|
||||||
26
docs/_utilities/prettier.cjs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
const { format } = require('prettier');
|
||||||
|
|
||||||
|
/** Formats markup using prettier. */
|
||||||
|
module.exports = function (content, options) {
|
||||||
|
options = {
|
||||||
|
arrowParens: 'avoid',
|
||||||
|
bracketSpacing: true,
|
||||||
|
htmlWhitespaceSensitivity: 'css',
|
||||||
|
insertPragma: false,
|
||||||
|
bracketSameLine: false,
|
||||||
|
jsxSingleQuote: false,
|
||||||
|
parser: 'html',
|
||||||
|
printWidth: 120,
|
||||||
|
proseWrap: 'preserve',
|
||||||
|
quoteProps: 'as-needed',
|
||||||
|
requirePragma: false,
|
||||||
|
semi: true,
|
||||||
|
singleQuote: true,
|
||||||
|
tabWidth: 2,
|
||||||
|
trailingComma: 'none',
|
||||||
|
useTabs: false,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
return format(content, options);
|
||||||
|
};
|
||||||
19
docs/_utilities/replacer.cjs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* @typedef {object} Replacement
|
||||||
|
* @property {string | RegExp} pattern
|
||||||
|
* @property {string} replacement
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Array<Replacement>} Replacements
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Document} content
|
||||||
|
* @param {Replacements} replacements
|
||||||
|
*/
|
||||||
|
module.exports = function (content, replacements) {
|
||||||
|
replacements.forEach(replacement => {
|
||||||
|
content.body.innerHTML = content.body.innerHTML.replaceAll(replacement.pattern, replacement.replacement);
|
||||||
|
});
|
||||||
|
};
|
||||||
21
docs/_utilities/scrolling-tables.cjs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Turns headings into clickable, deep linkable anchors. The provided doc should be a document object provided by JSDOM.
|
||||||
|
* The same document will be returned with the appropriate DOM manipulations.
|
||||||
|
*/
|
||||||
|
module.exports = function (doc, options) {
|
||||||
|
const tables = [...doc.querySelectorAll('table')];
|
||||||
|
|
||||||
|
options = {
|
||||||
|
className: 'table-scroll', // the class name to add to the table's container
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
tables.forEach(table => {
|
||||||
|
const div = doc.createElement('div');
|
||||||
|
div.classList.add(options.className);
|
||||||
|
table.insertAdjacentElement('beforebegin', div);
|
||||||
|
div.append(table);
|
||||||
|
});
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
};
|
||||||
16
docs/_utilities/strings.cjs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
const slugify = require('slugify');
|
||||||
|
|
||||||
|
/** Creates a slug from an arbitrary string of text. */
|
||||||
|
module.exports.createSlug = function (text) {
|
||||||
|
return slugify(String(text), {
|
||||||
|
remove: /[^\w|\s]/g,
|
||||||
|
lower: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Determines whether or not a link is external. */
|
||||||
|
module.exports.isExternalLink = function (link) {
|
||||||
|
// We use the "internal" hostname when initializing JSDOM so we know that those are local links
|
||||||
|
if (!link.hostname || link.hostname === 'internal') return false;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
42
docs/_utilities/table-of-contents.cjs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Generates an in-page table of contents based on headings.
|
||||||
|
*/
|
||||||
|
module.exports = function (doc, options) {
|
||||||
|
options = {
|
||||||
|
levels: ['h2'], // headings to include (they must have an id)
|
||||||
|
container: 'nav', // the container to append links to
|
||||||
|
listItem: true, // if true, links will be wrapped in <li>
|
||||||
|
within: 'body', // the element containing the headings to summarize
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
const container = doc.querySelector(options.container);
|
||||||
|
const within = doc.querySelector(options.within);
|
||||||
|
const headingSelector = options.levels.map(h => `${h}[id]`).join(', ');
|
||||||
|
|
||||||
|
if (!container || !within) {
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
within.querySelectorAll(headingSelector).forEach(heading => {
|
||||||
|
const listItem = doc.createElement('li');
|
||||||
|
const link = doc.createElement('a');
|
||||||
|
const level = heading.tagName.slice(1);
|
||||||
|
|
||||||
|
link.href = `#${heading.id}`;
|
||||||
|
link.textContent = heading.textContent;
|
||||||
|
|
||||||
|
if (options.listItem) {
|
||||||
|
// List item + link
|
||||||
|
listItem.setAttribute('data-level', level);
|
||||||
|
listItem.append(link);
|
||||||
|
container.append(listItem);
|
||||||
|
} else {
|
||||||
|
// Link only
|
||||||
|
link.setAttribute('data-level', level);
|
||||||
|
container.append(link);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
};
|
||||||
23
docs/_utilities/typography.cjs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
const smartquotes = require('smartquotes');
|
||||||
|
|
||||||
|
smartquotes.replacements.push([/---/g, '\u2014']); // em dash
|
||||||
|
smartquotes.replacements.push([/--/g, '\u2013']); // en dash
|
||||||
|
smartquotes.replacements.push([/\.\.\./g, '\u2026']); // ellipsis
|
||||||
|
smartquotes.replacements.push([/\(c\)/gi, '\u00A9']); // copyright
|
||||||
|
smartquotes.replacements.push([/\(r\)/gi, '\u00AE']); // registered trademark
|
||||||
|
smartquotes.replacements.push([/\?!/g, '\u2048']); // ?!
|
||||||
|
smartquotes.replacements.push([/!!/g, '\u203C']); // !!
|
||||||
|
smartquotes.replacements.push([/\?\?/g, '\u2047']); // ??
|
||||||
|
smartquotes.replacements.push([/([0-9]\s?)-(\s?[0-9])/g, '$1\u2013$2']); // number ranges use en dash
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Improves typography by adding smart quotes and similar corrections within the specified element(s).
|
||||||
|
*
|
||||||
|
* The provided doc should be a document object provided by JSDOM. The same document will be returned with the
|
||||||
|
* appropriate DOM manipulations.
|
||||||
|
*/
|
||||||
|
module.exports = function (doc, selector = 'body') {
|
||||||
|
const elements = [...doc.querySelectorAll(selector)];
|
||||||
|
elements.forEach(el => smartquotes.element(el));
|
||||||
|
return doc;
|
||||||
|
};
|
||||||
|
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 |
7
docs/assets/images/logo.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg viewBox="0 0 127 141" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g fill-rule="nonzero" fill="#0ea5e9">
|
||||||
|
<path d="M102.375,90.85 C102.979,90.557 103.57,90.215 104.15,89.826 L106.425,88.501 C106.848,88.19 107.64,87.573 108.8,86.65 L108.95,86.501 C109.883,85.567 110.916,85.117 112.05,85.15 C112.55,85.15 113.133,85.284 113.8,85.55 L122.3,78 C122.533,77.8 122.767,77.633 123,77.5 L123.05,77.5 C123.95,76.967 124.7,77 125.3,77.6 C126.367,78.166 126.45,79.133 125.55,80.5 L116.65,88.399 C116.717,88.533 116.75,88.666 116.75,88.799 C116.883,89.399 116.833,89.999 116.6,90.599 C116.4,90.999 116.117,91.382 115.75,91.749 C115.379,92.145 114.996,92.528 114.6,92.898 L109.45,96.648 C108.99,96.97 108.523,97.27 108.05,97.548 C107.46,97.906 106.86,98.232 106.25,98.523 C105.985,98.644 105.718,98.76 105.45,98.874 C103.841,99.559 102.174,100.017 100.45,100.249 C99.65,106.982 97.35,113.183 93.55,118.849 C88.75,125.915 82.183,131.299 73.85,134.999 C65.55,138.699 56.567,140.549 46.9,140.549 C33.567,140.415 22.75,137.415 14.45,131.549 C4.817,124.75 0,115.7 0,104.399 L0,102.849 C0.333,95.149 2.6,87.849 6.8,80.95 C10.933,74.116 16.983,69.5 24.95,67.1 C29.05,65.9 33.166,65.3 37.3,65.3 C41.567,65.3 45.967,66.083 50.5,67.65 C55.033,69.216 60.15,71.916 65.85,75.75 L80.7,85.7 C84.833,88.399 88.6,90.233 92,91.2 C91.3,84.3 88.8,78.399 84.5,73.5 C80.2,68.533 74.717,64.9 68.05,62.6 L61.65,60.4 C55.783,58.333 51.417,56.3 48.55,54.3 C40.817,49.067 36.517,41.883 35.65,32.75 L35.5,30.05 C35.5,21.25 39.067,13.883 46.2,7.95 C52.567,2.65 60.133,0 68.9,0 C75.5,0 81.417,1.9 86.65,5.7 C91.917,9.533 94.9,14.967 95.6,22 L95.75,24.75 C95.75,29.85 94.433,34.216 91.8,37.85 C89.1,41.483 86.717,43.3 84.65,43.3 C84.21,43.269 83.802,43.21 83.425,43.125 L74.1,51.9 C72.6,52.733 71.583,52.583 71.05,51.45 C70.517,50.85 70.567,50.1 71.2,49.2 L71.25,49.15 C71.383,48.95 71.567,48.733 71.8,48.5 L80.475,40.275 C80.376,39.872 80.318,39.431 80.3,38.95 C80.3,37.817 80.75,36.867 81.65,36.1 C85.45,32.7 87.35,28.784 87.35,24.35 C87.35,19.95 85.683,16.25 82.35,13.25 C79.017,10.25 74.467,8.716 68.7,8.65 C61.5,8.65 55.583,10.817 50.95,15.15 C46.317,19.483 44,24.683 44,30.75 C44,35.65 45.883,40.066 49.65,44 C53.383,47.9 59.15,50.966 66.95,53.2 C77.883,56.367 86.233,61.7 92,69.2 C97.133,75.833 100,83.283 100.6,91.55 C101.199,91.365 101.791,91.132 102.375,90.85 Z M71.95,49.05 C71.95,49.35 72.117,49.5 72.45,49.5 C72.483,49.5 75.117,47.066 80.35,42.2 C80.35,41.533 78.95,42.45 76.15,44.95 C73.35,47.483 71.95,48.85 71.95,49.05 Z M74.15,50.8 C74.15,50.533 74.017,50.4 73.75,50.4 C73.45,50.4 73.183,50.55 72.95,50.85 C72.416,50.817 72.033,50.884 71.8,51.05 C71.7,51.117 71.65,51.183 71.65,51.25 C71.65,51.45 71.783,51.6 72.05,51.7 L72.95,51.7 C73.75,51.4 74.15,51.1 74.15,50.8 Z M80.35,45.35 C80.35,44.583 79.9,44.583 79,45.35 C78.567,45.75 78.017,46.317 77.35,47.05 C77.117,47.217 76.633,47.667 75.9,48.4 C75.133,49.2 74.75,49.683 74.75,49.85 L74.8,50.2 C75,50.267 75.133,50.3 75.2,50.3 C75.233,50.3 76.1,49.5 77.8,47.9 C79.5,46.3 80.35,45.45 80.35,45.35 Z M124.2,78.3 L115.8,85.7 C116.3,85.967 116.667,86.349 116.9,86.849 L125.2,79.45 C125.266,79.116 125.217,78.849 125.05,78.649 C124.883,78.517 124.6,78.399 124.2,78.3 Z M123.75,78.05 L123.55,77.85 L116.15,83.85 L116.6,84.4 L123.75,78.05 Z M91.85,99.899 C89.65,99.333 87.617,98.649 85.75,97.849 C81.55,96.149 76.333,93.183 70.1,88.95 L59.85,82 C55.517,79.233 51.567,77.166 48,75.8 C44.4,74.467 40.867,73.8 37.4,73.8 L35.9,73.8 C27.067,74.267 20.2,77.583 15.3,83.75 C10.734,89.45 8.45,96.184 8.45,103.95 C8.45,112.683 11.95,119.516 18.95,124.45 C25.916,129.416 35.467,131.899 47.6,131.899 C57.133,131.899 65.166,130.2 71.7,126.799 C78.2,123.399 83.25,118.899 86.85,113.299 C89.55,109.033 91.217,104.566 91.85,99.899 Z"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.8 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 |
|
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 |
@@ -1,3 +0,0 @@
|
|||||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M11.63 3.625C11.63 4.27911 11.2435 4.84296 10.6865 5.10064L14 8L17.2622 7.34755C17.0968 7.10642 17 6.81452 17 6.5C17 5.67157 17.6716 5 18.5 5C19.3284 5 20 5.67157 20 6.5C20 7.31157 19.3555 7.9726 18.5504 7.99917L15.0307 15.8207C14.7077 16.5384 13.9939 17 13.2068 17H6.79317C6.00615 17 5.29229 16.5384 4.96933 15.8207L1.44963 7.99917C0.64452 7.9726 0 7.31157 0 6.5C0 5.67157 0.671573 5 1.5 5C2.32843 5 3 5.67157 3 6.5C3 6.81452 2.9032 7.10642 2.73777 7.34755L6 8L9.31702 5.09761C8.76346 4.83855 8.38 4.27656 8.38 3.625C8.38 2.72754 9.10754 2 10.005 2C10.9025 2 11.63 2.72754 11.63 3.625Z" fill="currentColor"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 722 B |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
@@ -1,13 +1,40 @@
|
|||||||
(() => {
|
(() => {
|
||||||
function convertModuleLinks(html) {
|
function convertModuleLinks(html) {
|
||||||
html = html
|
html = html
|
||||||
.replace(/@shoelace-style\/shoelace/g, `https://esm.sh/@shoelace-style/shoelace@${waVersion}`)
|
.replace(/@shoelace-style\/shoelace/g, `https://esm.sh/@shoelace-style/shoelace@${shoelaceVersion}`)
|
||||||
.replace(/from 'react'/g, `from 'https://esm.sh/react@${reactVersion}'`)
|
.replace(/from 'react'/g, `from 'https://esm.sh/react@${reactVersion}'`)
|
||||||
.replace(/from "react"/g, `from "https://esm.sh/react@${reactVersion}"`);
|
.replace(/from "react"/g, `from "https://esm.sh/react@${reactVersion}"`);
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAdjacentExample(name, pre) {
|
||||||
|
let currentPre = pre.nextElementSibling;
|
||||||
|
|
||||||
|
while (currentPre?.tagName.toLowerCase() === 'pre') {
|
||||||
|
if (currentPre?.getAttribute('data-lang').split(' ').includes(name)) {
|
||||||
|
return currentPre;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPre = currentPre.nextElementSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function runScript(script) {
|
||||||
|
const newScript = document.createElement('script');
|
||||||
|
|
||||||
|
if (script.type === 'module') {
|
||||||
|
newScript.type = 'module';
|
||||||
|
newScript.textContent = script.innerHTML;
|
||||||
|
} else {
|
||||||
|
newScript.appendChild(document.createTextNode(`(() => { ${script.innerHTML} })();`));
|
||||||
|
}
|
||||||
|
|
||||||
|
script.parentNode.replaceChild(newScript, script);
|
||||||
|
}
|
||||||
|
|
||||||
function getFlavor() {
|
function getFlavor() {
|
||||||
return sessionStorage.getItem('flavor') || 'html';
|
return sessionStorage.getItem('flavor') || 'html';
|
||||||
}
|
}
|
||||||
@@ -37,7 +64,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const waVersion = document.querySelector("meta[name='wa-version']").getAttribute('content');
|
const shoelaceVersion = document.documentElement.getAttribute('data-shoelace-version');
|
||||||
const reactVersion = '18.2.0';
|
const reactVersion = '18.2.0';
|
||||||
const cdndir = 'cdn';
|
const cdndir = 'cdn';
|
||||||
const npmdir = 'dist';
|
const npmdir = 'dist';
|
||||||
@@ -45,8 +72,8 @@
|
|||||||
let count = 1;
|
let count = 1;
|
||||||
|
|
||||||
// We need the version to open
|
// We need the version to open
|
||||||
if (!waVersion) {
|
if (!shoelaceVersion) {
|
||||||
throw new Error('The data-wa-version attribute is missing from <html>.');
|
throw new Error('The data-shoelace-version attribute is missing from <html>.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync flavor UI on page load
|
// Sync flavor UI on page load
|
||||||
@@ -139,6 +166,9 @@
|
|||||||
const htmlExample = codeBlock.querySelector('.code-preview__source--html > pre > code')?.textContent;
|
const htmlExample = codeBlock.querySelector('.code-preview__source--html > pre > code')?.textContent;
|
||||||
const reactExample = codeBlock.querySelector('.code-preview__source--react > pre > code')?.textContent;
|
const reactExample = codeBlock.querySelector('.code-preview__source--react > pre > code')?.textContent;
|
||||||
const isReact = flavor === 'react' && typeof reactExample === 'string';
|
const isReact = flavor === 'react' && typeof reactExample === 'string';
|
||||||
|
const theme = document.documentElement.classList.contains('sl-theme-dark') ? 'dark' : 'light';
|
||||||
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
const isDark = theme === 'dark' || (theme === 'auto' && prefersDark);
|
||||||
const editors = isReact ? '0010' : '1000';
|
const editors = isReact ? '0010' : '1000';
|
||||||
let htmlTemplate = '';
|
let htmlTemplate = '';
|
||||||
let jsTemplate = '';
|
let jsTemplate = '';
|
||||||
@@ -152,7 +182,7 @@
|
|||||||
// HTML templates
|
// HTML templates
|
||||||
if (!isReact) {
|
if (!isReact) {
|
||||||
htmlTemplate =
|
htmlTemplate =
|
||||||
`<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${waVersion}/${cdndir}/autoloader.js"></script>\n` +
|
`<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/shoelace.js"></script>\n` +
|
||||||
`\n${htmlExample}`;
|
`\n${htmlExample}`;
|
||||||
jsTemplate = '';
|
jsTemplate = '';
|
||||||
}
|
}
|
||||||
@@ -163,10 +193,10 @@
|
|||||||
jsTemplate =
|
jsTemplate =
|
||||||
`import React from 'https://esm.sh/react@${reactVersion}';\n` +
|
`import React from 'https://esm.sh/react@${reactVersion}';\n` +
|
||||||
`import ReactDOM from 'https://esm.sh/react-dom@${reactVersion}';\n` +
|
`import ReactDOM from 'https://esm.sh/react-dom@${reactVersion}';\n` +
|
||||||
`import { setBasePath } from 'https://esm.sh/@shoelace-style/shoelace@${waVersion}/${cdndir}/utilities/base-path';\n` +
|
`import { setBasePath } from 'https://esm.sh/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/utilities/base-path';\n` +
|
||||||
`\n` +
|
`\n` +
|
||||||
`// Set the base path for Web Awesome assets\n` +
|
`// Set the base path for Shoelace assets\n` +
|
||||||
`setBasePath('https://esm.sh/@shoelace-style/shoelace@${waVersion}/${npmdir}/')\n` +
|
`setBasePath('https://esm.sh/@shoelace-style/shoelace@${shoelaceVersion}/${npmdir}/')\n` +
|
||||||
`\n${convertModuleLinks(reactExample)}\n` +
|
`\n${convertModuleLinks(reactExample)}\n` +
|
||||||
`\n` +
|
`\n` +
|
||||||
`ReactDOM.render(<App />, document.getElementById('root'));`;
|
`ReactDOM.render(<App />, document.getElementById('root'));`;
|
||||||
@@ -174,22 +204,25 @@
|
|||||||
|
|
||||||
// CSS templates
|
// CSS templates
|
||||||
cssTemplate =
|
cssTemplate =
|
||||||
`@import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${waVersion}/${cdndir}/themes/default.css';\n` +
|
`@import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/themes/${
|
||||||
|
isDark ? 'dark' : 'light'
|
||||||
|
}.css';\n` +
|
||||||
'\n' +
|
'\n' +
|
||||||
'body {\n' +
|
'body {\n' +
|
||||||
' font: var(--wa-font-size-root) sans-serif;\n' +
|
' font: 16px sans-serif;\n' +
|
||||||
' background-color: var(--wa-color-surface-default);\n' +
|
' background-color: var(--sl-color-neutral-0);\n' +
|
||||||
' color: var(--wa-color-text-normal);\n' +
|
' color: var(--sl-color-neutral-900);\n' +
|
||||||
' padding: var(--wa-space-m);\n' +
|
' padding: 1rem;\n' +
|
||||||
'}';
|
'}';
|
||||||
|
|
||||||
// Docs: https://blog.codepen.io/documentation/prefill/
|
// Docs: https://blog.codepen.io/documentation/prefill/
|
||||||
const data = {
|
const data = {
|
||||||
title: '',
|
title: '',
|
||||||
description: '',
|
description: '',
|
||||||
tags: ['web awesome', 'web components'],
|
tags: ['shoelace', 'web components'],
|
||||||
editors,
|
editors,
|
||||||
head: `<meta name="viewport" content="width=device-width">`,
|
head: `<meta name="viewport" content="width=device-width">`,
|
||||||
|
html_classes: `sl-theme-${isDark ? 'dark' : 'light'}`,
|
||||||
css_external: ``,
|
css_external: ``,
|
||||||
js_external: ``,
|
js_external: ``,
|
||||||
js_module: true,
|
js_module: true,
|
||||||
@@ -213,5 +246,4 @@
|
|||||||
|
|
||||||
// Set the initial flavor
|
// Set the initial flavor
|
||||||
window.addEventListener('turbo:load', syncFlavor);
|
window.addEventListener('turbo:load', syncFlavor);
|
||||||
syncFlavor();
|
|
||||||
})();
|
})();
|
||||||
298
docs/assets/scripts/docs.js
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
//
|
||||||
|
// Sidebar
|
||||||
|
//
|
||||||
|
// When the sidebar is hidden, we apply the inert attribute to prevent focus from reaching it. Due to the many states
|
||||||
|
// the sidebar can have (e.g. static, hidden, expanded), we test for visibility by checking to see if it's placed
|
||||||
|
// offscreen or not. Then, on resize/transition we make sure to update the attribute accordingly.
|
||||||
|
//
|
||||||
|
(() => {
|
||||||
|
function getSidebar() {
|
||||||
|
return document.getElementById('sidebar');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSidebarOpen() {
|
||||||
|
return document.documentElement.classList.contains('sidebar-open');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSidebarVisible() {
|
||||||
|
return getSidebar().getBoundingClientRect().x >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSidebar(force) {
|
||||||
|
const isOpen = typeof force === 'boolean' ? force : !isSidebarOpen();
|
||||||
|
return document.documentElement.classList.toggle('sidebar-open', isOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateInert() {
|
||||||
|
getSidebar().inert = !isSidebarVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle the menu
|
||||||
|
document.addEventListener('click', event => {
|
||||||
|
const menuToggle = event.target.closest('#menu-toggle');
|
||||||
|
if (!menuToggle) return;
|
||||||
|
toggleSidebar();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the sidebar's inert state when the window resizes and when the sidebar transitions
|
||||||
|
window.addEventListener('resize', () => toggleSidebar(false));
|
||||||
|
|
||||||
|
document.addEventListener('transitionend', event => {
|
||||||
|
const sidebar = event.target.closest('#sidebar');
|
||||||
|
if (!sidebar) return;
|
||||||
|
updateInert();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close when a menu item is selected on mobile
|
||||||
|
document.addEventListener('click', event => {
|
||||||
|
const sidebar = event.target.closest('#sidebar');
|
||||||
|
const link = event.target.closest('a');
|
||||||
|
if (!sidebar || !link) return;
|
||||||
|
|
||||||
|
if (isSidebarOpen()) {
|
||||||
|
toggleSidebar();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close when open and escape is pressed
|
||||||
|
document.addEventListener('keydown', event => {
|
||||||
|
if (event.key === 'Escape' && isSidebarOpen()) {
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
toggleSidebar();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close when clicking outside of the sidebar
|
||||||
|
document.addEventListener('mousedown', event => {
|
||||||
|
if (isSidebarOpen() & !event.target?.closest('#sidebar, #menu-toggle')) {
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
toggleSidebar();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateInert();
|
||||||
|
})();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Theme selector
|
||||||
|
//
|
||||||
|
(() => {
|
||||||
|
function getTheme() {
|
||||||
|
return localStorage.getItem('theme') || 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDark() {
|
||||||
|
if (theme === 'auto') {
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
}
|
||||||
|
return theme === 'dark';
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTheme(newTheme) {
|
||||||
|
theme = newTheme;
|
||||||
|
localStorage.setItem('theme', theme);
|
||||||
|
|
||||||
|
// Update the UI
|
||||||
|
updateSelection();
|
||||||
|
|
||||||
|
// Toggle the dark mode class
|
||||||
|
document.documentElement.classList.toggle('sl-theme-dark', isDark());
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSelection() {
|
||||||
|
const menu = document.querySelector('#theme-selector sl-menu');
|
||||||
|
if (!menu) return;
|
||||||
|
[...menu.querySelectorAll('sl-menu-item')].map(item => (item.checked = item.getAttribute('value') === theme));
|
||||||
|
}
|
||||||
|
|
||||||
|
let theme = getTheme();
|
||||||
|
|
||||||
|
// Selection is not preserved when changing page, so update when opening dropdown
|
||||||
|
document.addEventListener('sl-show', event => {
|
||||||
|
const themeSelector = event.target.closest('#theme-selector');
|
||||||
|
if (!themeSelector) return;
|
||||||
|
updateSelection();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for selections
|
||||||
|
document.addEventListener('sl-select', event => {
|
||||||
|
const menu = event.target.closest('#theme-selector sl-menu');
|
||||||
|
if (!menu) return;
|
||||||
|
setTheme(event.detail.item.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the theme when the preference changes
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => setTheme(theme));
|
||||||
|
|
||||||
|
// Toggle with backslash
|
||||||
|
document.addEventListener('keydown', event => {
|
||||||
|
if (
|
||||||
|
event.key === '\\' &&
|
||||||
|
!event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
setTheme(isDark() ? 'light' : 'dark');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set the initial theme and sync the UI
|
||||||
|
setTheme(theme);
|
||||||
|
})();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Open details when printing
|
||||||
|
//
|
||||||
|
(() => {
|
||||||
|
const detailsOpenOnPrint = new Set();
|
||||||
|
|
||||||
|
window.addEventListener('beforeprint', () => {
|
||||||
|
detailsOpenOnPrint.clear();
|
||||||
|
document.querySelectorAll('details').forEach(details => {
|
||||||
|
if (details.open) {
|
||||||
|
detailsOpenOnPrint.add(details);
|
||||||
|
}
|
||||||
|
details.open = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('afterprint', () => {
|
||||||
|
document.querySelectorAll('details').forEach(details => {
|
||||||
|
details.open = detailsOpenOnPrint.has(details);
|
||||||
|
});
|
||||||
|
detailsOpenOnPrint.clear();
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Copy code buttons
|
||||||
|
//
|
||||||
|
(() => {
|
||||||
|
document.addEventListener('click', event => {
|
||||||
|
const button = event.target.closest('.copy-code-button');
|
||||||
|
const pre = button?.closest('pre');
|
||||||
|
const code = pre?.querySelector('code');
|
||||||
|
const copyIcon = button?.querySelector('.copy-code-button__copy-icon');
|
||||||
|
const copiedIcon = button?.querySelector('.copy-code-button__copied-icon');
|
||||||
|
|
||||||
|
if (button && code) {
|
||||||
|
navigator.clipboard.writeText(code.innerText);
|
||||||
|
copyIcon.style.display = 'none';
|
||||||
|
copiedIcon.style.display = 'inline';
|
||||||
|
button.classList.add('copy-code-button--copied');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
copyIcon.style.display = 'inline';
|
||||||
|
copiedIcon.style.display = 'none';
|
||||||
|
button.classList.remove('copy-code-button--copied');
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Smooth links
|
||||||
|
//
|
||||||
|
(() => {
|
||||||
|
document.addEventListener('click', event => {
|
||||||
|
const link = event.target.closest('a');
|
||||||
|
const id = (link?.hash ?? '').substr(1);
|
||||||
|
const isFragment = link?.hasAttribute('href') && link?.getAttribute('href').startsWith('#');
|
||||||
|
|
||||||
|
if (!link || !isFragment || link.getAttribute('data-smooth-link') === 'false') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll to the top
|
||||||
|
if (link.hash === '') {
|
||||||
|
event.preventDefault();
|
||||||
|
window.scroll({ top: 0, behavior: 'smooth' });
|
||||||
|
history.pushState(undefined, undefined, location.pathname);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll to an id
|
||||||
|
if (id) {
|
||||||
|
const target = document.getElementById(id);
|
||||||
|
|
||||||
|
if (target) {
|
||||||
|
event.preventDefault();
|
||||||
|
window.scroll({ top: target.offsetTop, behavior: 'smooth' });
|
||||||
|
history.pushState(undefined, undefined, `#${id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Table of Contents scrollspy
|
||||||
|
//
|
||||||
|
(() => {
|
||||||
|
// This will be stale if its not a function.
|
||||||
|
const getLinks = () => [...document.querySelectorAll('.content__toc a')];
|
||||||
|
const linkTargets = new WeakMap();
|
||||||
|
const visibleTargets = new WeakSet();
|
||||||
|
const observer = new IntersectionObserver(handleIntersect, { rootMargin: '0px 0px' });
|
||||||
|
let debounce;
|
||||||
|
|
||||||
|
function handleIntersect(entries) {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
// Remember which targets are visible
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
visibleTargets.add(entry.target);
|
||||||
|
} else {
|
||||||
|
visibleTargets.delete(entry.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateActiveLinks();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateActiveLinks() {
|
||||||
|
const links = getLinks();
|
||||||
|
// Find the first visible target and activate the respective link
|
||||||
|
links.find(link => {
|
||||||
|
const target = linkTargets.get(link);
|
||||||
|
|
||||||
|
if (target && visibleTargets.has(target)) {
|
||||||
|
links.forEach(el => el.classList.toggle('active', el === link));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe link targets
|
||||||
|
function observeLinks() {
|
||||||
|
getLinks().forEach(link => {
|
||||||
|
const hash = link.hash.slice(1);
|
||||||
|
const target = hash ? document.querySelector(`.content__body #${hash}`) : null;
|
||||||
|
|
||||||
|
if (target) {
|
||||||
|
linkTargets.set(link, target);
|
||||||
|
observer.observe(target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
observeLinks();
|
||||||
|
|
||||||
|
document.addEventListener('turbo:load', updateActiveLinks);
|
||||||
|
document.addEventListener('turbo:load', observeLinks);
|
||||||
|
})();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Show custom versions in the sidebar
|
||||||
|
//
|
||||||
|
(() => {
|
||||||
|
function updateVersion() {
|
||||||
|
const el = document.querySelector('.sidebar-version');
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
|
if (location.hostname === 'next.shoelace.style') el.textContent = 'Next';
|
||||||
|
if (location.hostname === 'localhost') el.textContent = 'Development';
|
||||||
|
}
|
||||||
|
|
||||||
|
updateVersion();
|
||||||
|
|
||||||
|
document.addEventListener('turbo:load', updateVersion);
|
||||||
|
})();
|
||||||
376
docs/assets/scripts/search.js
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
(() => {
|
||||||
|
// Append the search dialog to the body
|
||||||
|
const siteSearch = document.createElement('div');
|
||||||
|
const scrollbarWidth = Math.abs(window.innerWidth - document.documentElement.clientWidth);
|
||||||
|
|
||||||
|
siteSearch.classList.add('search');
|
||||||
|
siteSearch.innerHTML = `
|
||||||
|
<div class="search__overlay"></div>
|
||||||
|
<dialog id="search-dialog" class="search__dialog">
|
||||||
|
<div class="search__content">
|
||||||
|
<div class="search__header">
|
||||||
|
<div id="search-combobox" class="search__input-wrapper">
|
||||||
|
<sl-icon name="search"></sl-icon>
|
||||||
|
<input
|
||||||
|
id="search-input"
|
||||||
|
class="search__input"
|
||||||
|
type="search"
|
||||||
|
placeholder="Search"
|
||||||
|
autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
enterkeyhint="go"
|
||||||
|
spellcheck="false"
|
||||||
|
maxlength="100"
|
||||||
|
role="combobox"
|
||||||
|
aria-autocomplete="list"
|
||||||
|
aria-expanded="true"
|
||||||
|
aria-controls="search-listbox"
|
||||||
|
aria-haspopup="listbox"
|
||||||
|
aria-activedescendant
|
||||||
|
>
|
||||||
|
<button type="button" class="search__clear-button" aria-label="Clear entry" tabindex="-1" hidden>
|
||||||
|
<sl-icon name="x-circle-fill"></sl-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="search__body">
|
||||||
|
<ul
|
||||||
|
id="search-listbox"
|
||||||
|
class="search__results"
|
||||||
|
role="listbox"
|
||||||
|
aria-label="Search results"
|
||||||
|
></ul>
|
||||||
|
<div class="search__empty">No matching pages</div>
|
||||||
|
</div>
|
||||||
|
<footer class="search__footer">
|
||||||
|
<small><kbd>↑</kbd> <kbd>↓</kbd> Navigate</small>
|
||||||
|
<small><kbd>↲</kbd> Select</small>
|
||||||
|
<small><kbd>Esc</kbd> Close</small>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const overlay = siteSearch.querySelector('.search__overlay');
|
||||||
|
const dialog = siteSearch.querySelector('.search__dialog');
|
||||||
|
const input = siteSearch.querySelector('.search__input');
|
||||||
|
const clearButton = siteSearch.querySelector('.search__clear-button');
|
||||||
|
const results = siteSearch.querySelector('.search__results');
|
||||||
|
const version = document.documentElement.getAttribute('data-shoelace-version');
|
||||||
|
const 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">
|
||||||
|
<sl-icon name="${icon}"></sl-icon>
|
||||||
|
</div>
|
||||||
|
<div class="search__result__details">
|
||||||
|
<div class="search__result-title"></div>
|
||||||
|
<div class="search__result-description"></div>
|
||||||
|
<div class="search__result-url"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
a.querySelector('.search__result-title').textContent = displayTitle;
|
||||||
|
a.querySelector('.search__result-description').textContent = displayDescription;
|
||||||
|
a.querySelector('.search__result-url').textContent = displayUrl;
|
||||||
|
|
||||||
|
li.appendChild(a);
|
||||||
|
results.appendChild(li);
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// Ignore query errors as the user types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the search dialog when clicking on data-plugin="search"
|
||||||
|
document.addEventListener('click', event => {
|
||||||
|
const searchButton = event.target.closest('[data-plugin="search"]');
|
||||||
|
if (searchButton) {
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show the search dialog when slash (or CMD+K) is pressed and focus is not inside a form element
|
||||||
|
document.addEventListener('keydown', event => {
|
||||||
|
if (
|
||||||
|
!isShowing &&
|
||||||
|
(event.key === '/' || (event.key === 'k' && (event.metaKey || event.ctrlKey))) &&
|
||||||
|
!event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
29
docs/assets/scripts/turbo.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import * as Turbo from 'https://cdn.jsdelivr.net/npm/@hotwired/turbo@7.3.0/+esm';
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
if (!window.scrollPositions) {
|
||||||
|
window.scrollPositions = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function preserveScroll() {
|
||||||
|
document.querySelectorAll('[data-preserve-scroll').forEach(element => {
|
||||||
|
scrollPositions[element.id] = element.scrollTop;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreScroll(event) {
|
||||||
|
document.querySelectorAll('[data-preserve-scroll').forEach(element => {
|
||||||
|
element.scrollTop = scrollPositions[element.id];
|
||||||
|
});
|
||||||
|
|
||||||
|
if (event.detail && event.detail.newBody) {
|
||||||
|
event.detail.newBody.querySelectorAll('[data-preserve-scroll').forEach(element => {
|
||||||
|
element.scrollTop = scrollPositions[element.id];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('turbo:before-cache', preserveScroll);
|
||||||
|
window.addEventListener('turbo:before-render', restoreScroll);
|
||||||
|
window.addEventListener('turbo:render', restoreScroll);
|
||||||
|
})();
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
/* Interactive code blocks */
|
/* Interactive code blocks */
|
||||||
.code-preview {
|
.code-preview {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: var(--wa-corners-s);
|
border-radius: 3px;
|
||||||
background-color: var(--wa-color-neutral-fill-subtle);
|
background-color: var(--sl-color-neutral-50);
|
||||||
margin-bottom: var(--wa-space-xl);
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-preview__preview {
|
.code-preview__preview {
|
||||||
position: relative;
|
position: relative;
|
||||||
border: var(--wa-border-style) var(--wa-border-width-s) var(--wa-color-surface-border);
|
border: solid 1px var(--sl-color-neutral-200);
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
border-top-left-radius: var(--wa-corners-s);
|
border-top-left-radius: 3px;
|
||||||
border-top-right-radius: var(--wa-corners-s);
|
border-top-right-radius: 3px;
|
||||||
background-color: var(--wa-color-surface-default);
|
background-color: var(--sl-color-neutral-0);
|
||||||
min-width: 20rem;
|
min-width: 20rem;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: var(--wa-space-xl) var(--wa-space-3xl) var(--wa-space-xl) var(--wa-space-xl);
|
padding: 1.5rem 3.25rem 1.5rem 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Block the preview while dragging to prevent iframes from intercepting drag events */
|
/* Block the preview while dragging to prevent iframes from intercepting drag events */
|
||||||
@@ -39,17 +39,17 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 1.75rem;
|
width: 1.75rem;
|
||||||
font-size: var(--wa-font-size-xs);
|
font-size: 20px;
|
||||||
color: var(--wa-color-text-quiet);
|
color: var(--sl-color-neutral-600);
|
||||||
background-color: var(--wa-color-surface-default);
|
background-color: var(--sl-color-neutral-0);
|
||||||
border-left: var(--wa-border-style) var(--wa-border-width-s) var(--wa-color-surface-border);
|
border-left: solid 1px var(--sl-color-neutral-200);
|
||||||
border-top-right-radius: var(--wa-corners-s);
|
border-top-right-radius: 3px;
|
||||||
cursor: ew-resize;
|
cursor: ew-resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 600px) {
|
@media screen and (max-width: 600px) {
|
||||||
.code-preview__preview {
|
.code-preview__preview {
|
||||||
padding-right: var(--wa-space-xl);
|
padding-right: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-preview__resizer {
|
.code-preview__resizer {
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.code-preview__source {
|
.code-preview__source {
|
||||||
border: var(--wa-border-style) var(--wa-border-width-s) var(--wa-color-surface-border);
|
border: solid 1px var(--sl-color-neutral-200);
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
display: none;
|
display: none;
|
||||||
@@ -74,9 +74,9 @@
|
|||||||
|
|
||||||
.code-preview__buttons {
|
.code-preview__buttons {
|
||||||
position: relative;
|
position: relative;
|
||||||
border: var(--wa-border-style) var(--wa-border-width-s) var(--wa-color-surface-border);
|
border: solid 1px var(--sl-color-neutral-200);
|
||||||
border-bottom-left-radius: var(--wa-corners-s);
|
border-bottom-left-radius: 3px;
|
||||||
border-bottom-right-radius: var(--wa-corners-s);
|
border-bottom-right-radius: 3px;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,18 +86,18 @@
|
|||||||
min-width: 2.5rem;
|
min-width: 2.5rem;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: var(--wa-color-surface-default);
|
background: var(--sl-color-neutral-0);
|
||||||
font: inherit;
|
font: inherit;
|
||||||
font-size: var(--wa-font-size-xs);
|
font-size: 0.7rem;
|
||||||
font-weight: var(--wa-font-weight-normal);
|
font-weight: 500;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--wa-color-text-quiet);
|
color: var(--sl-color-neutral-600);
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-preview__button:not(:last-of-type) {
|
.code-preview__button:not(:last-of-type) {
|
||||||
border-right: var(--wa-border-style) var(--wa-border-width-s) var(--wa-color-surface-border);
|
border-right: solid 1px var(--sl-color-neutral-200);
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-preview__button--html,
|
.code-preview__button--html,
|
||||||
@@ -109,8 +109,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.code-preview__button--selected {
|
.code-preview__button--selected {
|
||||||
font-weight: var(--wa-font-weight-heavy);
|
font-weight: 700;
|
||||||
color: var(--wa-color-brand-text-on-surface);
|
color: var(--sl-color-primary-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-preview__button--codepen {
|
.code-preview__button--codepen {
|
||||||
@@ -120,25 +120,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.code-preview__button:first-of-type {
|
.code-preview__button:first-of-type {
|
||||||
border-bottom-left-radius: var(--wa-corners-s);
|
border-bottom-left-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-preview__button:last-of-type {
|
.code-preview__button:last-of-type {
|
||||||
border-bottom-right-radius: var(--wa-corners-s);
|
border-bottom-right-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-preview__button:hover,
|
.code-preview__button:hover,
|
||||||
.code-preview__button:active {
|
.code-preview__button:active {
|
||||||
box-shadow: 0 0 0 var(--wa-border-width-s) var(--wa-color-brand-border-subtle);
|
box-shadow: 0 0 0 1px var(--sl-color-primary-400);
|
||||||
border-right-color: transparent;
|
border-right-color: transparent;
|
||||||
background-color: var(--wa-color-brand-fill-subtle);
|
background-color: var(--sl-color-primary-50);
|
||||||
color: var(--wa-color-brand-text-on-surface);
|
color: var(--sl-color-primary-600);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-preview__button:focus-visible {
|
.code-preview__button:focus-visible {
|
||||||
outline: none;
|
outline: none;
|
||||||
outline: var(--wa-focus-ring);
|
outline: var(--sl-focus-ring);
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: var(--wa-color-text-quiet);
|
color: var(--sl-color-neutral-600);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
1422
docs/assets/styles/docs.css
Normal file
@@ -1,31 +1,34 @@
|
|||||||
/* Search plugin */
|
/* Search plugin */
|
||||||
:root {
|
:root,
|
||||||
--docs-search-box-background: var(--wa-form-controls-background);
|
:root.sl-theme-dark {
|
||||||
--docs-search-box-border-width: var(--wa-form-controls-border-width);
|
--docs-search-box-background: var(--sl-color-neutral-0);
|
||||||
--docs-search-box-border-color: var(--wa-form-controls-resting-color);
|
--docs-search-box-border-width: 1px;
|
||||||
--docs-search-box-color: var(--wa-form-controls-placeholder-color);
|
--docs-search-box-border-color: var(--sl-color-neutral-300);
|
||||||
|
--docs-search-box-color: var(--sl-color-neutral-600);
|
||||||
--docs-search-dialog-background: var(--wa-color-surface-raised);
|
--docs-search-dialog-background: var(--sl-color-neutral-0);
|
||||||
--docs-search-border-width: var(--wa-border-width-s);
|
--docs-search-border-width: var(--docs-border-width);
|
||||||
--docs-search-border-color: var(--wa-color-surface-border);
|
--docs-search-border-color: var(--docs-border-color);
|
||||||
--docs-search-text-color: var(--wa-color-text-normal);
|
--docs-search-text-color: var(--sl-color-neutral-900);
|
||||||
--docs-search-text-color-muted: var(--wa-color-text-quiet);
|
--docs-search-text-color-muted: var(--sl-color-neutral-500);
|
||||||
--docs-search-font-weight-normal: var(--wa-font-weight-normal);
|
--docs-search-font-weight-normal: var(--sl-font-weight-normal);
|
||||||
--docs-search-font-weight-semibold: var(--wa-font-weight-medium);
|
--docs-search-font-weight-semibold: var(--sl-font-weight-semibold);
|
||||||
--docs-search-border-radius: calc(2 * var(--wa-corners-s));
|
--docs-search-border-radius: calc(2 * var(--docs-border-radius));
|
||||||
|
--docs-search-accent-color: var(--sl-color-primary-600);
|
||||||
--docs-search-accent-color: var(--wa-color-brand-text-on-surface);
|
--docs-search-icon-color: var(--sl-color-neutral-500);
|
||||||
--docs-search-icon-color: var(--wa-color-neutral-spot);
|
--docs-search-icon-color-active: var(--sl-color-neutral-600);
|
||||||
--docs-search-icon-color-active: color-mix(in lch, var(--wa-color-neutral-spot), 8% black);
|
--docs-search-shadow: var(--docs-shadow-x-large);
|
||||||
--docs-search-shadow: var(--wa-shadow-level-3);
|
--docs-search-result-background-hover: var(--sl-color-neutral-100);
|
||||||
--docs-search-result-background-hover: var(--wa-color-neutral-fill-highlight);
|
--docs-search-result-color-hover: var(--sl-color-neutral-900);
|
||||||
--docs-search-result-color-hover: var(--wa-color-neutral-text-on-fill);
|
--docs-search-result-background-active: var(--sl-color-primary-600);
|
||||||
--docs-search-result-background-active: var(--wa-color-brand-spot);
|
--docs-search-result-color-active: var(--sl-color-neutral-0);
|
||||||
--docs-search-result-color-active: var(--wa-color-brand-text-on-spot);
|
--docs-search-focus-ring: var(--sl-focus-ring);
|
||||||
--docs-search-focus-ring: var(--wa-focus-ring);
|
|
||||||
--docs-search-overlay-background: rgb(0 0 0 / 0.33);
|
--docs-search-overlay-background: rgb(0 0 0 / 0.33);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root.sl-theme-dark {
|
||||||
|
--docs-search-overlay-background: rgb(71 71 71 / 0.33);
|
||||||
|
}
|
||||||
|
|
||||||
body.search-visible {
|
body.search-visible {
|
||||||
padding-right: var(--docs-search-scroll-lock-size) !important;
|
padding-right: var(--docs-search-scroll-lock-size) !important;
|
||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
@@ -44,7 +47,7 @@ body.search-visible {
|
|||||||
font: inherit;
|
font: inherit;
|
||||||
color: var(--docs-search-box-color);
|
color: var(--docs-search-box-color);
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
margin: var(--wa-space-l) 0;
|
margin: var(--sl-spacing-large) 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +150,7 @@ body.search-visible {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search__input-wrapper wa-icon {
|
.search__input-wrapper sl-icon {
|
||||||
width: 1.5rem;
|
width: 1.5rem;
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
@@ -169,7 +172,7 @@ body.search-visible {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search__clear-button:active wa-icon {
|
.search__clear-button:active sl-icon {
|
||||||
color: var(--docs-search-icon-color-active);
|
color: var(--docs-search-icon-color-active);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +276,7 @@ body.search-visible {
|
|||||||
color: var(--docs-search-text-color-muted);
|
color: var(--docs-search-text-color-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.search__result-icon wa-icon {
|
.search__result-icon sl-icon {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
import * as path from 'node:path';
|
|
||||||
import * as url from 'node:url';
|
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import starlight from '@astrojs/starlight';
|
|
||||||
// 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 GithubAutolink from './src/plugins/github-autolink.ts';
|
|
||||||
import rehypeExternalLinks from 'rehype-external-links';
|
|
||||||
import remarkCodeHighlighter from './src/plugins/prism';
|
|
||||||
|
|
||||||
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
|
|
||||||
},
|
|
||||||
vite: {
|
|
||||||
server: {
|
|
||||||
watch: {
|
|
||||||
ignored: ['./public/pagefind/**/*.*'] // HERE
|
|
||||||
}
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
FullReload([
|
|
||||||
path.relative(__dirname, '../dist/custom-elements.json')
|
|
||||||
// path.relative(__dirname, './public/**/*.*')
|
|
||||||
])
|
|
||||||
]
|
|
||||||
},
|
|
||||||
outDir: '../_site',
|
|
||||||
site: 'https://shoelace.style',
|
|
||||||
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: 'Patterns', autogenerate: { directory: 'patterns' } },
|
|
||||||
{
|
|
||||||
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'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]
|
|
||||||
});
|
|
||||||
239
docs/eleventy.config.cjs
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
/* eslint-disable no-invalid-this */
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const lunr = require('lunr');
|
||||||
|
const { capitalCase } = require('change-case');
|
||||||
|
const { JSDOM } = require('jsdom');
|
||||||
|
const { customElementsManifest, getAllComponents } = require('./_utilities/cem.cjs');
|
||||||
|
const shoelaceFlavoredMarkdown = require('./_utilities/markdown.cjs');
|
||||||
|
const activeLinks = require('./_utilities/active-links.cjs');
|
||||||
|
const anchorHeadings = require('./_utilities/anchor-headings.cjs');
|
||||||
|
const codePreviews = require('./_utilities/code-previews.cjs');
|
||||||
|
const copyCodeButtons = require('./_utilities/copy-code-buttons.cjs');
|
||||||
|
const externalLinks = require('./_utilities/external-links.cjs');
|
||||||
|
const highlightCodeBlocks = require('./_utilities/highlight-code.cjs');
|
||||||
|
const tableOfContents = require('./_utilities/table-of-contents.cjs');
|
||||||
|
const prettier = require('./_utilities/prettier.cjs');
|
||||||
|
const scrollingTables = require('./_utilities/scrolling-tables.cjs');
|
||||||
|
const typography = require('./_utilities/typography.cjs');
|
||||||
|
const replacer = require('./_utilities/replacer.cjs');
|
||||||
|
|
||||||
|
const assetsDir = 'assets';
|
||||||
|
const cdndir = 'cdn';
|
||||||
|
const npmdir = 'dist';
|
||||||
|
const allComponents = getAllComponents();
|
||||||
|
let hasBuiltSearchIndex = false;
|
||||||
|
|
||||||
|
module.exports = function (eleventyConfig) {
|
||||||
|
//
|
||||||
|
// Global data
|
||||||
|
//
|
||||||
|
eleventyConfig.addGlobalData('baseUrl', 'https://shoelace.style/'); // the production URL
|
||||||
|
eleventyConfig.addGlobalData('layout', 'default'); // make 'default' the default layout
|
||||||
|
eleventyConfig.addGlobalData('toc', true); // enable the table of contents
|
||||||
|
eleventyConfig.addGlobalData('meta', {
|
||||||
|
title: 'Shoelace',
|
||||||
|
description: 'A forward-thinking library of web components.',
|
||||||
|
image: 'images/og-image.png',
|
||||||
|
version: customElementsManifest.package.version,
|
||||||
|
components: allComponents,
|
||||||
|
cdndir,
|
||||||
|
npmdir
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Layout aliases
|
||||||
|
//
|
||||||
|
eleventyConfig.addLayoutAlias('default', 'default.njk');
|
||||||
|
|
||||||
|
//
|
||||||
|
// Copy assets
|
||||||
|
//
|
||||||
|
eleventyConfig.addPassthroughCopy(assetsDir);
|
||||||
|
eleventyConfig.setServerPassthroughCopyBehavior('passthrough'); // emulates passthrough copy during --serve
|
||||||
|
|
||||||
|
//
|
||||||
|
// Functions
|
||||||
|
//
|
||||||
|
|
||||||
|
// Generates a URL relative to the site's root
|
||||||
|
eleventyConfig.addNunjucksGlobal('rootUrl', (value = '', absolute = false) => {
|
||||||
|
value = path.join('/', value);
|
||||||
|
return absolute ? new URL(value, eleventyConfig.globalData.baseUrl).toString() : value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generates a URL relative to the site's asset directory
|
||||||
|
eleventyConfig.addNunjucksGlobal('assetUrl', (value = '', absolute = false) => {
|
||||||
|
value = path.join(`/${assetsDir}`, value);
|
||||||
|
return absolute ? new URL(value, eleventyConfig.globalData.baseUrl).toString() : value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetches a specific component's metadata
|
||||||
|
eleventyConfig.addNunjucksGlobal('getComponent', tagName => {
|
||||||
|
const component = allComponents.find(c => c.tagName === tagName);
|
||||||
|
if (!component) {
|
||||||
|
throw new Error(
|
||||||
|
`Unable to find a component called "${tagName}". Make sure the file name is the same as the component's tag ` +
|
||||||
|
`name (minus the sl- prefix).`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return component;
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Custom markdown syntaxes
|
||||||
|
//
|
||||||
|
eleventyConfig.setLibrary('md', shoelaceFlavoredMarkdown);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Filters
|
||||||
|
//
|
||||||
|
eleventyConfig.addFilter('markdown', content => {
|
||||||
|
return shoelaceFlavoredMarkdown.render(content);
|
||||||
|
});
|
||||||
|
|
||||||
|
eleventyConfig.addFilter('markdownInline', content => {
|
||||||
|
return shoelaceFlavoredMarkdown.renderInline(content);
|
||||||
|
});
|
||||||
|
|
||||||
|
eleventyConfig.addFilter('classNameToComponentName', className => {
|
||||||
|
let name = capitalCase(className.replace(/^Sl/, ''));
|
||||||
|
if (name === 'Qr Code') name = 'QR Code'; // manual override
|
||||||
|
return name;
|
||||||
|
});
|
||||||
|
|
||||||
|
eleventyConfig.addFilter('removeSlPrefix', tagName => {
|
||||||
|
return tagName.replace(/^sl-/, '');
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Transforms
|
||||||
|
//
|
||||||
|
eleventyConfig.addTransform('html-transform', function (content) {
|
||||||
|
// Parse the template and get a Document object
|
||||||
|
const doc = new JSDOM(content, {
|
||||||
|
// We must set a default URL so links are parsed with a hostname. Let's use a bogus TLD so we can easily
|
||||||
|
// identify which ones are internal and which ones are external.
|
||||||
|
url: `https://internal/`
|
||||||
|
}).window.document;
|
||||||
|
|
||||||
|
// DOM transforms
|
||||||
|
activeLinks(doc, { pathname: this.page.url });
|
||||||
|
anchorHeadings(doc, {
|
||||||
|
within: '#content .content__body',
|
||||||
|
levels: ['h2', 'h3', 'h4', 'h5']
|
||||||
|
});
|
||||||
|
tableOfContents(doc, {
|
||||||
|
levels: ['h2', 'h3'],
|
||||||
|
container: '#content .content__toc > ul',
|
||||||
|
within: '#content .content__body'
|
||||||
|
});
|
||||||
|
codePreviews(doc);
|
||||||
|
externalLinks(doc, { target: '_blank' });
|
||||||
|
highlightCodeBlocks(doc);
|
||||||
|
scrollingTables(doc);
|
||||||
|
copyCodeButtons(doc); // must be after codePreviews + highlightCodeBlocks
|
||||||
|
typography(doc, '#content');
|
||||||
|
replacer(doc, [
|
||||||
|
{ pattern: '%VERSION%', replacement: customElementsManifest.package.version },
|
||||||
|
{ pattern: '%CDNDIR%', replacement: cdndir },
|
||||||
|
{ pattern: '%NPMDIR%', replacement: npmdir }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Serialize the Document object to an HTML string and prepend the doctype
|
||||||
|
content = `<!DOCTYPE html>\n${doc.documentElement.outerHTML}`;
|
||||||
|
|
||||||
|
// String transforms
|
||||||
|
content = prettier(content);
|
||||||
|
|
||||||
|
return content;
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Build a search index
|
||||||
|
//
|
||||||
|
eleventyConfig.on('eleventy.after', ({ results }) => {
|
||||||
|
// We only want to build the search index on the first run so all pages get indexed.
|
||||||
|
if (hasBuiltSearchIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const map = {};
|
||||||
|
const searchIndexFilename = path.join(eleventyConfig.dir.output, assetsDir, 'search.json');
|
||||||
|
const lunrInput = path.resolve('../node_modules/lunr/lunr.min.js');
|
||||||
|
const lunrOutput = path.join(eleventyConfig.dir.output, assetsDir, 'scripts/lunr.js');
|
||||||
|
const searchIndex = lunr(function () {
|
||||||
|
// The search index uses these field names extensively, so shortening them can save some serious bytes. The
|
||||||
|
// initial index file went from 468 KB => 401 KB by using single-character names!
|
||||||
|
this.ref('id'); // id
|
||||||
|
this.field('t', { boost: 50 }); // title
|
||||||
|
this.field('h', { boost: 25 }); // headings
|
||||||
|
this.field('c'); // content
|
||||||
|
|
||||||
|
results.forEach((result, index) => {
|
||||||
|
const url = path
|
||||||
|
.join('/', path.relative(eleventyConfig.dir.output, result.outputPath))
|
||||||
|
.replace(/\\/g, '/') // 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
|
||||||
|
};
|
||||||
|
};
|
||||||
19
docs/pages/404.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Page Not Found
|
||||||
|
description: "The page you were looking for couldn't be found."
|
||||||
|
permalink: 404.html
|
||||||
|
toc: false
|
||||||
|
---
|
||||||
|
|
||||||
|
<div style="text-align: center;">
|
||||||
|
|
||||||
|
# Page Not Found
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The page you were looking for couldn't be found.
|
||||||
|
|
||||||
|
Press [[/]] to search, or [head back to the homepage](/).
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -1,29 +1,30 @@
|
|||||||
---
|
---
|
||||||
title: Alert
|
meta:
|
||||||
description: Alerts are used to display important messages inline or as toast notifications.
|
title: Alert
|
||||||
layout: ../../../layouts/ComponentLayout.astro
|
description: Alerts are used to display important messages inline or as toast notifications.
|
||||||
|
layout: component
|
||||||
---
|
---
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-alert open>
|
<sl-alert open>
|
||||||
<wa-icon slot="icon" name="circle-info" variant="regular"></wa-icon>
|
<sl-icon slot="icon" name="info-circle"></sl-icon>
|
||||||
This is a standard alert. You can customize its content and even the icon.
|
This is a standard alert. You can customize its content and even the icon.
|
||||||
</wa-alert>
|
</sl-alert>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaAlert from '@shoelace-style/shoelace/dist/react/alert';
|
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
|
||||||
import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<WaAlert open>
|
<SlAlert open>
|
||||||
<WaIcon slot="icon" name="circle-info" variant="regular" />
|
<SlIcon slot="icon" name="info-circle" />
|
||||||
This is a standard alert. You can customize its content and even the icon.
|
This is a standard alert. You can customize its content and even the icon.
|
||||||
</WaAlert>
|
</SlAlert>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
:::caution
|
:::tip
|
||||||
Alerts will not be visible if the `open` attribute is not present.
|
Alerts will not be visible if the `open` attribute is not present.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
@@ -34,93 +35,93 @@ Alerts will not be visible if the `open` attribute is not present.
|
|||||||
Set the `variant` attribute to change the alert's variant.
|
Set the `variant` attribute to change the alert's variant.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-alert variant="brand" open>
|
<sl-alert variant="primary" open>
|
||||||
<wa-icon slot="icon" name="circle-info" variant="regular"></wa-icon>
|
<sl-icon slot="icon" name="info-circle"></sl-icon>
|
||||||
<strong>This is super informative</strong><br />
|
<strong>This is super informative</strong><br />
|
||||||
You can tell by how pretty the alert is.
|
You can tell by how pretty the alert is.
|
||||||
</wa-alert>
|
</sl-alert>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<wa-alert variant="success" open>
|
<sl-alert variant="success" open>
|
||||||
<wa-icon slot="icon" name="circle-check" variant="regular"></wa-icon>
|
<sl-icon slot="icon" name="check2-circle"></sl-icon>
|
||||||
<strong>Your changes have been saved</strong><br />
|
<strong>Your changes have been saved</strong><br />
|
||||||
You can safely exit the app now.
|
You can safely exit the app now.
|
||||||
</wa-alert>
|
</sl-alert>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<wa-alert variant="neutral" open>
|
<sl-alert variant="neutral" open>
|
||||||
<wa-icon slot="icon" name="gear" variant="regular"></wa-icon>
|
<sl-icon slot="icon" name="gear"></sl-icon>
|
||||||
<strong>Your settings have been updated</strong><br />
|
<strong>Your settings have been updated</strong><br />
|
||||||
Settings will take effect on next login.
|
Settings will take affect on next login.
|
||||||
</wa-alert>
|
</sl-alert>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<wa-alert variant="warning" open>
|
<sl-alert variant="warning" open>
|
||||||
<wa-icon slot="icon" name="triangle-exclamation" variant="regular"></wa-icon>
|
<sl-icon slot="icon" name="exclamation-triangle"></sl-icon>
|
||||||
<strong>Your session has ended</strong><br />
|
<strong>Your session has ended</strong><br />
|
||||||
Please login again to continue.
|
Please login again to continue.
|
||||||
</wa-alert>
|
</sl-alert>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<wa-alert variant="danger" open>
|
<sl-alert variant="danger" open>
|
||||||
<wa-icon slot="icon" name="circle-exclamation" variant="regular"></wa-icon>
|
<sl-icon slot="icon" name="exclamation-octagon"></sl-icon>
|
||||||
<strong>Your account has been deleted</strong><br />
|
<strong>Your account has been deleted</strong><br />
|
||||||
We're very sorry to see you go!
|
We're very sorry to see you go!
|
||||||
</wa-alert>
|
</sl-alert>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaAlert from '@shoelace-style/shoelace/dist/react/alert';
|
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
|
||||||
import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<WaAlert variant="brand" open>
|
<SlAlert variant="primary" open>
|
||||||
<WaIcon slot="icon" name="circle-info" variant="regular" />
|
<SlIcon slot="icon" name="info-circle" />
|
||||||
<strong>This is super informative</strong>
|
<strong>This is super informative</strong>
|
||||||
<br />
|
<br />
|
||||||
You can tell by how pretty the alert is.
|
You can tell by how pretty the alert is.
|
||||||
</WaAlert>
|
</SlAlert>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<WaAlert variant="success" open>
|
<SlAlert variant="success" open>
|
||||||
<WaIcon slot="icon" name="circle-check" variant="regular" />
|
<SlIcon slot="icon" name="check2-circle" />
|
||||||
<strong>Your changes have been saved</strong>
|
<strong>Your changes have been saved</strong>
|
||||||
<br />
|
<br />
|
||||||
You can safely exit the app now.
|
You can safely exit the app now.
|
||||||
</WaAlert>
|
</SlAlert>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<WaAlert variant="neutral" open>
|
<SlAlert variant="neutral" open>
|
||||||
<WaIcon slot="icon" name="gear" variant="regular" />
|
<SlIcon slot="icon" name="gear" />
|
||||||
<strong>Your settings have been updated</strong>
|
<strong>Your settings have been updated</strong>
|
||||||
<br />
|
<br />
|
||||||
Settings will take effect on next login.
|
Settings will take affect on next login.
|
||||||
</WaAlert>
|
</SlAlert>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<WaAlert variant="warning" open>
|
<SlAlert variant="warning" open>
|
||||||
<WaIcon slot="icon" name="triangle-exclamation" variant="regular" />
|
<SlIcon slot="icon" name="exclamation-triangle" />
|
||||||
<strong>Your session has ended</strong>
|
<strong>Your session has ended</strong>
|
||||||
<br />
|
<br />
|
||||||
Please login again to continue.
|
Please login again to continue.
|
||||||
</WaAlert>
|
</SlAlert>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<WaAlert variant="danger" open>
|
<SlAlert variant="danger" open>
|
||||||
<WaIcon slot="icon" name="circle-exclamation" variant="regular" />
|
<SlIcon slot="icon" name="exclamation-octagon" />
|
||||||
<strong>Your account has been deleted</strong>
|
<strong>Your account has been deleted</strong>
|
||||||
<br />
|
<br />
|
||||||
We're very sorry to see you go!
|
We're very sorry to see you go!
|
||||||
</WaAlert>
|
</SlAlert>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
@@ -130,14 +131,14 @@ const App = () => (
|
|||||||
Add the `closable` attribute to show a close button that will hide the alert.
|
Add the `closable` attribute to show a close button that will hide the alert.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-alert variant="brand" open closable class="alert-closable">
|
<sl-alert variant="primary" open closable class="alert-closable">
|
||||||
<wa-icon slot="icon" name="circle-info" variant="regular"></wa-icon>
|
<sl-icon slot="icon" name="info-circle"></sl-icon>
|
||||||
You can close this alert any time!
|
You can close this alert any time!
|
||||||
</wa-alert>
|
</sl-alert>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const alert = document.querySelector('.alert-closable');
|
const alert = document.querySelector('.alert-closable');
|
||||||
alert.addEventListener('wa-after-hide', () => {
|
alert.addEventListener('sl-after-hide', () => {
|
||||||
setTimeout(() => (alert.open = true), 2000);
|
setTimeout(() => (alert.open = true), 2000);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -145,8 +146,8 @@ Add the `closable` attribute to show a close button that will hide the alert.
|
|||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaAlert from '@shoelace-style/shoelace/dist/react/alert';
|
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
|
||||||
import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
@@ -157,10 +158,10 @@ const App = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WaAlert open={open} closable onWaAfterHide={handleHide}>
|
<SlAlert open={open} closable onSlAfterHide={handleHide}>
|
||||||
<WaIcon slot="icon" name="circle-info" variant="regular" />
|
<SlIcon slot="icon" name="info-circle" />
|
||||||
You can close this alert any time!
|
You can close this alert any time!
|
||||||
</WaAlert>
|
</SlAlert>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
@@ -170,16 +171,16 @@ const App = () => {
|
|||||||
Icons are optional. Simply omit the `icon` slot if you don't want them.
|
Icons are optional. Simply omit the `icon` slot if you don't want them.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-alert variant="brand" open> Nothing fancy here, just a simple alert. </wa-alert>
|
<sl-alert variant="primary" open> Nothing fancy here, just a simple alert. </sl-alert>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaAlert from '@shoelace-style/shoelace/dist/react/alert';
|
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<WaAlert variant="brand" open>
|
<SlAlert variant="primary" open>
|
||||||
Nothing fancy here, just a simple alert.
|
Nothing fancy here, just a simple alert.
|
||||||
</WaAlert>
|
</SlAlert>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -189,38 +190,38 @@ Set the `duration` attribute to automatically hide an alert after a period of ti
|
|||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<div class="alert-duration">
|
<div class="alert-duration">
|
||||||
<wa-button variant="brand">Show Alert</wa-button>
|
<sl-button variant="primary">Show Alert</sl-button>
|
||||||
|
|
||||||
<wa-alert variant="brand" duration="3000" closable>
|
<sl-alert variant="primary" duration="3000" closable>
|
||||||
<wa-icon slot="icon" name="circle-info" variant="regular"></wa-icon>
|
<sl-icon slot="icon" name="info-circle"></sl-icon>
|
||||||
This alert will automatically hide itself after three seconds, unless you interact with it.
|
This alert will automatically hide itself after three seconds, unless you interact with it.
|
||||||
</wa-alert>
|
</sl-alert>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const container = document.querySelector('.alert-duration');
|
const container = document.querySelector('.alert-duration');
|
||||||
const button = container.querySelector('wa-button');
|
const button = container.querySelector('sl-button');
|
||||||
const alert = container.querySelector('wa-alert');
|
const alert = container.querySelector('sl-alert');
|
||||||
|
|
||||||
button.addEventListener('click', () => alert.show());
|
button.addEventListener('click', () => alert.show());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.alert-duration wa-alert {
|
.alert-duration sl-alert {
|
||||||
margin-top: var(--wa-space-m);
|
margin-top: var(--sl-spacing-medium);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaAlert from '@shoelace-style/shoelace/dist/react/alert';
|
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
const css = `
|
const css = `
|
||||||
.alert-duration wa-alert {
|
.alert-duration sl-alert {
|
||||||
margin-top: var(--wa-space-m);
|
margin-top: var(--sl-spacing-medium);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -230,14 +231,14 @@ const App = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="alert-duration">
|
<div className="alert-duration">
|
||||||
<WaButton variant="brand" onClick={() => setOpen(true)}>
|
<SlButton variant="primary" onClick={() => setOpen(true)}>
|
||||||
Show Alert
|
Show Alert
|
||||||
</WaButton>
|
</SlButton>
|
||||||
|
|
||||||
<WaAlert variant="brand" duration="3000" open={open} closable onWaAfterHide={() => setOpen(false)}>
|
<SlAlert variant="primary" duration="3000" open={open} closable onSlAfterHide={() => setOpen(false)}>
|
||||||
<WaIcon slot="icon" name="circle-info" variant="regular" />
|
<SlIcon slot="icon" name="info-circle" />
|
||||||
This alert will automatically hide itself after three seconds, unless you interact with it.
|
This alert will automatically hide itself after three seconds, unless you interact with it.
|
||||||
</WaAlert>
|
</SlAlert>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>{css}</style>
|
<style>{css}</style>
|
||||||
@@ -254,49 +255,49 @@ You should always use the `closable` attribute so users can dismiss the notifica
|
|||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<div class="alert-toast">
|
<div class="alert-toast">
|
||||||
<wa-button variant="brand">Brand</wa-button>
|
<sl-button variant="primary">Primary</sl-button>
|
||||||
<wa-button variant="success">Success</wa-button>
|
<sl-button variant="success">Success</sl-button>
|
||||||
<wa-button variant="neutral">Neutral</wa-button>
|
<sl-button variant="neutral">Neutral</sl-button>
|
||||||
<wa-button variant="warning">Warning</wa-button>
|
<sl-button variant="warning">Warning</sl-button>
|
||||||
<wa-button variant="danger">Danger</wa-button>
|
<sl-button variant="danger">Danger</sl-button>
|
||||||
|
|
||||||
<wa-alert variant="brand" duration="3000" closable>
|
<sl-alert variant="primary" duration="3000" closable>
|
||||||
<wa-icon slot="icon" name="circle-info" variant="regular"></wa-icon>
|
<sl-icon slot="icon" name="info-circle"></sl-icon>
|
||||||
<strong>This is super informative</strong><br />
|
<strong>This is super informative</strong><br />
|
||||||
You can tell by how pretty the alert is.
|
You can tell by how pretty the alert is.
|
||||||
</wa-alert>
|
</sl-alert>
|
||||||
|
|
||||||
<wa-alert variant="success" duration="3000" closable>
|
<sl-alert variant="success" duration="3000" closable>
|
||||||
<wa-icon slot="icon" name="circle-check" variant="regular"></wa-icon>
|
<sl-icon slot="icon" name="check2-circle"></sl-icon>
|
||||||
<strong>Your changes have been saved</strong><br />
|
<strong>Your changes have been saved</strong><br />
|
||||||
You can safely exit the app now.
|
You can safely exit the app now.
|
||||||
</wa-alert>
|
</sl-alert>
|
||||||
|
|
||||||
<wa-alert variant="neutral" duration="3000" closable>
|
<sl-alert variant="neutral" duration="3000" closable>
|
||||||
<wa-icon slot="icon" name="gear" variant="regular"></wa-icon>
|
<sl-icon slot="icon" name="gear"></sl-icon>
|
||||||
<strong>Your settings have been updated</strong><br />
|
<strong>Your settings have been updated</strong><br />
|
||||||
Settings will take effect on next login.
|
Settings will take affect on next login.
|
||||||
</wa-alert>
|
</sl-alert>
|
||||||
|
|
||||||
<wa-alert variant="warning" duration="3000" closable>
|
<sl-alert variant="warning" duration="3000" closable>
|
||||||
<wa-icon slot="icon" name="triangle-exclamation" variant="regular"></wa-icon>
|
<sl-icon slot="icon" name="exclamation-triangle"></sl-icon>
|
||||||
<strong>Your session has ended</strong><br />
|
<strong>Your session has ended</strong><br />
|
||||||
Please login again to continue.
|
Please login again to continue.
|
||||||
</wa-alert>
|
</sl-alert>
|
||||||
|
|
||||||
<wa-alert variant="danger" duration="3000" closable>
|
<sl-alert variant="danger" duration="3000" closable>
|
||||||
<wa-icon slot="icon" name="circle-exclamation" variant="regular"></wa-icon>
|
<sl-icon slot="icon" name="exclamation-octagon"></sl-icon>
|
||||||
<strong>Your account has been deleted</strong><br />
|
<strong>Your account has been deleted</strong><br />
|
||||||
We're very sorry to see you go!
|
We're very sorry to see you go!
|
||||||
</wa-alert>
|
</sl-alert>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const container = document.querySelector('.alert-toast');
|
const container = document.querySelector('.alert-toast');
|
||||||
|
|
||||||
['brand', 'success', 'neutral', 'warning', 'danger'].map(variant => {
|
['primary', 'success', 'neutral', 'warning', 'danger'].map(variant => {
|
||||||
const button = container.querySelector(`wa-button[variant="${variant}"]`);
|
const button = container.querySelector(`sl-button[variant="${variant}"]`);
|
||||||
const alert = container.querySelector(`wa-alert[variant="${variant}"]`);
|
const alert = container.querySelector(`sl-alert[variant="${variant}"]`);
|
||||||
|
|
||||||
button.addEventListener('click', () => alert.toast());
|
button.addEventListener('click', () => alert.toast());
|
||||||
});
|
});
|
||||||
@@ -305,16 +306,16 @@ You should always use the `closable` attribute so users can dismiss the notifica
|
|||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import WaAlert from '@shoelace-style/shoelace/dist/react/alert';
|
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
function showToast(alert) {
|
function showToast(alert) {
|
||||||
alert.toast();
|
alert.toast();
|
||||||
}
|
}
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const brand = useRef(null);
|
const primary = useRef(null);
|
||||||
const success = useRef(null);
|
const success = useRef(null);
|
||||||
const neutral = useRef(null);
|
const neutral = useRef(null);
|
||||||
const warning = useRef(null);
|
const warning = useRef(null);
|
||||||
@@ -322,60 +323,60 @@ const App = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WaButton variant="brand" onClick={() => brand.current.toast()}>
|
<SlButton variant="primary" onClick={() => primary.current.toast()}>
|
||||||
Brand
|
Primary
|
||||||
</WaButton>
|
</SlButton>
|
||||||
|
|
||||||
<WaButton variant="success" onClick={() => success.current.toast()}>
|
<SlButton variant="success" onClick={() => success.current.toast()}>
|
||||||
Success
|
Success
|
||||||
</WaButton>
|
</SlButton>
|
||||||
|
|
||||||
<WaButton variant="neutral" onClick={() => neutral.current.toast()}>
|
<SlButton variant="neutral" onClick={() => neutral.current.toast()}>
|
||||||
Neutral
|
Neutral
|
||||||
</WaButton>
|
</SlButton>
|
||||||
|
|
||||||
<WaButton variant="warning" onClick={() => warning.current.toast()}>
|
<SlButton variant="warning" onClick={() => warning.current.toast()}>
|
||||||
Warning
|
Warning
|
||||||
</WaButton>
|
</SlButton>
|
||||||
|
|
||||||
<WaButton variant="danger" onClick={() => danger.current.toast()}>
|
<SlButton variant="danger" onClick={() => danger.current.toast()}>
|
||||||
Danger
|
Danger
|
||||||
</WaButton>
|
</SlButton>
|
||||||
|
|
||||||
<WaAlert ref={brand} variant="brand" duration="3000" closable>
|
<SlAlert ref={primary} variant="primary" duration="3000" closable>
|
||||||
<WaIcon slot="icon" name="circle-info" variant="regular" />
|
<SlIcon slot="icon" name="info-circle" />
|
||||||
<strong>This is super informative</strong>
|
<strong>This is super informative</strong>
|
||||||
<br />
|
<br />
|
||||||
You can tell by how pretty the alert is.
|
You can tell by how pretty the alert is.
|
||||||
</WaAlert>
|
</SlAlert>
|
||||||
|
|
||||||
<WaAlert ref={success} variant="success" duration="3000" closable>
|
<SlAlert ref={success} variant="success" duration="3000" closable>
|
||||||
<WaIcon slot="icon" name="circle-check" variant="regular" />
|
<SlIcon slot="icon" name="check2-circle" />
|
||||||
<strong>Your changes have been saved</strong>
|
<strong>Your changes have been saved</strong>
|
||||||
<br />
|
<br />
|
||||||
You can safely exit the app now.
|
You can safely exit the app now.
|
||||||
</WaAlert>
|
</SlAlert>
|
||||||
|
|
||||||
<WaAlert ref={neutral} variant="neutral" duration="3000" closable>
|
<SlAlert ref={neutral} variant="neutral" duration="3000" closable>
|
||||||
<WaIcon slot="icon" name="gear" variant="regular" />
|
<SlIcon slot="icon" name="gear" />
|
||||||
<strong>Your settings have been updated</strong>
|
<strong>Your settings have been updated</strong>
|
||||||
<br />
|
<br />
|
||||||
Settings will take effect on next login.
|
Settings will take affect on next login.
|
||||||
</WaAlert>
|
</SlAlert>
|
||||||
|
|
||||||
<WaAlert ref={warning} variant="warning" duration="3000" closable>
|
<SlAlert ref={warning} variant="warning" duration="3000" closable>
|
||||||
<WaIcon slot="icon" name="triangle-exclamation" variant="regular" />
|
<SlIcon slot="icon" name="exclamation-triangle" />
|
||||||
<strong>Your session has ended</strong>
|
<strong>Your session has ended</strong>
|
||||||
<br />
|
<br />
|
||||||
Please login again to continue.
|
Please login again to continue.
|
||||||
</WaAlert>
|
</SlAlert>
|
||||||
|
|
||||||
<WaAlert ref={danger} variant="danger" duration="3000" closable>
|
<SlAlert ref={danger} variant="danger" duration="3000" closable>
|
||||||
<WaIcon slot="icon" name="circle-exclamation" variant="regular" />
|
<SlIcon slot="icon" name="exclamation-octagon" />
|
||||||
<strong>Your account has been deleted</strong>
|
<strong>Your account has been deleted</strong>
|
||||||
<br />
|
<br />
|
||||||
We're very sorry to see you go!
|
We're very sorry to see you go!
|
||||||
</WaAlert>
|
</SlAlert>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -387,12 +388,12 @@ For convenience, you can create a utility that emits toast notifications with a
|
|||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<div class="alert-toast-wrapper">
|
<div class="alert-toast-wrapper">
|
||||||
<wa-button variant="brand">Create Toast</wa-button>
|
<sl-button variant="primary">Create Toast</sl-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const container = document.querySelector('.alert-toast-wrapper');
|
const container = document.querySelector('.alert-toast-wrapper');
|
||||||
const button = container.querySelector('wa-button');
|
const button = container.querySelector('sl-button');
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
// Always escape HTML for text arguments!
|
// Always escape HTML for text arguments!
|
||||||
@@ -403,13 +404,13 @@ For convenience, you can create a utility that emits toast notifications with a
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Custom function to emit toast notifications
|
// Custom function to emit toast notifications
|
||||||
function notify(message, variant = 'brand', icon = 'circle-info', duration = 3000) {
|
function notify(message, variant = 'primary', icon = 'info-circle', duration = 3000) {
|
||||||
const alert = Object.assign(document.createElement('wa-alert'), {
|
const alert = Object.assign(document.createElement('sl-alert'), {
|
||||||
variant,
|
variant,
|
||||||
closable: true,
|
closable: true,
|
||||||
duration: duration,
|
duration: duration,
|
||||||
innerHTML: `
|
innerHTML: `
|
||||||
<wa-icon name="${icon}" variant="regular" slot="icon"></wa-icon>
|
<sl-icon name="${icon}" slot="icon"></sl-icon>
|
||||||
${escapeHtml(message)}
|
${escapeHtml(message)}
|
||||||
`
|
`
|
||||||
});
|
});
|
||||||
@@ -428,10 +429,10 @@ For convenience, you can create a utility that emits toast notifications with a
|
|||||||
|
|
||||||
The toast stack is a fixed position singleton element created and managed internally by the alert component. It will be added and removed from the DOM as needed when toasts are shown. When more than one toast is visible, they will stack vertically in the toast stack.
|
The toast stack is a fixed position singleton element created and managed internally by the alert component. It will be added and removed from the DOM as needed when toasts are shown. When more than one toast is visible, they will stack vertically in the toast stack.
|
||||||
|
|
||||||
By default, the toast stack is positioned at the top-right of the viewport. You can change its position by targeting `.wa-toast-stack` in your stylesheet. To make toasts appear at the top-left of the viewport, for example, use the following styles.
|
By default, the toast stack is positioned at the top-right of the viewport. You can change its position by targeting `.sl-toast-stack` in your stylesheet. To make toasts appear at the top-left of the viewport, for example, use the following styles.
|
||||||
|
|
||||||
```css
|
```css
|
||||||
.wa-toast-stack {
|
.sl-toast-stack {
|
||||||
left: 0;
|
left: 0;
|
||||||
right: auto;
|
right: auto;
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,22 @@
|
|||||||
---
|
---
|
||||||
title: Animated Image
|
meta:
|
||||||
description: A component for displaying animated GIFs and WEBPs that play and pause on interaction.
|
title: Animated Image
|
||||||
layout: ../../../layouts/ComponentLayout.astro
|
description: A component for displaying animated GIFs and WEBPs that play and pause on interaction.
|
||||||
|
layout: component
|
||||||
---
|
---
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-animated-image
|
<sl-animated-image
|
||||||
src="https://shoelace.style/assets/images/walk.gif"
|
src="https://shoelace.style/assets/images/walk.gif"
|
||||||
alt="Animation of untied shoes walking on pavement"
|
alt="Animation of untied shoes walking on pavement"
|
||||||
></wa-animated-image>
|
></sl-animated-image>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';
|
import SlAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<WaAnimatedImage
|
<SlAnimatedImage
|
||||||
src="https://shoelace.style/assets/images/walk.gif"
|
src="https://shoelace.style/assets/images/walk.gif"
|
||||||
alt="Animation of untied shoes walking on pavement"
|
alt="Animation of untied shoes walking on pavement"
|
||||||
/>
|
/>
|
||||||
@@ -33,17 +34,17 @@ This component uses `<canvas>` to draw freeze frames, so images are subject to [
|
|||||||
Both GIF and WEBP images are supported.
|
Both GIF and WEBP images are supported.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-animated-image
|
<sl-animated-image
|
||||||
src="https://shoelace.style/assets/images/tie.webp"
|
src="https://shoelace.style/assets/images/tie.webp"
|
||||||
alt="Animation of a shoe being tied"
|
alt="Animation of a shoe being tied"
|
||||||
></wa-animated-image>
|
></sl-animated-image>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';
|
import SlAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<WaAnimatedImage src="https://shoelace.style/assets/images/tie.webp" alt="Animation of a shoe being tied" />
|
<SlAnimatedImage src="https://shoelace.style/assets/images/tie.webp" alt="Animation of a shoe being tied" />
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -52,19 +53,21 @@ const App = () => (
|
|||||||
To set a custom size, apply a width and/or height to the host element.
|
To set a custom size, apply a width and/or height to the host element.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-animated-image
|
<sl-animated-image
|
||||||
src="https://shoelace.style/assets/images/walk.gif"
|
src="https://shoelace.style/assets/images/walk.gif"
|
||||||
alt="Animation of untied shoes walking on pavement"
|
alt="Animation of untied shoes walking on pavement"
|
||||||
style="width: 150px; height: 200px;"
|
style="width: 150px; height: 200px;"
|
||||||
>
|
>
|
||||||
</wa-animated-image>
|
</sl-animated-image>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';
|
import SlAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<WaAnimatedImage
|
<SlAnimatedImage
|
||||||
src="https://shoelace.style/assets/images/walk.gif"
|
src="https://shoelace.style/assets/images/walk.gif"
|
||||||
alt="Animation of untied shoes walking on pavement"
|
alt="Animation of untied shoes walking on pavement"
|
||||||
style={{ width: '150px', height: '200px' }}
|
style={{ width: '150px', height: '200px' }}
|
||||||
@@ -72,16 +75,18 @@ const App = () => (
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
### Customizing the Control Box
|
### Customizing the Control Box
|
||||||
|
|
||||||
You can change the appearance and location of the control box by targeting the `control-box` part in your styles.
|
You can change the appearance and location of the control box by targeting the `control-box` part in your styles.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-animated-image
|
<sl-animated-image
|
||||||
src="https://shoelace.style/assets/images/walk.gif"
|
src="https://shoelace.style/assets/images/walk.gif"
|
||||||
alt="Animation of untied shoes walking on pavement"
|
alt="Animation of untied shoes walking on pavement"
|
||||||
class="animated-image-custom-control-box"
|
class="animated-image-custom-control-box"
|
||||||
></wa-animated-image>
|
></sl-animated-image>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.animated-image-custom-control-box::part(control-box) {
|
.animated-image-custom-control-box::part(control-box) {
|
||||||
@@ -97,7 +102,7 @@ You can change the appearance and location of the control box by targeting the `
|
|||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';
|
import SlAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';
|
||||||
|
|
||||||
const css = `
|
const css = `
|
||||||
.animated-image-custom-control-box::part(control-box) {
|
.animated-image-custom-control-box::part(control-box) {
|
||||||
@@ -113,7 +118,7 @@ const css = `
|
|||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<WaAnimatedImage
|
<SlAnimatedImage
|
||||||
className="animated-image-custom-control-box"
|
className="animated-image-custom-control-box"
|
||||||
src="https://shoelace.style/assets/images/walk.gif"
|
src="https://shoelace.style/assets/images/walk.gif"
|
||||||
alt="Animation of untied shoes walking on pavement"
|
alt="Animation of untied shoes walking on pavement"
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
---
|
---
|
||||||
title: Animation
|
meta:
|
||||||
description: Animate elements declaratively with nearly 100 baked-in presets, or roll your own with custom keyframes.
|
title: Animation
|
||||||
layout: ../../../layouts/ComponentLayout.astro
|
description: Animate elements declaratively with nearly 100 baked-in presets, or roll your own with custom keyframes.
|
||||||
|
layout: component
|
||||||
---
|
---
|
||||||
|
|
||||||
To animate an element, wrap it in `<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.
|
To animate an element, wrap it in `<sl-animation>` and set an animation `name`. The animation will not start until you add the `play` attribute. Refer to the [properties table](#properties) for a list of all animation options.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<div class="animation-overview">
|
<div class="animation-overview">
|
||||||
<wa-animation name="bounce" duration="2000" play><div class="box"></div></wa-animation>
|
<sl-animation name="bounce" duration="2000" play><div class="box"></div></sl-animation>
|
||||||
<wa-animation name="jello" duration="2000" play><div class="box"></div></wa-animation>
|
<sl-animation name="jello" duration="2000" play><div class="box"></div></sl-animation>
|
||||||
<wa-animation name="heartBeat" duration="2000" play><div class="box"></div></wa-animation>
|
<sl-animation name="heartBeat" duration="2000" play><div class="box"></div></sl-animation>
|
||||||
<wa-animation name="flip" duration="2000" play><div class="box"></div></wa-animation>
|
<sl-animation name="flip" duration="2000" play><div class="box"></div></sl-animation>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -19,21 +20,21 @@ To animate an element, wrap it in `<wa-animation>` and set an animation `name`.
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
background-color: var(--wa-color-brand-spot);
|
background-color: var(--sl-color-primary-600);
|
||||||
margin: 1.5rem;
|
margin: 1.5rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaAnimation from '@shoelace-style/shoelace/dist/react/animation';
|
import SlAnimation from '@shoelace-style/shoelace/dist/react/animation';
|
||||||
|
|
||||||
const css = `
|
const css = `
|
||||||
.animation-overview .box {
|
.animation-overview .box {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
background-color: var(--wa-color-brand-spot);
|
background-color: var(--sl-color-primary-600);
|
||||||
margin: 1.5rem;
|
margin: 1.5rem;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -41,18 +42,18 @@ const css = `
|
|||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<div class="animation-overview">
|
<div class="animation-overview">
|
||||||
<WaAnimation name="bounce" duration={2000} play>
|
<SlAnimation name="bounce" duration={2000} play>
|
||||||
<div class="box" />
|
<div class="box" />
|
||||||
</WaAnimation>
|
</SlAnimation>
|
||||||
<WaAnimation name="jello" duration={2000} play>
|
<SlAnimation name="jello" duration={2000} play>
|
||||||
<div class="box" />
|
<div class="box" />
|
||||||
</WaAnimation>
|
</SlAnimation>
|
||||||
<WaAnimation name="heartBeat" duration={2000} play>
|
<SlAnimation name="heartBeat" duration={2000} play>
|
||||||
<div class="box" />
|
<div class="box" />
|
||||||
</WaAnimation>
|
</SlAnimation>
|
||||||
<WaAnimation name="flip" duration={2000} play>
|
<SlAnimation name="flip" duration={2000} play>
|
||||||
<div class="box" />
|
<div class="box" />
|
||||||
</WaAnimation>
|
</SlAnimation>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>{css}</style>
|
<style>{css}</style>
|
||||||
@@ -61,7 +62,7 @@ const App = () => (
|
|||||||
```
|
```
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
The animation will only be applied to the first child element found in `<wa-animation>`.
|
The animation will only be applied to the first child element found in `<sl-animation>`.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
@@ -72,15 +73,14 @@ This example demonstrates all of the baked-in animations and easings. Animations
|
|||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<div class="animation-sandbox">
|
<div class="animation-sandbox">
|
||||||
<wa-animation name="bounce" easing="ease-in-out" duration="2000" play>
|
<sl-animation name="bounce" easing="ease-in-out" duration="2000" play>
|
||||||
<div class="box"></div>
|
<div class="box"></div>
|
||||||
</wa-animation>
|
</sl-animation>
|
||||||
|
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<wa-select label="Animation" value="bounce"></wa-select>
|
<sl-select label="Animation" value="bounce"></sl-select>
|
||||||
<wa-select label="Easing" value="linear"></wa-select>
|
<sl-select label="Easing" value="linear"></sl-select>
|
||||||
<wa-input label="Playback Rate" type="number" min="0" max="2" step=".25" value="1">
|
<sl-input label="Playback Rate" type="number" min="0" max="2" step=".25" value="1"></sl-input>
|
||||||
</wa-input>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -88,15 +88,15 @@ This example demonstrates all of the baked-in animations and easings. Animations
|
|||||||
import { getAnimationNames, getEasingNames } from '/dist/utilities/animation.js';
|
import { getAnimationNames, getEasingNames } from '/dist/utilities/animation.js';
|
||||||
|
|
||||||
const container = document.querySelector('.animation-sandbox');
|
const container = document.querySelector('.animation-sandbox');
|
||||||
const animation = container.querySelector('wa-animation');
|
const animation = container.querySelector('sl-animation');
|
||||||
const animationName = container.querySelector('.controls wa-select:nth-child(1)');
|
const animationName = container.querySelector('.controls sl-select:nth-child(1)');
|
||||||
const easingName = container.querySelector('.controls wa-select:nth-child(2)');
|
const easingName = container.querySelector('.controls sl-select:nth-child(2)');
|
||||||
const playbackRate = container.querySelector('wa-input[type="number"]');
|
const playbackRate = container.querySelector('sl-input[type="number"]');
|
||||||
const animations = getAnimationNames();
|
const animations = getAnimationNames();
|
||||||
const easings = getEasingNames();
|
const easings = getEasingNames();
|
||||||
|
|
||||||
animations.map(name => {
|
animations.map(name => {
|
||||||
const option = Object.assign(document.createElement('wa-option'), {
|
const option = Object.assign(document.createElement('sl-option'), {
|
||||||
textContent: name,
|
textContent: name,
|
||||||
value: name
|
value: name
|
||||||
});
|
});
|
||||||
@@ -104,23 +104,23 @@ This example demonstrates all of the baked-in animations and easings. Animations
|
|||||||
});
|
});
|
||||||
|
|
||||||
easings.map(name => {
|
easings.map(name => {
|
||||||
const option = Object.assign(document.createElement('wa-option'), {
|
const option = Object.assign(document.createElement('sl-option'), {
|
||||||
textContent: name,
|
textContent: name,
|
||||||
value: name
|
value: name
|
||||||
});
|
});
|
||||||
easingName.appendChild(option);
|
easingName.appendChild(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
animationName.addEventListener('wa-change', () => (animation.name = animationName.value));
|
animationName.addEventListener('sl-change', () => (animation.name = animationName.value));
|
||||||
easingName.addEventListener('wa-change', () => (animation.easing = easingName.value));
|
easingName.addEventListener('sl-change', () => (animation.easing = easingName.value));
|
||||||
playbackRate.addEventListener('wa-input', () => (animation.playbackRate = playbackRate.value));
|
playbackRate.addEventListener('sl-input', () => (animation.playbackRate = playbackRate.value));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.animation-sandbox .box {
|
.animation-sandbox .box {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
background-color: var(--wa-color-brand-spot);
|
background-color: var(--sl-color-primary-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
.animation-sandbox .controls {
|
.animation-sandbox .controls {
|
||||||
@@ -128,28 +128,24 @@ This example demonstrates all of the baked-in animations and easings. Animations
|
|||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.animation-sandbox .controls wa-select {
|
.animation-sandbox .controls sl-select {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using Intersection Observer
|
### 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.
|
Use an [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) to control the animation when an element enters or exits the viewport. For example, scroll the box below in and out of your screen. The animation stops when the box exits the viewport and restarts each time it enters the viewport.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<div class="animation-scroll">
|
<div class="animation-scroll">
|
||||||
<wa-animation name="jackInTheBox" duration="2000" iterations="1"><div class="box"></div></wa-animation>
|
<sl-animation name="jackInTheBox" duration="2000" iterations="1"><div class="box"></div></sl-animation>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const container = document.querySelector('.animation-scroll');
|
const container = document.querySelector('.animation-scroll');
|
||||||
const animation = container.querySelector('wa-animation');
|
const animation = container.querySelector('sl-animation');
|
||||||
const box = animation.querySelector('.box');
|
const box = animation.querySelector('.box');
|
||||||
|
|
||||||
// Watch for the box to enter and exit the viewport. Note that we're observing the box, not the animation element!
|
// Watch for the box to enter and exit the viewport. Note that we're observing the box, not the animation element!
|
||||||
@@ -170,14 +166,14 @@ Use an [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
background-color: var(--wa-color-brand-spot);
|
background-color: var(--sl-color-primary-600);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import WaAnimation from '@shoelace-style/shoelace/dist/react/animation';
|
import SlAnimation from '@shoelace-style/shoelace/dist/react/animation';
|
||||||
|
|
||||||
const css = `
|
const css = `
|
||||||
.animation-scroll {
|
.animation-scroll {
|
||||||
@@ -188,7 +184,7 @@ const css = `
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
background-color: var(--wa-color-brand-spot);
|
background-color: var(--sl-color-primary-600);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -214,9 +210,9 @@ const App = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="animation-scroll">
|
<div class="animation-scroll">
|
||||||
<WaAnimation ref={animation} name="jackInTheBox" duration={2000} iterations={1}>
|
<SlAnimation ref={animation} name="jackInTheBox" duration={2000} iterations={1}>
|
||||||
<div ref={box} class="box" />
|
<div ref={box} class="box" />
|
||||||
</WaAnimation>
|
</SlAnimation>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>{css}</style>
|
<style>{css}</style>
|
||||||
@@ -231,13 +227,13 @@ Supply your own [keyframe formats](https://developer.mozilla.org/en-US/docs/Web/
|
|||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<div class="animation-keyframes">
|
<div class="animation-keyframes">
|
||||||
<wa-animation easing="ease-in-out" duration="2000" play>
|
<sl-animation easing="ease-in-out" duration="2000" play>
|
||||||
<div class="box"></div>
|
<div class="box"></div>
|
||||||
</wa-animation>
|
</sl-animation>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const animation = document.querySelector('.animation-keyframes wa-animation');
|
const animation = document.querySelector('.animation-keyframes sl-animation');
|
||||||
animation.keyframes = [
|
animation.keyframes = [
|
||||||
{
|
{
|
||||||
offset: 0,
|
offset: 0,
|
||||||
@@ -260,26 +256,26 @@ Supply your own [keyframe formats](https://developer.mozilla.org/en-US/docs/Web/
|
|||||||
.animation-keyframes .box {
|
.animation-keyframes .box {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
background-color: var(--wa-color-brand-spot);
|
background-color: var(--sl-color-primary-600);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaAnimation from '@shoelace-style/shoelace/dist/react/animation';
|
import SlAnimation from '@shoelace-style/shoelace/dist/react/animation';
|
||||||
|
|
||||||
const css = `
|
const css = `
|
||||||
.animation-keyframes .box {
|
.animation-keyframes .box {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
background-color: var(--wa-color-brand-spot);
|
background-color: var(--sl-color-primary-600);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<div class="animation-keyframes">
|
<div class="animation-keyframes">
|
||||||
<WaAnimation
|
<SlAnimation
|
||||||
easing="ease-in-out"
|
easing="ease-in-out"
|
||||||
duration={2000}
|
duration={2000}
|
||||||
play
|
play
|
||||||
@@ -301,7 +297,7 @@ const App = () => (
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<div class="box" />
|
<div class="box" />
|
||||||
</WaAnimation>
|
</SlAnimation>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>{css}</style>
|
<style>{css}</style>
|
||||||
@@ -315,15 +311,15 @@ Animations won't play until you apply the `play` attribute. You can omit it init
|
|||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<div class="animation-form">
|
<div class="animation-form">
|
||||||
<wa-animation name="rubberBand" duration="1000" iterations="1">
|
<sl-animation name="rubberBand" duration="1000" iterations="1">
|
||||||
<wa-button variant="brand">Click me</wa-button>
|
<sl-button variant="primary">Click me</sl-button>
|
||||||
</wa-animation>
|
</sl-animation>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const container = document.querySelector('.animation-form');
|
const container = document.querySelector('.animation-form');
|
||||||
const animation = container.querySelector('wa-animation');
|
const animation = container.querySelector('sl-animation');
|
||||||
const button = container.querySelector('wa-button');
|
const button = container.querySelector('sl-button');
|
||||||
|
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
animation.play = true;
|
animation.play = true;
|
||||||
@@ -333,19 +329,19 @@ Animations won't play until you apply the `play` attribute. You can omit it init
|
|||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaAnimation from '@shoelace-style/shoelace/dist/react/animation';
|
import SlAnimation from '@shoelace-style/shoelace/dist/react/animation';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [play, setPlay] = useState(false);
|
const [play, setPlay] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="animation-form">
|
<div class="animation-form">
|
||||||
<WaAnimation name="rubberBand" duration={1000} iterations={1} play={play} onWaFinish={() => setPlay(false)}>
|
<SlAnimation name="rubberBand" duration={1000} iterations={1} play={play} onSlFinish={() => setPlay(false)}>
|
||||||
<WaButton variant="brand" onClick={() => setPlay(true)}>
|
<SlButton variant="primary" onClick={() => setPlay(true)}>
|
||||||
Click me
|
Click me
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaAnimation>
|
</SlAnimation>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
---
|
---
|
||||||
title: Avatar
|
meta:
|
||||||
description: Avatars are used to represent a person or object.
|
title: Avatar
|
||||||
layout: ../../../layouts/ComponentLayout.astro
|
description: Avatars are used to represent a person or object.
|
||||||
|
layout: component
|
||||||
---
|
---
|
||||||
|
|
||||||
By default, a generic icon will be shown. You can personalize avatars by adding custom icons, initials, and images. You should always provide a `label` for assistive devices.
|
By default, a generic icon will be shown. You can personalize avatars by adding custom icons, initials, and images. You should always provide a `label` for assistive devices.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-avatar label="User avatar"></wa-avatar>
|
<sl-avatar label="User avatar"></sl-avatar>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
||||||
|
|
||||||
const App = () => <WaAvatar label="User avatar" />;
|
const App = () => <SlAvatar label="User avatar" />;
|
||||||
```
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
@@ -24,26 +25,26 @@ To use an image for the avatar, set the `image` and `label` attributes. This wil
|
|||||||
Avatar images can be lazily loaded by setting the `loading` attribute to `lazy`.
|
Avatar images can be lazily loaded by setting the `loading` attribute to `lazy`.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-avatar
|
<sl-avatar
|
||||||
image="https://images.unsplash.com/photo-1529778873920-4da4926a72c2?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=80"
|
image="https://images.unsplash.com/photo-1529778873920-4da4926a72c2?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=80"
|
||||||
label="Avatar of a gray tabby kitten looking down"
|
label="Avatar of a gray tabby kitten looking down"
|
||||||
></wa-avatar>
|
></sl-avatar>
|
||||||
<wa-avatar
|
<sl-avatar
|
||||||
image="https://images.unsplash.com/photo-1591871937573-74dbba515c4c?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=80"
|
image="https://images.unsplash.com/photo-1591871937573-74dbba515c4c?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=80"
|
||||||
label="Avatar of a white and grey kitten on grey textile"
|
label="Avatar of a white and grey kitten on grey textile"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
></wa-avatar>
|
></sl-avatar>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<WaAvatar
|
<SlAvatar
|
||||||
image="https://images.unsplash.com/photo-1529778873920-4da4926a72c2?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=80"
|
image="https://images.unsplash.com/photo-1529778873920-4da4926a72c2?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=80"
|
||||||
label="Avatar of a gray tabby kitten looking down"
|
label="Avatar of a gray tabby kitten looking down"
|
||||||
/>
|
/>
|
||||||
<WaAvatar
|
<SlAvatar
|
||||||
image="https://images.unsplash.com/photo-1591871937573-74dbba515c4c?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=80"
|
image="https://images.unsplash.com/photo-1591871937573-74dbba515c4c?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=80"
|
||||||
label="Avatar of a white and grey kitten on grey textile"
|
label="Avatar of a white and grey kitten on grey textile"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
@@ -56,13 +57,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.
|
When you don't have an image to use, you can set the `initials` attribute to show something more personalized than an icon.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-avatar initials="WA" label="Avatar with initials: SL"></wa-avatar>
|
<sl-avatar initials="SL" label="Avatar with initials: SL"></sl-avatar>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
||||||
|
|
||||||
const App = () => <WaAvatar initials="WA" label="Avatar with initials: SL" />;
|
const App = () => <SlAvatar initials="SL" label="Avatar with initials: SL" />;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Custom Icons
|
### Custom Icons
|
||||||
@@ -70,36 +71,36 @@ const App = () => <WaAvatar initials="WA" label="Avatar with initials: SL" />;
|
|||||||
When no image or initials are set, an icon will be shown. The default avatar shows a generic "user" icon, but you can customize this with the `icon` slot.
|
When no image or initials are set, an icon will be shown. The default avatar shows a generic "user" icon, but you can customize this with the `icon` slot.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-avatar label="Avatar with an image icon">
|
<sl-avatar label="Avatar with an image icon">
|
||||||
<wa-icon slot="icon" name="image" variant="solid"></wa-icon>
|
<sl-icon slot="icon" name="image"></sl-icon>
|
||||||
</wa-avatar>
|
</sl-avatar>
|
||||||
|
|
||||||
<wa-avatar label="Avatar with an archive icon">
|
<sl-avatar label="Avatar with an archive icon">
|
||||||
<wa-icon slot="icon" name="archive" variant="solid"></wa-icon>
|
<sl-icon slot="icon" name="archive"></sl-icon>
|
||||||
</wa-avatar>
|
</sl-avatar>
|
||||||
|
|
||||||
<wa-avatar label="Avatar with a briefcase icon">
|
<sl-avatar label="Avatar with a briefcase icon">
|
||||||
<wa-icon slot="icon" name="briefcase" variant="solid"></wa-icon>
|
<sl-icon slot="icon" name="briefcase"></sl-icon>
|
||||||
</wa-avatar>
|
</sl-avatar>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
||||||
import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<WaAvatar label="Avatar with an image icon">
|
<SlAvatar label="Avatar with an image icon">
|
||||||
<WaIcon slot="icon" name="image" />
|
<SlIcon slot="icon" name="image" />
|
||||||
</WaAvatar>
|
</SlAvatar>
|
||||||
|
|
||||||
<WaAvatar label="Avatar with an archive icon">
|
<SlAvatar label="Avatar with an archive icon">
|
||||||
<WaIcon slot="icon" name="archive" />
|
<SlIcon slot="icon" name="archive" />
|
||||||
</WaAvatar>
|
</SlAvatar>
|
||||||
|
|
||||||
<WaAvatar label="Avatar with a briefcase icon">
|
<SlAvatar label="Avatar with a briefcase icon">
|
||||||
<WaIcon slot="icon" name="briefcase" />
|
<SlIcon slot="icon" name="briefcase" />
|
||||||
</WaAvatar>
|
</SlAvatar>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
@@ -109,20 +110,20 @@ const App = () => (
|
|||||||
Avatars can be shaped using the `shape` attribute.
|
Avatars can be shaped using the `shape` attribute.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-avatar shape="square" label="Square avatar"></wa-avatar>
|
<sl-avatar shape="square" label="Square avatar"></sl-avatar>
|
||||||
<wa-avatar shape="rounded" label="Rounded avatar"></wa-avatar>
|
<sl-avatar shape="rounded" label="Rounded avatar"></sl-avatar>
|
||||||
<wa-avatar shape="circle" label="Circle avatar"></wa-avatar>
|
<sl-avatar shape="circle" label="Circle avatar"></sl-avatar>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
||||||
import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<WaAvatar shape="square" label="Square avatar" />
|
<SlAvatar shape="square" label="Square avatar" />
|
||||||
<WaAvatar shape="rounded" label="Rounded avatar" />
|
<SlAvatar shape="rounded" label="Rounded avatar" />
|
||||||
<WaAvatar shape="circle" label="Circle avatar" />
|
<SlAvatar shape="circle" label="Circle avatar" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
@@ -133,71 +134,71 @@ You can group avatars with a few lines of CSS.
|
|||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<div class="avatar-group">
|
<div class="avatar-group">
|
||||||
<wa-avatar
|
<sl-avatar
|
||||||
image="https://images.unsplash.com/photo-1490150028299-bf57d78394e0?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&q=80&crop=right"
|
image="https://images.unsplash.com/photo-1490150028299-bf57d78394e0?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&q=80&crop=right"
|
||||||
label="Avatar 1 of 4"
|
label="Avatar 1 of 4"
|
||||||
></wa-avatar>
|
></sl-avatar>
|
||||||
|
|
||||||
<wa-avatar
|
<sl-avatar
|
||||||
image="https://images.unsplash.com/photo-1503454537195-1dcabb73ffb9?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80"
|
image="https://images.unsplash.com/photo-1503454537195-1dcabb73ffb9?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80"
|
||||||
label="Avatar 2 of 4"
|
label="Avatar 2 of 4"
|
||||||
></wa-avatar>
|
></sl-avatar>
|
||||||
|
|
||||||
<wa-avatar
|
<sl-avatar
|
||||||
image="https://images.unsplash.com/photo-1456439663599-95b042d50252?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80"
|
image="https://images.unsplash.com/photo-1456439663599-95b042d50252?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80"
|
||||||
label="Avatar 3 of 4"
|
label="Avatar 3 of 4"
|
||||||
></wa-avatar>
|
></sl-avatar>
|
||||||
|
|
||||||
<wa-avatar
|
<sl-avatar
|
||||||
image="https://images.unsplash.com/flagged/photo-1554078875-e37cb8b0e27d?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=top&q=80"
|
image="https://images.unsplash.com/flagged/photo-1554078875-e37cb8b0e27d?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=top&q=80"
|
||||||
label="Avatar 4 of 4"
|
label="Avatar 4 of 4"
|
||||||
></wa-avatar>
|
></sl-avatar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.avatar-group wa-avatar:not(:first-of-type) {
|
.avatar-group sl-avatar:not(:first-of-type) {
|
||||||
margin-left: calc(-1 * var(--wa-space-m));
|
margin-left: -1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-group wa-avatar::part(base) {
|
.avatar-group sl-avatar::part(base) {
|
||||||
border: solid 2px var(--wa-color-surface-default);
|
border: solid 2px var(--sl-color-neutral-0);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
||||||
import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
const css = `
|
const css = `
|
||||||
.avatar-group wa-avatar:not(:first-of-type) {
|
.avatar-group sl-avatar:not(:first-of-type) {
|
||||||
margin-left: calc(-1 * var(--wa-space-m));
|
margin-left: -1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-group wa-avatar::part(base) {
|
.avatar-group sl-avatar::part(base) {
|
||||||
border: solid 2px var(--wa-color-surface-default);
|
border: solid 2px var(--sl-color-neutral-0);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<div className="avatar-group">
|
<div className="avatar-group">
|
||||||
<WaAvatar
|
<SlAvatar
|
||||||
image="https://images.unsplash.com/photo-1490150028299-bf57d78394e0?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&q=80&crop=right"
|
image="https://images.unsplash.com/photo-1490150028299-bf57d78394e0?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&q=80&crop=right"
|
||||||
label="Avatar 1 of 4"
|
label="Avatar 1 of 4"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WaAvatar
|
<SlAvatar
|
||||||
image="https://images.unsplash.com/photo-1503454537195-1dcabb73ffb9?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80"
|
image="https://images.unsplash.com/photo-1503454537195-1dcabb73ffb9?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80"
|
||||||
label="Avatar 2 of 4"
|
label="Avatar 2 of 4"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WaAvatar
|
<SlAvatar
|
||||||
image="https://images.unsplash.com/photo-1456439663599-95b042d50252?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80"
|
image="https://images.unsplash.com/photo-1456439663599-95b042d50252?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80"
|
||||||
label="Avatar 3 of 4"
|
label="Avatar 3 of 4"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WaAvatar
|
<SlAvatar
|
||||||
image="https://images.unsplash.com/flagged/photo-1554078875-e37cb8b0e27d?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=top&q=80"
|
image="https://images.unsplash.com/flagged/photo-1554078875-e37cb8b0e27d?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=top&q=80"
|
||||||
label="Avatar 4 of 4"
|
label="Avatar 4 of 4"
|
||||||
/>
|
/>
|
||||||
235
docs/pages/components/badge.md
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Badge
|
||||||
|
description: Badges are used to draw attention and display statuses or counts.
|
||||||
|
layout: component
|
||||||
|
---
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-badge>Badge</sl-badge>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
|
||||||
|
|
||||||
|
const App = () => <SlBadge>Badge</SlBadge>;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Variants
|
||||||
|
|
||||||
|
Set the `variant` attribute to change the badge's variant.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-badge variant="primary">Primary</sl-badge>
|
||||||
|
<sl-badge variant="success">Success</sl-badge>
|
||||||
|
<sl-badge variant="neutral">Neutral</sl-badge>
|
||||||
|
<sl-badge variant="warning">Warning</sl-badge>
|
||||||
|
<sl-badge variant="danger">Danger</sl-badge>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlBadge variant="primary">Primary</SlBadge>
|
||||||
|
<SlBadge variant="success">Success</SlBadge>
|
||||||
|
<SlBadge variant="neutral">Neutral</SlBadge>
|
||||||
|
<SlBadge variant="warning">Warning</SlBadge>
|
||||||
|
<SlBadge variant="danger">Danger</SlBadge>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pill Badges
|
||||||
|
|
||||||
|
Use the `pill` attribute to give badges rounded edges.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-badge variant="primary" pill>Primary</sl-badge>
|
||||||
|
<sl-badge variant="success" pill>Success</sl-badge>
|
||||||
|
<sl-badge variant="neutral" pill>Neutral</sl-badge>
|
||||||
|
<sl-badge variant="warning" pill>Warning</sl-badge>
|
||||||
|
<sl-badge variant="danger" pill>Danger</sl-badge>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlBadge variant="primary" pill>
|
||||||
|
Primary
|
||||||
|
</SlBadge>
|
||||||
|
<SlBadge variant="success" pill>
|
||||||
|
Success
|
||||||
|
</SlBadge>
|
||||||
|
<SlBadge variant="neutral" pill>
|
||||||
|
Neutral
|
||||||
|
</SlBadge>
|
||||||
|
<SlBadge variant="warning" pill>
|
||||||
|
Warning
|
||||||
|
</SlBadge>
|
||||||
|
<SlBadge variant="danger" pill>
|
||||||
|
Danger
|
||||||
|
</SlBadge>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pulsating Badges
|
||||||
|
|
||||||
|
Use the `pulse` attribute to draw attention to the badge with a subtle animation.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<div class="badge-pulse">
|
||||||
|
<sl-badge variant="primary" pill pulse>1</sl-badge>
|
||||||
|
<sl-badge variant="success" pill pulse>1</sl-badge>
|
||||||
|
<sl-badge variant="neutral" pill pulse>1</sl-badge>
|
||||||
|
<sl-badge variant="warning" pill pulse>1</sl-badge>
|
||||||
|
<sl-badge variant="danger" pill pulse>1</sl-badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.badge-pulse sl-badge:not(:last-of-type) {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
|
||||||
|
|
||||||
|
const css = `
|
||||||
|
.badge-pulse sl-badge:not(:last-of-type) {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<div className="badge-pulse">
|
||||||
|
<SlBadge variant="primary" pill pulse>
|
||||||
|
1
|
||||||
|
</SlBadge>
|
||||||
|
<SlBadge variant="success" pill pulse>
|
||||||
|
1
|
||||||
|
</SlBadge>
|
||||||
|
<SlBadge variant="neutral" pill pulse>
|
||||||
|
1
|
||||||
|
</SlBadge>
|
||||||
|
<SlBadge variant="warning" pill pulse>
|
||||||
|
1
|
||||||
|
</SlBadge>
|
||||||
|
<SlBadge variant="danger" pill pulse>
|
||||||
|
1
|
||||||
|
</SlBadge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>{css}</style>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Buttons
|
||||||
|
|
||||||
|
One of the most common use cases for badges is attaching them to buttons. To make this easier, badges will be automatically positioned at the top-right when they're a child of a button.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button>
|
||||||
|
Requests
|
||||||
|
<sl-badge pill>30</sl-badge>
|
||||||
|
</sl-button>
|
||||||
|
|
||||||
|
<sl-button style="margin-inline-start: 1rem;">
|
||||||
|
Warnings
|
||||||
|
<sl-badge variant="warning" pill>8</sl-badge>
|
||||||
|
</sl-button>
|
||||||
|
|
||||||
|
<sl-button style="margin-inline-start: 1rem;">
|
||||||
|
Errors
|
||||||
|
<sl-badge variant="danger" pill>6</sl-badge>
|
||||||
|
</sl-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlButton>
|
||||||
|
Requests
|
||||||
|
<SlBadge pill>30</SlBadge>
|
||||||
|
</SlButton>
|
||||||
|
|
||||||
|
<SlButton style={{ marginInlineStart: '1rem' }}>
|
||||||
|
Warnings
|
||||||
|
<SlBadge variant="warning" pill>
|
||||||
|
8
|
||||||
|
</SlBadge>
|
||||||
|
</SlButton>
|
||||||
|
|
||||||
|
<SlButton style={{ marginInlineStart: '1rem' }}>
|
||||||
|
Errors
|
||||||
|
<SlBadge variant="danger" pill>
|
||||||
|
6
|
||||||
|
</SlBadge>
|
||||||
|
</SlButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
### With Menu Items
|
||||||
|
|
||||||
|
When including badges in menu items, use the `suffix` slot to make sure they're aligned correctly.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-menu style="max-width: 240px;">
|
||||||
|
<sl-menu-label>Messages</sl-menu-label>
|
||||||
|
<sl-menu-item>Comments <sl-badge slot="suffix" variant="neutral" pill>4</sl-badge></sl-menu-item>
|
||||||
|
<sl-menu-item>Replies <sl-badge slot="suffix" variant="neutral" pill>12</sl-badge></sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
import SlMenuLabel from '@shoelace-style/shoelace/dist/react/menu-label';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlMenu
|
||||||
|
style={{
|
||||||
|
maxWidth: '240px',
|
||||||
|
border: 'solid 1px var(--sl-panel-border-color)',
|
||||||
|
borderRadius: 'var(--sl-border-radius-medium)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SlMenuLabel>Messages</SlMenuLabel>
|
||||||
|
<SlMenuItem>
|
||||||
|
Comments
|
||||||
|
<SlBadge slot="suffix" variant="neutral" pill>
|
||||||
|
4
|
||||||
|
</SlBadge>
|
||||||
|
</SlMenuItem>
|
||||||
|
<SlMenuItem>
|
||||||
|
Replies
|
||||||
|
<SlBadge slot="suffix" variant="neutral" pill>
|
||||||
|
12
|
||||||
|
</SlBadge>
|
||||||
|
</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
38
docs/pages/components/breadcrumb-item.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Breadcrumb Item
|
||||||
|
description: Breadcrumb Items are used inside breadcrumbs to represent different links.
|
||||||
|
layout: component
|
||||||
|
---
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-breadcrumb>
|
||||||
|
<sl-breadcrumb-item>
|
||||||
|
<sl-icon slot="prefix" name="house"></sl-icon>
|
||||||
|
Home
|
||||||
|
</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>Clothing</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>Shirts</sl-breadcrumb-item>
|
||||||
|
</sl-breadcrumb>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
|
||||||
|
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
|
||||||
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlBreadcrumb>
|
||||||
|
<SlBreadcrumbItem>
|
||||||
|
<SlIcon slot="prefix" name="house"></SlIcon>
|
||||||
|
Home
|
||||||
|
</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>Clothing</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>Shirts</SlBreadcrumbItem>
|
||||||
|
</SlBreadcrumb>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Additional demonstrations can be found in the [breadcrumb examples](/components/breadcrumb).
|
||||||
|
:::
|
||||||
258
docs/pages/components/breadcrumb.md
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Breadcrumb
|
||||||
|
description: Breadcrumbs provide a group of links so users can easily navigate a website's hierarchy.
|
||||||
|
layout: component
|
||||||
|
---
|
||||||
|
|
||||||
|
Breadcrumbs are usually placed before a page's main content with the current page shown last to indicate the user's position in the navigation.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-breadcrumb>
|
||||||
|
<sl-breadcrumb-item>Catalog</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>Clothing</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>Women's</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>Shirts & Tops</sl-breadcrumb-item>
|
||||||
|
</sl-breadcrumb>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
|
||||||
|
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlBreadcrumb>
|
||||||
|
<SlBreadcrumbItem>Catalog</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>Clothing</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>Women's</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>Shirts & Tops</SlBreadcrumbItem>
|
||||||
|
</SlBreadcrumb>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Breadcrumb Links
|
||||||
|
|
||||||
|
By default, breadcrumb items are rendered as buttons so you can use them to navigate single-page applications. In this case, you'll need to add event listeners to handle clicks.
|
||||||
|
|
||||||
|
For websites, you'll probably want to use links instead. You can make any breadcrumb item a link by applying an `href` attribute to it. Now, when the user activates it, they'll be taken to the corresponding page — no event listeners required.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-breadcrumb>
|
||||||
|
<sl-breadcrumb-item href="https://example.com/home">Homepage</sl-breadcrumb-item>
|
||||||
|
|
||||||
|
<sl-breadcrumb-item href="https://example.com/home/services">Our Services</sl-breadcrumb-item>
|
||||||
|
|
||||||
|
<sl-breadcrumb-item href="https://example.com/home/services/digital">Digital Media</sl-breadcrumb-item>
|
||||||
|
|
||||||
|
<sl-breadcrumb-item href="https://example.com/home/services/digital/web-design">Web Design</sl-breadcrumb-item>
|
||||||
|
</sl-breadcrumb>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
|
||||||
|
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlBreadcrumb>
|
||||||
|
<SlBreadcrumbItem href="https://example.com/home">Homepage</SlBreadcrumbItem>
|
||||||
|
|
||||||
|
<SlBreadcrumbItem href="https://example.com/home/services">Our Services</SlBreadcrumbItem>
|
||||||
|
|
||||||
|
<SlBreadcrumbItem href="https://example.com/home/services/digital">Digital Media</SlBreadcrumbItem>
|
||||||
|
|
||||||
|
<SlBreadcrumbItem href="https://example.com/home/services/digital/web-design">Web Design</SlBreadcrumbItem>
|
||||||
|
</SlBreadcrumb>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Separators
|
||||||
|
|
||||||
|
Use the `separator` slot to change the separator that goes between breadcrumb items. Icons work well, but you can also use text or an image.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-breadcrumb>
|
||||||
|
<sl-icon name="dot" slot="separator"></sl-icon>
|
||||||
|
<sl-breadcrumb-item>First</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>Second</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>Third</sl-breadcrumb-item>
|
||||||
|
</sl-breadcrumb>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<sl-breadcrumb>
|
||||||
|
<sl-icon name="arrow-right" slot="separator"></sl-icon>
|
||||||
|
<sl-breadcrumb-item>First</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>Second</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>Third</sl-breadcrumb-item>
|
||||||
|
</sl-breadcrumb>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<sl-breadcrumb>
|
||||||
|
<span slot="separator">/</span>
|
||||||
|
<sl-breadcrumb-item>First</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>Second</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>Third</sl-breadcrumb-item>
|
||||||
|
</sl-breadcrumb>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import '@shoelace-style/shoelace/dist/components/icon/icon.js';
|
||||||
|
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
|
||||||
|
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlBreadcrumb>
|
||||||
|
<sl-icon name="dot" slot="separator" />
|
||||||
|
<SlBreadcrumbItem>First</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>Second</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>Third</SlBreadcrumbItem>
|
||||||
|
</SlBreadcrumb>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<SlBreadcrumb>
|
||||||
|
<sl-icon name="arrow-right" slot="separator" />
|
||||||
|
<SlBreadcrumbItem>First</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>Second</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>Third</SlBreadcrumbItem>
|
||||||
|
</SlBreadcrumb>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<SlBreadcrumb>
|
||||||
|
<span slot="separator">/</span>
|
||||||
|
<SlBreadcrumbItem>First</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>Second</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>Third</SlBreadcrumbItem>
|
||||||
|
</SlBreadcrumb>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prefixes
|
||||||
|
|
||||||
|
Use the `prefix` slot to add content before any breadcrumb item.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-breadcrumb>
|
||||||
|
<sl-breadcrumb-item>
|
||||||
|
<sl-icon slot="prefix" name="house"></sl-icon>
|
||||||
|
Home
|
||||||
|
</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>Articles</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>Traveling</sl-breadcrumb-item>
|
||||||
|
</sl-breadcrumb>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
|
||||||
|
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
|
||||||
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlBreadcrumb>
|
||||||
|
<SlBreadcrumbItem>
|
||||||
|
<SlIcon slot="prefix" name="house" />
|
||||||
|
Home
|
||||||
|
</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>Articles</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>Traveling</SlBreadcrumbItem>
|
||||||
|
</SlBreadcrumb>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Suffixes
|
||||||
|
|
||||||
|
Use the `suffix` slot to add content after any breadcrumb item.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-breadcrumb>
|
||||||
|
<sl-breadcrumb-item>Documents</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>Policies</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>
|
||||||
|
Security
|
||||||
|
<sl-icon slot="suffix" name="shield-lock"></sl-icon>
|
||||||
|
</sl-breadcrumb-item>
|
||||||
|
</sl-breadcrumb>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
|
||||||
|
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
|
||||||
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlBreadcrumb>
|
||||||
|
<SlBreadcrumbItem>Documents</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>Policies</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>
|
||||||
|
Security
|
||||||
|
<SlIcon slot="suffix" name="shield-lock"></SlIcon>
|
||||||
|
</SlBreadcrumbItem>
|
||||||
|
</SlBreadcrumb>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Dropdowns
|
||||||
|
|
||||||
|
Dropdown menus can be placed in a prefix or suffix slot to provide additional options.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-breadcrumb>
|
||||||
|
<sl-breadcrumb-item>Homepage</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>Our Services</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>Digital Media</sl-breadcrumb-item>
|
||||||
|
<sl-breadcrumb-item>
|
||||||
|
Web Design
|
||||||
|
<sl-dropdown slot="suffix">
|
||||||
|
<sl-button slot="trigger" size="small" circle>
|
||||||
|
<sl-icon label="More options" name="three-dots"></sl-icon>
|
||||||
|
</sl-button>
|
||||||
|
<sl-menu>
|
||||||
|
<sl-menu-item type="checkbox" checked>Web Design</sl-menu-item>
|
||||||
|
<sl-menu-item type="checkbox">Web Development</sl-menu-item>
|
||||||
|
<sl-menu-item type="checkbox">Marketing</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
</sl-dropdown>
|
||||||
|
</sl-breadcrumb-item>
|
||||||
|
</sl-breadcrumb>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import {
|
||||||
|
SlBreadcrumb,
|
||||||
|
SlBreadcrumbItem,
|
||||||
|
SlButton,
|
||||||
|
SlDropdown,
|
||||||
|
SlIcon,
|
||||||
|
SlMenu,
|
||||||
|
SlMenuItem
|
||||||
|
} from '@shoelace-style/shoelace/dist/react';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlBreadcrumb>
|
||||||
|
<SlBreadcrumbItem>Homepage</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>Our Services</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>Digital Media</SlBreadcrumbItem>
|
||||||
|
<SlBreadcrumbItem>
|
||||||
|
Web Design
|
||||||
|
<SlDropdown slot="suffix">
|
||||||
|
<SlButton slot="trigger" size="small" circle>
|
||||||
|
<SlIcon label="More options" name="three-dots"></SlIcon>
|
||||||
|
</SlButton>
|
||||||
|
<SlMenu>
|
||||||
|
<SlMenuItem type="checkbox" checked>
|
||||||
|
Web Design
|
||||||
|
</SlMenuItem>
|
||||||
|
<SlMenuItem type="checkbox">Web Development</SlMenuItem>
|
||||||
|
<SlMenuItem type="checkbox">Marketing</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
</SlDropdown>
|
||||||
|
</SlBreadcrumbItem>
|
||||||
|
</SlBreadcrumb>
|
||||||
|
);
|
||||||
|
```
|
||||||
511
docs/pages/components/button-group.md
Normal file
@@ -0,0 +1,511 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Button Group
|
||||||
|
description: Button groups can be used to group related buttons into sections.
|
||||||
|
layout: component
|
||||||
|
---
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button-group label="Alignment">
|
||||||
|
<sl-button>Left</sl-button>
|
||||||
|
<sl-button>Center</sl-button>
|
||||||
|
<sl-button>Right</sl-button>
|
||||||
|
</sl-button-group>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlButtonGroup label="Alignment">
|
||||||
|
<SlButton>Left</SlButton>
|
||||||
|
<SlButton>Center</SlButton>
|
||||||
|
<SlButton>Right</SlButton>
|
||||||
|
</SlButtonGroup>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Button Sizes
|
||||||
|
|
||||||
|
All button sizes are supported, but avoid mixing sizes within the same button group.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button-group label="Alignment">
|
||||||
|
<sl-button size="small">Left</sl-button>
|
||||||
|
<sl-button size="small">Center</sl-button>
|
||||||
|
<sl-button size="small">Right</sl-button>
|
||||||
|
</sl-button-group>
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
<sl-button-group label="Alignment">
|
||||||
|
<sl-button size="medium">Left</sl-button>
|
||||||
|
<sl-button size="medium">Center</sl-button>
|
||||||
|
<sl-button size="medium">Right</sl-button>
|
||||||
|
</sl-button-group>
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
<sl-button-group label="Alignment">
|
||||||
|
<sl-button size="large">Left</sl-button>
|
||||||
|
<sl-button size="large">Center</sl-button>
|
||||||
|
<sl-button size="large">Right</sl-button>
|
||||||
|
</sl-button-group>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlButtonGroup label="Alignment">
|
||||||
|
<SlButton size="small">Left</SlButton>
|
||||||
|
<SlButton size="small">Center</SlButton>
|
||||||
|
<SlButton size="small">Right</SlButton>
|
||||||
|
</SlButtonGroup>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<SlButtonGroup label="Alignment">
|
||||||
|
<SlButton size="medium">Left</SlButton>
|
||||||
|
<SlButton size="medium">Center</SlButton>
|
||||||
|
<SlButton size="medium">Right</SlButton>
|
||||||
|
</SlButtonGroup>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<SlButtonGroup label="Alignment">
|
||||||
|
<SlButton size="large">Left</SlButton>
|
||||||
|
<SlButton size="large">Center</SlButton>
|
||||||
|
<SlButton size="large">Right</SlButton>
|
||||||
|
</SlButtonGroup>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Theme Buttons
|
||||||
|
|
||||||
|
Theme buttons are supported through the button's `variant` attribute.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button-group label="Alignment">
|
||||||
|
<sl-button variant="primary">Left</sl-button>
|
||||||
|
<sl-button variant="primary">Center</sl-button>
|
||||||
|
<sl-button variant="primary">Right</sl-button>
|
||||||
|
</sl-button-group>
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
<sl-button-group label="Alignment">
|
||||||
|
<sl-button variant="success">Left</sl-button>
|
||||||
|
<sl-button variant="success">Center</sl-button>
|
||||||
|
<sl-button variant="success">Right</sl-button>
|
||||||
|
</sl-button-group>
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
<sl-button-group label="Alignment">
|
||||||
|
<sl-button variant="neutral">Left</sl-button>
|
||||||
|
<sl-button variant="neutral">Center</sl-button>
|
||||||
|
<sl-button variant="neutral">Right</sl-button>
|
||||||
|
</sl-button-group>
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
<sl-button-group label="Alignment">
|
||||||
|
<sl-button variant="warning">Left</sl-button>
|
||||||
|
<sl-button variant="warning">Center</sl-button>
|
||||||
|
<sl-button variant="warning">Right</sl-button>
|
||||||
|
</sl-button-group>
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
<sl-button-group label="Alignment">
|
||||||
|
<sl-button variant="danger">Left</sl-button>
|
||||||
|
<sl-button variant="danger">Center</sl-button>
|
||||||
|
<sl-button variant="danger">Right</sl-button>
|
||||||
|
</sl-button-group>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlButtonGroup label="Alignment">
|
||||||
|
<SlButton variant="primary">Left</SlButton>
|
||||||
|
<SlButton variant="primary">Center</SlButton>
|
||||||
|
<SlButton variant="primary">Right</SlButton>
|
||||||
|
</SlButtonGroup>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<SlButtonGroup label="Alignment">
|
||||||
|
<SlButton variant="success">Left</SlButton>
|
||||||
|
<SlButton variant="success">Center</SlButton>
|
||||||
|
<SlButton variant="success">Right</SlButton>
|
||||||
|
</SlButtonGroup>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<SlButtonGroup label="Alignment">
|
||||||
|
<SlButton variant="neutral">Left</SlButton>
|
||||||
|
<SlButton variant="neutral">Center</SlButton>
|
||||||
|
<SlButton variant="neutral">Right</SlButton>
|
||||||
|
</SlButtonGroup>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<SlButtonGroup label="Alignment">
|
||||||
|
<SlButton variant="warning">Left</SlButton>
|
||||||
|
<SlButton variant="warning">Center</SlButton>
|
||||||
|
<SlButton variant="warning">Right</SlButton>
|
||||||
|
</SlButtonGroup>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<SlButtonGroup label="Alignment">
|
||||||
|
<SlButton variant="danger">Left</SlButton>
|
||||||
|
<SlButton variant="danger">Center</SlButton>
|
||||||
|
<SlButton variant="danger">Right</SlButton>
|
||||||
|
</SlButtonGroup>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pill Buttons
|
||||||
|
|
||||||
|
Pill buttons are supported through the button's `pill` attribute.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button-group label="Alignment">
|
||||||
|
<sl-button size="small" pill>Left</sl-button>
|
||||||
|
<sl-button size="small" pill>Center</sl-button>
|
||||||
|
<sl-button size="small" pill>Right</sl-button>
|
||||||
|
</sl-button-group>
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
<sl-button-group label="Alignment">
|
||||||
|
<sl-button size="medium" pill>Left</sl-button>
|
||||||
|
<sl-button size="medium" pill>Center</sl-button>
|
||||||
|
<sl-button size="medium" pill>Right</sl-button>
|
||||||
|
</sl-button-group>
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
<sl-button-group label="Alignment">
|
||||||
|
<sl-button size="large" pill>Left</sl-button>
|
||||||
|
<sl-button size="large" pill>Center</sl-button>
|
||||||
|
<sl-button size="large" pill>Right</sl-button>
|
||||||
|
</sl-button-group>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlButtonGroup label="Alignment">
|
||||||
|
<SlButton size="small" pill>
|
||||||
|
Left
|
||||||
|
</SlButton>
|
||||||
|
<SlButton size="small" pill>
|
||||||
|
Center
|
||||||
|
</SlButton>
|
||||||
|
<SlButton size="small" pill>
|
||||||
|
Right
|
||||||
|
</SlButton>
|
||||||
|
</SlButtonGroup>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<SlButtonGroup label="Alignment">
|
||||||
|
<SlButton size="medium" pill>
|
||||||
|
Left
|
||||||
|
</SlButton>
|
||||||
|
<SlButton size="medium" pill>
|
||||||
|
Center
|
||||||
|
</SlButton>
|
||||||
|
<SlButton size="medium" pill>
|
||||||
|
Right
|
||||||
|
</SlButton>
|
||||||
|
</SlButtonGroup>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<SlButtonGroup label="Alignment">
|
||||||
|
<SlButton size="large" pill>
|
||||||
|
Left
|
||||||
|
</SlButton>
|
||||||
|
<SlButton size="large" pill>
|
||||||
|
Center
|
||||||
|
</SlButton>
|
||||||
|
<SlButton size="large" pill>
|
||||||
|
Right
|
||||||
|
</SlButton>
|
||||||
|
</SlButtonGroup>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dropdowns in Button Groups
|
||||||
|
|
||||||
|
Dropdowns can be placed inside button groups as long as the trigger is an `<sl-button>` element.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button-group label="Example Button Group">
|
||||||
|
<sl-button>Button</sl-button>
|
||||||
|
<sl-button>Button</sl-button>
|
||||||
|
<sl-dropdown>
|
||||||
|
<sl-button slot="trigger" caret>Dropdown</sl-button>
|
||||||
|
<sl-menu>
|
||||||
|
<sl-menu-item>Item 1</sl-menu-item>
|
||||||
|
<sl-menu-item>Item 2</sl-menu-item>
|
||||||
|
<sl-menu-item>Item 3</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
</sl-dropdown>
|
||||||
|
</sl-button-group>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
|
||||||
|
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlButtonGroup label="Example Button Group">
|
||||||
|
<SlButton>Button</SlButton>
|
||||||
|
<SlButton>Button</SlButton>
|
||||||
|
<SlDropdown>
|
||||||
|
<SlButton slot="trigger" caret>
|
||||||
|
Dropdown
|
||||||
|
</SlButton>
|
||||||
|
<SlMenu>
|
||||||
|
<SlMenuItem>Item 1</SlMenuItem>
|
||||||
|
<SlMenuItem>Item 2</SlMenuItem>
|
||||||
|
<SlMenuItem>Item 3</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
</SlDropdown>
|
||||||
|
</SlButtonGroup>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Split Buttons
|
||||||
|
|
||||||
|
Create a split button using a button and a dropdown. Use a [visually hidden](/components/visually-hidden) label to ensure the dropdown is accessible to users with assistive devices.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button-group label="Example Button Group">
|
||||||
|
<sl-button variant="primary">Save</sl-button>
|
||||||
|
<sl-dropdown placement="bottom-end">
|
||||||
|
<sl-button slot="trigger" variant="primary" caret>
|
||||||
|
<sl-visually-hidden>More options</sl-visually-hidden>
|
||||||
|
</sl-button>
|
||||||
|
<sl-menu>
|
||||||
|
<sl-menu-item>Save</sl-menu-item>
|
||||||
|
<sl-menu-item>Save as…</sl-menu-item>
|
||||||
|
<sl-menu-item>Save all</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
</sl-dropdown>
|
||||||
|
</sl-button-group>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
|
||||||
|
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlButtonGroup label="Example Button Group">
|
||||||
|
<SlButton variant="primary">Save</SlButton>
|
||||||
|
<SlDropdown placement="bottom-end">
|
||||||
|
<SlButton slot="trigger" variant="primary" caret></SlButton>
|
||||||
|
<SlMenu>
|
||||||
|
<SlMenuItem>Save</SlMenuItem>
|
||||||
|
<SlMenuItem>Save as…</SlMenuItem>
|
||||||
|
<SlMenuItem>Save all</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
</SlDropdown>
|
||||||
|
</SlButtonGroup>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tooltips in Button Groups
|
||||||
|
|
||||||
|
Buttons can be wrapped in tooltips to provide more detail when the user interacts with them.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button-group label="Alignment">
|
||||||
|
<sl-tooltip content="I'm on the left">
|
||||||
|
<sl-button>Left</sl-button>
|
||||||
|
</sl-tooltip>
|
||||||
|
|
||||||
|
<sl-tooltip content="I'm in the middle">
|
||||||
|
<sl-button>Center</sl-button>
|
||||||
|
</sl-tooltip>
|
||||||
|
|
||||||
|
<sl-tooltip content="I'm on the right">
|
||||||
|
<sl-button>Right</sl-button>
|
||||||
|
</sl-tooltip>
|
||||||
|
</sl-button-group>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
|
||||||
|
import SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlButtonGroup label="Alignment">
|
||||||
|
<SlTooltip content="I'm on the left">
|
||||||
|
<SlButton>Left</SlButton>
|
||||||
|
</SlTooltip>
|
||||||
|
|
||||||
|
<SlTooltip content="I'm in the middle">
|
||||||
|
<SlButton>Center</SlButton>
|
||||||
|
</SlTooltip>
|
||||||
|
|
||||||
|
<SlTooltip content="I'm on the right">
|
||||||
|
<SlButton>Right</SlButton>
|
||||||
|
</SlTooltip>
|
||||||
|
</SlButtonGroup>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Toolbar Example
|
||||||
|
|
||||||
|
Create interactive toolbars with button groups.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<div class="button-group-toolbar">
|
||||||
|
<sl-button-group label="History">
|
||||||
|
<sl-tooltip content="Undo">
|
||||||
|
<sl-button><sl-icon name="arrow-counterclockwise" label="Undo"></sl-icon></sl-button>
|
||||||
|
</sl-tooltip>
|
||||||
|
<sl-tooltip content="Redo">
|
||||||
|
<sl-button><sl-icon name="arrow-clockwise" label="Redo"></sl-icon></sl-button>
|
||||||
|
</sl-tooltip>
|
||||||
|
</sl-button-group>
|
||||||
|
|
||||||
|
<sl-button-group label="Formatting">
|
||||||
|
<sl-tooltip content="Bold">
|
||||||
|
<sl-button><sl-icon name="type-bold" label="Bold"></sl-icon></sl-button>
|
||||||
|
</sl-tooltip>
|
||||||
|
<sl-tooltip content="Italic">
|
||||||
|
<sl-button><sl-icon name="type-italic" label="Italic"></sl-icon></sl-button>
|
||||||
|
</sl-tooltip>
|
||||||
|
<sl-tooltip content="Underline">
|
||||||
|
<sl-button><sl-icon name="type-underline" label="Underline"></sl-icon></sl-button>
|
||||||
|
</sl-tooltip>
|
||||||
|
</sl-button-group>
|
||||||
|
|
||||||
|
<sl-button-group label="Alignment">
|
||||||
|
<sl-tooltip content="Align Left">
|
||||||
|
<sl-button><sl-icon name="justify-left" label="Align Left"></sl-icon></sl-button>
|
||||||
|
</sl-tooltip>
|
||||||
|
<sl-tooltip content="Align Center">
|
||||||
|
<sl-button><sl-icon name="justify" label="Align Center"></sl-icon></sl-button>
|
||||||
|
</sl-tooltip>
|
||||||
|
<sl-tooltip content="Align Right">
|
||||||
|
<sl-button><sl-icon name="justify-right" label="Align Right"></sl-icon></sl-button>
|
||||||
|
</sl-tooltip>
|
||||||
|
</sl-button-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.button-group-toolbar sl-button-group:not(:last-of-type) {
|
||||||
|
margin-right: var(--sl-spacing-x-small);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
|
||||||
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
import SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';
|
||||||
|
|
||||||
|
const css = `
|
||||||
|
.button-group-toolbar sl-button-group:not(:last-of-type) {
|
||||||
|
margin-right: var(--sl-spacing-x-small);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<div className="button-group-toolbar">
|
||||||
|
<SlButtonGroup label="History">
|
||||||
|
<SlTooltip content="Undo">
|
||||||
|
<SlButton>
|
||||||
|
<SlIcon name="arrow-counterclockwise"></SlIcon>
|
||||||
|
</SlButton>
|
||||||
|
</SlTooltip>
|
||||||
|
<SlTooltip content="Redo">
|
||||||
|
<SlButton>
|
||||||
|
<SlIcon name="arrow-clockwise"></SlIcon>
|
||||||
|
</SlButton>
|
||||||
|
</SlTooltip>
|
||||||
|
</SlButtonGroup>
|
||||||
|
|
||||||
|
<SlButtonGroup label="Formatting">
|
||||||
|
<SlTooltip content="Bold">
|
||||||
|
<SlButton>
|
||||||
|
<SlIcon name="type-bold"></SlIcon>
|
||||||
|
</SlButton>
|
||||||
|
</SlTooltip>
|
||||||
|
<SlTooltip content="Italic">
|
||||||
|
<SlButton>
|
||||||
|
<SlIcon name="type-italic"></SlIcon>
|
||||||
|
</SlButton>
|
||||||
|
</SlTooltip>
|
||||||
|
<SlTooltip content="Underline">
|
||||||
|
<SlButton>
|
||||||
|
<SlIcon name="type-underline"></SlIcon>
|
||||||
|
</SlButton>
|
||||||
|
</SlTooltip>
|
||||||
|
</SlButtonGroup>
|
||||||
|
|
||||||
|
<SlButtonGroup label="Alignment">
|
||||||
|
<SlTooltip content="Align Left">
|
||||||
|
<SlButton>
|
||||||
|
<SlIcon name="justify-left"></SlIcon>
|
||||||
|
</SlButton>
|
||||||
|
</SlTooltip>
|
||||||
|
<SlTooltip content="Align Center">
|
||||||
|
<SlButton>
|
||||||
|
<SlIcon name="justify"></SlIcon>
|
||||||
|
</SlButton>
|
||||||
|
</SlTooltip>
|
||||||
|
<SlTooltip content="Align Right">
|
||||||
|
<SlButton>
|
||||||
|
<SlIcon name="justify-right"></SlIcon>
|
||||||
|
</SlButton>
|
||||||
|
</SlTooltip>
|
||||||
|
</SlButtonGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>{css}</style>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
545
docs/pages/components/button.md
Normal file
@@ -0,0 +1,545 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Button
|
||||||
|
description: Buttons represent actions that are available to the user.
|
||||||
|
layout: component
|
||||||
|
---
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button>Button</sl-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
|
||||||
|
const App = () => <SlButton>Button</SlButton>;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Variants
|
||||||
|
|
||||||
|
Use the `variant` attribute to set the button's variant.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button variant="default">Default</sl-button>
|
||||||
|
<sl-button variant="primary">Primary</sl-button>
|
||||||
|
<sl-button variant="success">Success</sl-button>
|
||||||
|
<sl-button variant="neutral">Neutral</sl-button>
|
||||||
|
<sl-button variant="warning">Warning</sl-button>
|
||||||
|
<sl-button variant="danger">Danger</sl-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlButton variant="default">Default</SlButton>
|
||||||
|
<SlButton variant="primary">Primary</SlButton>
|
||||||
|
<SlButton variant="success">Success</SlButton>
|
||||||
|
<SlButton variant="neutral">Neutral</SlButton>
|
||||||
|
<SlButton variant="warning">Warning</SlButton>
|
||||||
|
<SlButton variant="danger">Danger</SlButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sizes
|
||||||
|
|
||||||
|
Use the `size` attribute to change a button's size.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button size="small">Small</sl-button>
|
||||||
|
<sl-button size="medium">Medium</sl-button>
|
||||||
|
<sl-button size="large">Large</sl-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlButton size="small">Small</SlButton>
|
||||||
|
<SlButton size="medium">Medium</SlButton>
|
||||||
|
<SlButton size="large">Large</SlButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Outline Buttons
|
||||||
|
|
||||||
|
Use the `outline` attribute to draw outlined buttons with transparent backgrounds.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button variant="default" outline>Default</sl-button>
|
||||||
|
<sl-button variant="primary" outline>Primary</sl-button>
|
||||||
|
<sl-button variant="success" outline>Success</sl-button>
|
||||||
|
<sl-button variant="neutral" outline>Neutral</sl-button>
|
||||||
|
<sl-button variant="warning" outline>Warning</sl-button>
|
||||||
|
<sl-button variant="danger" outline>Danger</sl-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlButton variant="default" outline>
|
||||||
|
Default
|
||||||
|
</SlButton>
|
||||||
|
<SlButton variant="primary" outline>
|
||||||
|
Primary
|
||||||
|
</SlButton>
|
||||||
|
<SlButton variant="success" outline>
|
||||||
|
Success
|
||||||
|
</SlButton>
|
||||||
|
<SlButton variant="neutral" outline>
|
||||||
|
Neutral
|
||||||
|
</SlButton>
|
||||||
|
<SlButton variant="warning" outline>
|
||||||
|
Warning
|
||||||
|
</SlButton>
|
||||||
|
<SlButton variant="danger" outline>
|
||||||
|
Danger
|
||||||
|
</SlButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pill Buttons
|
||||||
|
|
||||||
|
Use the `pill` attribute to give buttons rounded edges.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button size="small" pill>Small</sl-button>
|
||||||
|
<sl-button size="medium" pill>Medium</sl-button>
|
||||||
|
<sl-button size="large" pill>Large</sl-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlButton size="small" pill>
|
||||||
|
Small
|
||||||
|
</SlButton>
|
||||||
|
<SlButton size="medium" pill>
|
||||||
|
Medium
|
||||||
|
</SlButton>
|
||||||
|
<SlButton size="large" pill>
|
||||||
|
Large
|
||||||
|
</SlButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Circle Buttons
|
||||||
|
|
||||||
|
Use the `circle` attribute to create circular icon buttons. When this attribute is set, the button expects a single `<sl-icon>` in the default slot.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button variant="default" size="small" circle>
|
||||||
|
<sl-icon name="gear" label="Settings"></sl-icon>
|
||||||
|
</sl-button>
|
||||||
|
|
||||||
|
<sl-button variant="default" size="medium" circle>
|
||||||
|
<sl-icon name="gear" label="Settings"></sl-icon>
|
||||||
|
</sl-button>
|
||||||
|
|
||||||
|
<sl-button variant="default" size="large" circle>
|
||||||
|
<sl-icon name="gear" label="Settings"></sl-icon>
|
||||||
|
</sl-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlButton variant="default" size="small" circle>
|
||||||
|
<SlIcon name="gear" />
|
||||||
|
</SlButton>
|
||||||
|
<SlButton variant="default" size="medium" circle>
|
||||||
|
<SlIcon name="gear" />
|
||||||
|
</SlButton>
|
||||||
|
<SlButton variant="default" size="large" circle>
|
||||||
|
<SlIcon name="gear" />
|
||||||
|
</SlButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Text Buttons
|
||||||
|
|
||||||
|
Use the `text` variant to create text buttons that share the same size as regular buttons but don't have backgrounds or borders.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button variant="text" size="small">Text</sl-button>
|
||||||
|
<sl-button variant="text" size="medium">Text</sl-button>
|
||||||
|
<sl-button variant="text" size="large">Text</sl-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlButton variant="text" size="small">
|
||||||
|
Text
|
||||||
|
</SlButton>
|
||||||
|
<SlButton variant="text" size="medium">
|
||||||
|
Text
|
||||||
|
</SlButton>
|
||||||
|
<SlButton variant="text" size="large">
|
||||||
|
Text
|
||||||
|
</SlButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Link Buttons
|
||||||
|
|
||||||
|
It's often helpful to have a button that works like a link. This is possible by setting the `href` attribute, which will make the component render an `<a>` under the hood. This gives you all the default link behavior the browser provides (e.g. [[CMD/CTRL/SHIFT]] + [[CLICK]]) and exposes the `target` and `download` attributes.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button href="https://example.com/">Link</sl-button>
|
||||||
|
<sl-button href="https://example.com/" target="_blank">New Window</sl-button>
|
||||||
|
<sl-button href="/assets/images/wordmark.svg" download="shoelace.svg">Download</sl-button>
|
||||||
|
<sl-button href="https://example.com/" disabled>Disabled</sl-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlButton href="https://example.com/">Link</SlButton>
|
||||||
|
<SlButton href="https://example.com/" target="_blank">
|
||||||
|
New Window
|
||||||
|
</SlButton>
|
||||||
|
<SlButton href="/assets/images/wordmark.svg" download="shoelace.svg">
|
||||||
|
Download
|
||||||
|
</SlButton>
|
||||||
|
<SlButton href="https://example.com/" disabled>
|
||||||
|
Disabled
|
||||||
|
</SlButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
When a `target` is set, the link will receive `rel="noreferrer noopener"` for [security reasons](https://mathiasbynens.github.io/rel-noopener/).
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Setting a Custom Width
|
||||||
|
|
||||||
|
As expected, buttons can be given a custom width by setting the `width` attribute. This is useful for making buttons span the full width of their container on smaller screens.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button variant="default" size="small" style="width: 100%; margin-bottom: 1rem;">Small</sl-button>
|
||||||
|
<sl-button variant="default" size="medium" style="width: 100%; margin-bottom: 1rem;">Medium</sl-button>
|
||||||
|
<sl-button variant="default" size="large" style="width: 100%;">Large</sl-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlButton variant="default" size="small" style={{ width: '100%', marginBottom: '1rem' }}>
|
||||||
|
Small
|
||||||
|
</SlButton>
|
||||||
|
<SlButton variant="default" size="medium" style={{ width: '100%', marginBottom: '1rem' }}>
|
||||||
|
Medium
|
||||||
|
</SlButton>
|
||||||
|
<SlButton variant="default" size="large" style={{ width: '100%' }}>
|
||||||
|
Large
|
||||||
|
</SlButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
### Prefix and Suffix Icons
|
||||||
|
|
||||||
|
Use the `prefix` and `suffix` slots to add icons.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button variant="default" size="small">
|
||||||
|
<sl-icon slot="prefix" name="gear"></sl-icon>
|
||||||
|
Settings
|
||||||
|
</sl-button>
|
||||||
|
|
||||||
|
<sl-button variant="default" size="small">
|
||||||
|
<sl-icon slot="suffix" name="arrow-counterclockwise"></sl-icon>
|
||||||
|
Refresh
|
||||||
|
</sl-button>
|
||||||
|
|
||||||
|
<sl-button variant="default" size="small">
|
||||||
|
<sl-icon slot="prefix" name="link-45deg"></sl-icon>
|
||||||
|
<sl-icon slot="suffix" name="box-arrow-up-right"></sl-icon>
|
||||||
|
Open
|
||||||
|
</sl-button>
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
<sl-button variant="default">
|
||||||
|
<sl-icon slot="prefix" name="gear"></sl-icon>
|
||||||
|
Settings
|
||||||
|
</sl-button>
|
||||||
|
|
||||||
|
<sl-button variant="default">
|
||||||
|
<sl-icon slot="suffix" name="arrow-counterclockwise"></sl-icon>
|
||||||
|
Refresh
|
||||||
|
</sl-button>
|
||||||
|
|
||||||
|
<sl-button variant="default">
|
||||||
|
<sl-icon slot="prefix" name="link-45deg"></sl-icon>
|
||||||
|
<sl-icon slot="suffix" name="box-arrow-up-right"></sl-icon>
|
||||||
|
Open
|
||||||
|
</sl-button>
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
<sl-button variant="default" size="large">
|
||||||
|
<sl-icon slot="prefix" name="gear"></sl-icon>
|
||||||
|
Settings
|
||||||
|
</sl-button>
|
||||||
|
|
||||||
|
<sl-button variant="default" size="large">
|
||||||
|
<sl-icon slot="suffix" name="arrow-counterclockwise"></sl-icon>
|
||||||
|
Refresh
|
||||||
|
</sl-button>
|
||||||
|
|
||||||
|
<sl-button variant="default" size="large">
|
||||||
|
<sl-icon slot="prefix" name="link-45deg"></sl-icon>
|
||||||
|
<sl-icon slot="suffix" name="box-arrow-up-right"></sl-icon>
|
||||||
|
Open
|
||||||
|
</sl-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlButton variant="default" size="small">
|
||||||
|
<SlIcon slot="prefix" name="gear"></SlIcon>
|
||||||
|
Settings
|
||||||
|
</SlButton>
|
||||||
|
|
||||||
|
<SlButton variant="default" size="small">
|
||||||
|
<SlIcon slot="suffix" name="arrow-counterclockwise"></SlIcon>
|
||||||
|
Refresh
|
||||||
|
</SlButton>
|
||||||
|
|
||||||
|
<SlButton variant="default" size="small">
|
||||||
|
<SlIcon slot="prefix" name="link-45deg"></SlIcon>
|
||||||
|
<SlIcon slot="suffix" name="box-arrow-up-right"></SlIcon>
|
||||||
|
Open
|
||||||
|
</SlButton>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<SlButton variant="default">
|
||||||
|
<SlIcon slot="prefix" name="gear"></SlIcon>
|
||||||
|
Settings
|
||||||
|
</SlButton>
|
||||||
|
|
||||||
|
<SlButton variant="default">
|
||||||
|
<SlIcon slot="suffix" name="arrow-counterclockwise"></SlIcon>
|
||||||
|
Refresh
|
||||||
|
</SlButton>
|
||||||
|
|
||||||
|
<SlButton variant="default">
|
||||||
|
<SlIcon slot="prefix" name="link-45deg"></SlIcon>
|
||||||
|
<SlIcon slot="suffix" name="box-arrow-up-right"></SlIcon>
|
||||||
|
Open
|
||||||
|
</SlButton>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<SlButton variant="default" size="large">
|
||||||
|
<SlIcon slot="prefix" name="gear"></SlIcon>
|
||||||
|
Settings
|
||||||
|
</SlButton>
|
||||||
|
|
||||||
|
<SlButton variant="default" size="large">
|
||||||
|
<SlIcon slot="suffix" name="arrow-counterclockwise"></SlIcon>
|
||||||
|
Refresh
|
||||||
|
</SlButton>
|
||||||
|
|
||||||
|
<SlButton variant="default" size="large">
|
||||||
|
<SlIcon slot="prefix" name="link-45deg"></SlIcon>
|
||||||
|
<SlIcon slot="suffix" name="box-arrow-up-right"></SlIcon>
|
||||||
|
Open
|
||||||
|
</SlButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Caret
|
||||||
|
|
||||||
|
Use the `caret` attribute to add a dropdown indicator when a button will trigger a dropdown, menu, or popover.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button size="small" caret>Small</sl-button>
|
||||||
|
<sl-button size="medium" caret>Medium</sl-button>
|
||||||
|
<sl-button size="large" caret>Large</sl-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlButton size="small" caret>
|
||||||
|
Small
|
||||||
|
</SlButton>
|
||||||
|
<SlButton size="medium" caret>
|
||||||
|
Medium
|
||||||
|
</SlButton>
|
||||||
|
<SlButton size="large" caret>
|
||||||
|
Large
|
||||||
|
</SlButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button variant="default" loading>Default</sl-button>
|
||||||
|
<sl-button variant="primary" loading>Primary</sl-button>
|
||||||
|
<sl-button variant="success" loading>Success</sl-button>
|
||||||
|
<sl-button variant="neutral" loading>Neutral</sl-button>
|
||||||
|
<sl-button variant="warning" loading>Warning</sl-button>
|
||||||
|
<sl-button variant="danger" loading>Danger</sl-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlButton variant="default" loading>
|
||||||
|
Default
|
||||||
|
</SlButton>
|
||||||
|
<SlButton variant="primary" loading>
|
||||||
|
Primary
|
||||||
|
</SlButton>
|
||||||
|
<SlButton variant="success" loading>
|
||||||
|
Success
|
||||||
|
</SlButton>
|
||||||
|
<SlButton variant="neutral" loading>
|
||||||
|
Neutral
|
||||||
|
</SlButton>
|
||||||
|
<SlButton variant="warning" loading>
|
||||||
|
Warning
|
||||||
|
</SlButton>
|
||||||
|
<SlButton variant="danger" loading>
|
||||||
|
Danger
|
||||||
|
</SlButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disabled
|
||||||
|
|
||||||
|
Use the `disabled` attribute to disable a button.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button variant="default" disabled>Default</sl-button>
|
||||||
|
<sl-button variant="primary" disabled>Primary</sl-button>
|
||||||
|
<sl-button variant="success" disabled>Success</sl-button>
|
||||||
|
<sl-button variant="neutral" disabled>Neutral</sl-button>
|
||||||
|
<sl-button variant="warning" disabled>Warning</sl-button>
|
||||||
|
<sl-button variant="danger" disabled>Danger</sl-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlButton variant="default" disabled>
|
||||||
|
Default
|
||||||
|
</SlButton>
|
||||||
|
|
||||||
|
<SlButton variant="primary" disabled>
|
||||||
|
Primary
|
||||||
|
</SlButton>
|
||||||
|
|
||||||
|
<SlButton variant="success" disabled>
|
||||||
|
Success
|
||||||
|
</SlButton>
|
||||||
|
|
||||||
|
<SlButton variant="neutral" disabled>
|
||||||
|
Neutral
|
||||||
|
</SlButton>
|
||||||
|
|
||||||
|
<SlButton variant="warning" disabled>
|
||||||
|
Warning
|
||||||
|
</SlButton>
|
||||||
|
|
||||||
|
<SlButton variant="danger" disabled>
|
||||||
|
Danger
|
||||||
|
</SlButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Styling Buttons
|
||||||
|
|
||||||
|
This example demonstrates how to style buttons using a custom class. This is the recommended approach if you need to add additional variations. To customize an existing variation, modify the selector to target the button's `variant` attribute instead of a class (e.g. `sl-button[variant="primary"]`).
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-button class="pink">Pink Button</sl-button>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
sl-button.pink::part(base) {
|
||||||
|
/* Set design tokens for height and border width */
|
||||||
|
--sl-input-height-medium: 48px;
|
||||||
|
--sl-input-border-width: 4px;
|
||||||
|
|
||||||
|
border-radius: 0;
|
||||||
|
background-color: #ff1493;
|
||||||
|
border-top-color: #ff7ac1;
|
||||||
|
border-left-color: #ff7ac1;
|
||||||
|
border-bottom-color: #ad005c;
|
||||||
|
border-right-color: #ad005c;
|
||||||
|
color: white;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
box-shadow: 0 2px 10px #0002;
|
||||||
|
transition: var(--sl-transition-medium) transform ease, var(--sl-transition-medium) border ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
sl-button.pink::part(base):hover {
|
||||||
|
transform: scale(1.05) rotate(-1deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
sl-button.pink::part(base):active {
|
||||||
|
border-top-color: #ad005c;
|
||||||
|
border-right-color: #ff7ac1;
|
||||||
|
border-bottom-color: #ff7ac1;
|
||||||
|
border-left-color: #ad005c;
|
||||||
|
transform: scale(1.05) rotate(-1deg) translateY(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
sl-button.pink::part(base):focus-visible {
|
||||||
|
outline: dashed 2px deeppink;
|
||||||
|
outline-offset: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
---
|
---
|
||||||
title: Card
|
meta:
|
||||||
description: Cards can be used to group related subjects in a container.
|
title: Card
|
||||||
layout: ../../../layouts/ComponentLayout.astro
|
description: Cards can be used to group related subjects in a container.
|
||||||
|
layout: component
|
||||||
---
|
---
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-card class="card-overview">
|
<sl-card class="card-overview">
|
||||||
<img
|
<img
|
||||||
slot="image"
|
slot="image"
|
||||||
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
|
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
|
||||||
@@ -17,10 +18,10 @@ layout: ../../../layouts/ComponentLayout.astro
|
|||||||
<small>6 weeks old</small>
|
<small>6 weeks old</small>
|
||||||
|
|
||||||
<div slot="footer">
|
<div slot="footer">
|
||||||
<wa-button variant="brand" pill>More Info</wa-button>
|
<sl-button variant="primary" pill>More Info</sl-button>
|
||||||
<wa-rating></wa-rating>
|
<sl-rating></sl-rating>
|
||||||
</div>
|
</div>
|
||||||
</wa-card>
|
</sl-card>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.card-overview {
|
.card-overview {
|
||||||
@@ -28,7 +29,7 @@ layout: ../../../layouts/ComponentLayout.astro
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-overview small {
|
.card-overview small {
|
||||||
color: var(--wa-color-text-quiet);
|
color: var(--sl-color-neutral-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-overview [slot='footer'] {
|
.card-overview [slot='footer'] {
|
||||||
@@ -40,9 +41,9 @@ layout: ../../../layouts/ComponentLayout.astro
|
|||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaCard from '@shoelace-style/shoelace/dist/react/card';
|
import SlCard from '@shoelace-style/shoelace/dist/react/card';
|
||||||
import WaRating from '@shoelace-style/shoelace/dist/react/rating';
|
import SlRating from '@shoelace-style/shoelace/dist/react/rating';
|
||||||
|
|
||||||
const css = `
|
const css = `
|
||||||
.card-overview {
|
.card-overview {
|
||||||
@@ -50,7 +51,7 @@ const css = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-overview small {
|
.card-overview small {
|
||||||
color: var(--wa-color-text-quiet);
|
color: var(--sl-color-neutral-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-overview [slot="footer"] {
|
.card-overview [slot="footer"] {
|
||||||
@@ -62,7 +63,7 @@ const css = `
|
|||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<WaCard className="card-overview">
|
<SlCard className="card-overview">
|
||||||
<img
|
<img
|
||||||
slot="image"
|
slot="image"
|
||||||
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
|
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
|
||||||
@@ -74,12 +75,12 @@ const App = () => (
|
|||||||
<br />
|
<br />
|
||||||
<small>6 weeks old</small>
|
<small>6 weeks old</small>
|
||||||
<div slot="footer">
|
<div slot="footer">
|
||||||
<WaButton variant="brand" pill>
|
<SlButton variant="primary" pill>
|
||||||
More Info
|
More Info
|
||||||
</WaButton>
|
</SlButton>
|
||||||
<WaRating></WaRating>
|
<SlRating></SlRating>
|
||||||
</div>
|
</div>
|
||||||
</WaCard>
|
</SlCard>
|
||||||
|
|
||||||
<style>{css}</style>
|
<style>{css}</style>
|
||||||
</>
|
</>
|
||||||
@@ -93,9 +94,9 @@ const App = () => (
|
|||||||
Basic cards aren't very exciting, but they can display any content you want them to.
|
Basic cards aren't very exciting, but they can display any content you want them to.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-card class="card-basic">
|
<sl-card class="card-basic">
|
||||||
This is just a basic card. No image, no header, and no footer. Just your content.
|
This is just a basic card. No image, no header, and no footer. Just your content.
|
||||||
</wa-card>
|
</sl-card>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.card-basic {
|
.card-basic {
|
||||||
@@ -105,7 +106,7 @@ Basic cards aren't very exciting, but they can display any content you want them
|
|||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaCard from '@shoelace-style/shoelace/dist/react/card';
|
import SlCard from '@shoelace-style/shoelace/dist/react/card';
|
||||||
|
|
||||||
const css = `
|
const css = `
|
||||||
.card-basic {
|
.card-basic {
|
||||||
@@ -115,9 +116,9 @@ const css = `
|
|||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<WaCard className="card-basic">
|
<SlCard className="card-basic">
|
||||||
This is just a basic card. No image, no header, and no footer. Just your content.
|
This is just a basic card. No image, no header, and no footer. Just your content.
|
||||||
</WaCard>
|
</SlCard>
|
||||||
|
|
||||||
<style>{css}</style>
|
<style>{css}</style>
|
||||||
</>
|
</>
|
||||||
@@ -129,14 +130,14 @@ const App = () => (
|
|||||||
Headers can be used to display titles and more.
|
Headers can be used to display titles and more.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-card class="card-header">
|
<sl-card class="card-header">
|
||||||
<div slot="header">
|
<div slot="header">
|
||||||
Header Title
|
Header Title
|
||||||
<wa-icon-button name="gear" variant="solid" label="Settings"></wa-icon-button>
|
<sl-icon-button name="gear" label="Settings"></sl-icon-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
This card has a header. You can put all sorts of things in it!
|
This card has a header. You can put all sorts of things in it!
|
||||||
</wa-card>
|
</sl-card>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.card-header {
|
.card-header {
|
||||||
@@ -153,15 +154,15 @@ Headers can be used to display titles and more.
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header wa-icon-button {
|
.card-header sl-icon-button {
|
||||||
font-size: var(--wa-font-size-m);
|
font-size: var(--sl-font-size-medium);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaCard from '@shoelace-style/shoelace/dist/react/card';
|
import SlCard from '@shoelace-style/shoelace/dist/react/card';
|
||||||
import WaIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
|
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
|
||||||
|
|
||||||
const css = `
|
const css = `
|
||||||
.card-header {
|
.card-header {
|
||||||
@@ -178,20 +179,20 @@ const css = `
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header wa-icon-button {
|
.card-header sl-icon-button {
|
||||||
font-size: var(--wa-font-size-m);
|
font-size: var(--sl-font-size-medium);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<WaCard className="card-header">
|
<SlCard className="card-header">
|
||||||
<div slot="header">
|
<div slot="header">
|
||||||
Header Title
|
Header Title
|
||||||
<WaIconButton name="gear" variant="solid"></WaIconButton>
|
<SlIconButton name="gear"></SlIconButton>
|
||||||
</div>
|
</div>
|
||||||
This card has a header. You can put all sorts of things in it!
|
This card has a header. You can put all sorts of things in it!
|
||||||
</WaCard>
|
</SlCard>
|
||||||
|
|
||||||
<style>{css}</style>
|
<style>{css}</style>
|
||||||
</>
|
</>
|
||||||
@@ -203,14 +204,14 @@ const App = () => (
|
|||||||
Footers can be used to display actions, summaries, or other relevant content.
|
Footers can be used to display actions, summaries, or other relevant content.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-card class="card-footer">
|
<sl-card class="card-footer">
|
||||||
This card has a footer. You can put all sorts of things in it!
|
This card has a footer. You can put all sorts of things in it!
|
||||||
|
|
||||||
<div slot="footer">
|
<div slot="footer">
|
||||||
<wa-rating></wa-rating>
|
<sl-rating></sl-rating>
|
||||||
<wa-button variant="brand">Preview</wa-button>
|
<sl-button variant="primary">Preview</sl-button>
|
||||||
</div>
|
</div>
|
||||||
</wa-card>
|
</sl-card>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.card-footer {
|
.card-footer {
|
||||||
@@ -226,9 +227,9 @@ Footers can be used to display actions, summaries, or other relevant content.
|
|||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaCard from '@shoelace-style/shoelace/dist/react/card';
|
import SlCard from '@shoelace-style/shoelace/dist/react/card';
|
||||||
import WaRating from '@shoelace-style/shoelace/dist/react/rating';
|
import SlRating from '@shoelace-style/shoelace/dist/react/rating';
|
||||||
|
|
||||||
const css = `
|
const css = `
|
||||||
.card-footer {
|
.card-footer {
|
||||||
@@ -244,15 +245,15 @@ const css = `
|
|||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<WaCard className="card-footer">
|
<SlCard className="card-footer">
|
||||||
This card has a footer. You can put all sorts of things in it!
|
This card has a footer. You can put all sorts of things in it!
|
||||||
<div slot="footer">
|
<div slot="footer">
|
||||||
<WaRating></WaRating>
|
<SlRating></SlRating>
|
||||||
<WaButton slot="footer" variant="brand">
|
<SlButton slot="footer" variant="primary">
|
||||||
Preview
|
Preview
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</div>
|
</div>
|
||||||
</WaCard>
|
</SlCard>
|
||||||
|
|
||||||
<style>{css}</style>
|
<style>{css}</style>
|
||||||
</>
|
</>
|
||||||
@@ -264,14 +265,14 @@ const App = () => (
|
|||||||
Cards accept an `image` slot. The image is displayed atop the card and stretches to fit.
|
Cards accept an `image` slot. The image is displayed atop the card and stretches to fit.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-card class="card-image">
|
<sl-card class="card-image">
|
||||||
<img
|
<img
|
||||||
slot="image"
|
slot="image"
|
||||||
src="https://images.unsplash.com/photo-1547191783-94d5f8f6d8b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=80"
|
src="https://images.unsplash.com/photo-1547191783-94d5f8f6d8b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=80"
|
||||||
alt="A kitten walks towards camera on top of pallet."
|
alt="A kitten walks towards camera on top of pallet."
|
||||||
/>
|
/>
|
||||||
This is a kitten, but not just any kitten. This kitten likes walking along pallets.
|
This is a kitten, but not just any kitten. This kitten likes walking along pallets.
|
||||||
</wa-card>
|
</sl-card>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.card-image {
|
.card-image {
|
||||||
@@ -281,7 +282,7 @@ Cards accept an `image` slot. The image is displayed atop the card and stretches
|
|||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaCard from '@shoelace-style/shoelace/dist/react/card';
|
import SlCard from '@shoelace-style/shoelace/dist/react/card';
|
||||||
|
|
||||||
const css = `
|
const css = `
|
||||||
.card-image {
|
.card-image {
|
||||||
@@ -291,14 +292,14 @@ const css = `
|
|||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<WaCard className="card-image">
|
<SlCard className="card-image">
|
||||||
<img
|
<img
|
||||||
slot="image"
|
slot="image"
|
||||||
src="https://images.unsplash.com/photo-1547191783-94d5f8f6d8b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=80"
|
src="https://images.unsplash.com/photo-1547191783-94d5f8f6d8b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=80"
|
||||||
alt="A kitten walks towards camera on top of pallet."
|
alt="A kitten walks towards camera on top of pallet."
|
||||||
/>
|
/>
|
||||||
This is a kitten, but not just any kitten. This kitten likes walking along pallets.
|
This is a kitten, but not just any kitten. This kitten likes walking along pallets.
|
||||||
</WaCard>
|
</SlCard>
|
||||||
|
|
||||||
<style>{css}</style>
|
<style>{css}</style>
|
||||||
</>
|
</>
|
||||||
@@ -1,81 +1,82 @@
|
|||||||
---
|
---
|
||||||
title: Carousel Item
|
meta:
|
||||||
description: A carousel item represent a slide within a carousel.
|
title: Carousel Item
|
||||||
layout: ../../../layouts/ComponentLayout.astro
|
description: A carousel item represent a slide within a carousel.
|
||||||
|
layout: component
|
||||||
---
|
---
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-carousel pagination>
|
<sl-carousel pagination>
|
||||||
<wa-carousel-item>
|
<sl-carousel-item>
|
||||||
<img
|
<img
|
||||||
alt="The sun shines on the mountains and trees - Photo by Adam Kool on Unsplash"
|
alt="The sun shines on the mountains and trees - Photo by Adam Kool on Unsplash"
|
||||||
src="/assets/examples/carousel/mountains.jpg"
|
src="/assets/examples/carousel/mountains.jpg"
|
||||||
/>
|
/>
|
||||||
</wa-carousel-item>
|
</sl-carousel-item>
|
||||||
<wa-carousel-item>
|
<sl-carousel-item>
|
||||||
<img
|
<img
|
||||||
alt="A waterfall in the middle of a forest - Photo by Thomas Kelly on Unsplash"
|
alt="A waterfall in the middle of a forest - Photo by Thomas Kelly on Unsplash"
|
||||||
src="/assets/examples/carousel/waterfall.jpg"
|
src="/assets/examples/carousel/waterfall.jpg"
|
||||||
/>
|
/>
|
||||||
</wa-carousel-item>
|
</sl-carousel-item>
|
||||||
<wa-carousel-item>
|
<sl-carousel-item>
|
||||||
<img
|
<img
|
||||||
alt="The sun is setting over a lavender field - Photo by Leonard Cotte on Unsplash"
|
alt="The sun is setting over a lavender field - Photo by Leonard Cotte on Unsplash"
|
||||||
src="/assets/examples/carousel/sunset.jpg"
|
src="/assets/examples/carousel/sunset.jpg"
|
||||||
/>
|
/>
|
||||||
</wa-carousel-item>
|
</sl-carousel-item>
|
||||||
<wa-carousel-item>
|
<sl-carousel-item>
|
||||||
<img
|
<img
|
||||||
alt="A field of grass with the sun setting in the background - Photo by Sapan Patel on Unsplash"
|
alt="A field of grass with the sun setting in the background - Photo by Sapan Patel on Unsplash"
|
||||||
src="/assets/examples/carousel/field.jpg"
|
src="/assets/examples/carousel/field.jpg"
|
||||||
/>
|
/>
|
||||||
</wa-carousel-item>
|
</sl-carousel-item>
|
||||||
<wa-carousel-item>
|
<sl-carousel-item>
|
||||||
<img
|
<img
|
||||||
alt="A scenic view of a mountain with clouds rolling in - Photo by V2osk on Unsplash"
|
alt="A scenic view of a mountain with clouds rolling in - Photo by V2osk on Unsplash"
|
||||||
src="/assets/examples/carousel/valley.jpg"
|
src="/assets/examples/carousel/valley.jpg"
|
||||||
/>
|
/>
|
||||||
</wa-carousel-item>
|
</sl-carousel-item>
|
||||||
</wa-carousel>
|
</sl-carousel>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaCarousel from '@shoelace-style/shoelace/dist/react/carousel';
|
import SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';
|
||||||
import WaCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';
|
import SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<WaCarousel pagination>
|
<SlCarousel pagination>
|
||||||
<WaCarouselItem>
|
<SlCarouselItem>
|
||||||
<img
|
<img
|
||||||
alt="The sun shines on the mountains and trees - Photo by Adam Kool on Unsplash"
|
alt="The sun shines on the mountains and trees - Photo by Adam Kool on Unsplash"
|
||||||
src="/assets/examples/carousel/mountains.jpg"
|
src="/assets/examples/carousel/mountains.jpg"
|
||||||
/>
|
/>
|
||||||
</WaCarouselItem>
|
</SlCarouselItem>
|
||||||
<WaCarouselItem>
|
<SlCarouselItem>
|
||||||
<img
|
<img
|
||||||
alt="A waterfall in the middle of a forest - Photo by Thomas Kelly on Unsplash"
|
alt="A waterfall in the middle of a forest - Photo by Thomas Kelly on Unsplash"
|
||||||
src="/assets/examples/carousel/waterfall.jpg"
|
src="/assets/examples/carousel/waterfall.jpg"
|
||||||
/>
|
/>
|
||||||
</WaCarouselItem>
|
</SlCarouselItem>
|
||||||
<WaCarouselItem>
|
<SlCarouselItem>
|
||||||
<img
|
<img
|
||||||
alt="The sun is setting over a lavender field - Photo by Leonard Cotte on Unsplash"
|
alt="The sun is setting over a lavender field - Photo by Leonard Cotte on Unsplash"
|
||||||
src="/assets/examples/carousel/sunset.jpg"
|
src="/assets/examples/carousel/sunset.jpg"
|
||||||
/>
|
/>
|
||||||
</WaCarouselItem>
|
</SlCarouselItem>
|
||||||
<WaCarouselItem>
|
<SlCarouselItem>
|
||||||
<img
|
<img
|
||||||
alt="A field of grass with the sun setting in the background - Photo by Sapan Patel on Unsplash"
|
alt="A field of grass with the sun setting in the background - Photo by Sapan Patel on Unsplash"
|
||||||
src="/assets/examples/carousel/field.jpg"
|
src="/assets/examples/carousel/field.jpg"
|
||||||
/>
|
/>
|
||||||
</WaCarouselItem>
|
</SlCarouselItem>
|
||||||
<WaCarouselItem>
|
<SlCarouselItem>
|
||||||
<img
|
<img
|
||||||
alt="A scenic view of a mountain with clouds rolling in - Photo by V2osk on Unsplash"
|
alt="A scenic view of a mountain with clouds rolling in - Photo by V2osk on Unsplash"
|
||||||
src="/assets/examples/carousel/valley.jpg"
|
src="/assets/examples/carousel/valley.jpg"
|
||||||
/>
|
/>
|
||||||
</WaCarouselItem>
|
</SlCarouselItem>
|
||||||
</WaCarousel>
|
</SlCarousel>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
164
docs/pages/components/checkbox.md
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Checkbox
|
||||||
|
description: Checkboxes allow the user to toggle an option on or off.
|
||||||
|
layout: component
|
||||||
|
---
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-checkbox>Checkbox</sl-checkbox>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
|
||||||
|
|
||||||
|
const App = () => <SlCheckbox>Checkbox</SlCheckbox>;
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Checked
|
||||||
|
|
||||||
|
Use the `checked` attribute to activate the checkbox.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-checkbox checked>Checked</sl-checkbox>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
|
||||||
|
|
||||||
|
const App = () => <SlCheckbox checked>Checked</SlCheckbox>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Indeterminate
|
||||||
|
|
||||||
|
Use the `indeterminate` attribute to make the checkbox indeterminate.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-checkbox indeterminate>Indeterminate</sl-checkbox>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
|
||||||
|
|
||||||
|
const App = () => <SlCheckbox indeterminate>Indeterminate</SlCheckbox>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disabled
|
||||||
|
|
||||||
|
Use the `disabled` attribute to disable the checkbox.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-checkbox disabled>Disabled</sl-checkbox>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
|
||||||
|
|
||||||
|
const App = () => <SlCheckbox disabled>Disabled</SlCheckbox>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sizes
|
||||||
|
|
||||||
|
Use the `size` attribute to change a checkbox's size.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-checkbox size="small">Small</sl-checkbox>
|
||||||
|
<br />
|
||||||
|
<sl-checkbox size="medium">Medium</sl-checkbox>
|
||||||
|
<br />
|
||||||
|
<sl-checkbox size="large">Large</sl-checkbox>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlCheckbox size="small">Small</SlCheckbox>
|
||||||
|
<br />
|
||||||
|
<SlCheckbox size="medium">Medium</SlCheckbox>
|
||||||
|
<br />
|
||||||
|
<SlCheckbox size="large">Large</SlCheckbox>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<form class="custom-validity">
|
||||||
|
<sl-checkbox>Check me</sl-checkbox>
|
||||||
|
<br />
|
||||||
|
<sl-button type="submit" variant="primary" style="margin-top: 1rem;">Submit</sl-button>
|
||||||
|
</form>
|
||||||
|
<script>
|
||||||
|
const form = document.querySelector('.custom-validity');
|
||||||
|
const checkbox = form.querySelector('sl-checkbox');
|
||||||
|
const errorMessage = `Don't forget to check me!`;
|
||||||
|
|
||||||
|
// Set initial validity as soon as the element is defined
|
||||||
|
customElements.whenDefined('sl-checkbox').then(async () => {
|
||||||
|
await checkbox.updateComplete;
|
||||||
|
checkbox.setCustomValidity(errorMessage);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update validity on change
|
||||||
|
checkbox.addEventListener('sl-change', () => {
|
||||||
|
checkbox.setCustomValidity(checkbox.checked ? '' : errorMessage);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle submit
|
||||||
|
form.addEventListener('submit', event => {
|
||||||
|
event.preventDefault();
|
||||||
|
alert('All fields are valid!');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const checkbox = useRef(null);
|
||||||
|
const errorMessage = `Don't forget to check me!`;
|
||||||
|
|
||||||
|
function handleChange() {
|
||||||
|
checkbox.current.setCustomValidity(checkbox.current.checked ? '' : errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
alert('All fields are valid!');
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
checkbox.current.setCustomValidity(errorMessage);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form class="custom-validity" onSubmit={handleSubmit}>
|
||||||
|
<SlCheckbox ref={checkbox} onSlChange={handleChange}>
|
||||||
|
Check me
|
||||||
|
</SlCheckbox>
|
||||||
|
<br />
|
||||||
|
<SlButton type="submit" variant="primary" style={{ marginTop: '1rem' }}>
|
||||||
|
Submit
|
||||||
|
</SlButton>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
---
|
---
|
||||||
title: Color Picker
|
meta:
|
||||||
description: Color pickers allow the user to select a color.
|
title: Color Picker
|
||||||
layout: ../../../layouts/ComponentLayout.astro
|
description: Color pickers allow the user to select a color.
|
||||||
|
layout: component
|
||||||
---
|
---
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-color-picker label="Select a color"></wa-color-picker>
|
<sl-color-picker label="Select a color"></sl-color-picker>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
||||||
|
|
||||||
const App = () => <WaColorPicker label="Select a color" />;
|
const App = () => <SlColorPicker label="Select a color" />;
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
@@ -25,13 +26,13 @@ This component works with standard `<form>` elements. Please refer to the sectio
|
|||||||
Use the `value` attribute to set an initial value for the color picker.
|
Use the `value` attribute to set an initial value for the color picker.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-color-picker value="#4a90e2" label="Select a color"></wa-color-picker>
|
<sl-color-picker value="#4a90e2" label="Select a color"></sl-color-picker>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
||||||
|
|
||||||
const App = () => <WaColorPicker value="#4a90e2" label="Select a color" />;
|
const App = () => <SlColorPicker value="#4a90e2" label="Select a color" />;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Opacity
|
### Opacity
|
||||||
@@ -39,13 +40,13 @@ const App = () => <WaColorPicker value="#4a90e2" label="Select a color" />;
|
|||||||
Use the `opacity` attribute to enable the opacity slider. When this is enabled, the value will be displayed as HEXA, RGBA, HSLA, or HSVA based on `format`.
|
Use the `opacity` attribute to enable the opacity slider. When this is enabled, the value will be displayed as HEXA, RGBA, HSLA, or HSVA based on `format`.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-color-picker value="#f5a623ff" opacity label="Select a color"></wa-color-picker>
|
<sl-color-picker value="#f5a623ff" opacity label="Select a color"></sl-color-picker>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
||||||
|
|
||||||
const App = () => <WaColorPicker opacity label="Select a color" />;
|
const App = () => <SlColorPicker opacity label="Select a color" />;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Formats
|
### Formats
|
||||||
@@ -55,21 +56,21 @@ Set the color picker's format with the `format` attribute. Valid options include
|
|||||||
To prevent users from toggling the format themselves, add the `no-format-toggle` attribute.
|
To prevent users from toggling the format themselves, add the `no-format-toggle` attribute.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-color-picker format="hex" value="#4a90e2" label="Select a color"></wa-color-picker>
|
<sl-color-picker format="hex" value="#4a90e2" label="Select a color"></sl-color-picker>
|
||||||
<wa-color-picker format="rgb" value="rgb(80, 227, 194)" label="Select a color"></wa-color-picker>
|
<sl-color-picker format="rgb" value="rgb(80, 227, 194)" label="Select a color"></sl-color-picker>
|
||||||
<wa-color-picker format="hsl" value="hsl(290, 87%, 47%)" label="Select a color"></wa-color-picker>
|
<sl-color-picker format="hsl" value="hsl(290, 87%, 47%)" label="Select a color"></sl-color-picker>
|
||||||
<wa-color-picker format="hsv" value="hsv(55, 89%, 97%)" label="Select a color"></wa-color-picker>
|
<sl-color-picker format="hsv" value="hsv(55, 89%, 97%)" label="Select a color"></sl-color-picker>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<WaColorPicker format="hex" value="#4a90e2" />
|
<SlColorPicker format="hex" value="#4a90e2" />
|
||||||
<WaColorPicker format="rgb" value="rgb(80, 227, 194)" />
|
<SlColorPicker format="rgb" value="rgb(80, 227, 194)" />
|
||||||
<WaColorPicker format="hsl" value="hsl(290, 87%, 47%)" />
|
<SlColorPicker format="hsl" value="hsl(290, 87%, 47%)" />
|
||||||
<WaColorPicker format="hsv" value="hsv(55, 89%, 97%)" />
|
<SlColorPicker format="hsv" value="hsv(55, 89%, 97%)" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
@@ -79,20 +80,20 @@ const App = () => (
|
|||||||
Use the `swatches` attribute to add convenient presets to the color picker. Any format the color picker can parse is acceptable (including CSS color names), but each value must be separated by a semicolon (`;`). Alternatively, you can pass an array of color values to this property using JavaScript.
|
Use the `swatches` attribute to add convenient presets to the color picker. Any format the color picker can parse is acceptable (including CSS color names), but each value must be separated by a semicolon (`;`). Alternatively, you can pass an array of color values to this property using JavaScript.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-color-picker
|
<sl-color-picker
|
||||||
label="Select a color"
|
label="Select a color"
|
||||||
swatches="
|
swatches="
|
||||||
#d0021b; #f5a623; #f8e71c; #8b572a; #7ed321; #417505; #bd10e0; #9013fe;
|
#d0021b; #f5a623; #f8e71c; #8b572a; #7ed321; #417505; #bd10e0; #9013fe;
|
||||||
#4a90e2; #50e3c2; #b8e986; #000; #444; #888; #ccc; #fff;
|
#4a90e2; #50e3c2; #b8e986; #000; #444; #888; #ccc; #fff;
|
||||||
"
|
"
|
||||||
></wa-color-picker>
|
></sl-color-picker>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<WaColorPicker
|
<SlColorPicker
|
||||||
label="Select a color"
|
label="Select a color"
|
||||||
swatches="
|
swatches="
|
||||||
#d0021b; #f5a623; #f8e71c; #8b572a; #7ed321; #417505; #bd10e0; #9013fe;
|
#d0021b; #f5a623; #f8e71c; #8b572a; #7ed321; #417505; #bd10e0; #9013fe;
|
||||||
@@ -107,19 +108,19 @@ const App = () => (
|
|||||||
Use the `size` attribute to change the color picker's trigger size.
|
Use the `size` attribute to change the color picker's trigger size.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-color-picker size="small" label="Select a color"></wa-color-picker>
|
<sl-color-picker size="small" label="Select a color"></sl-color-picker>
|
||||||
<wa-color-picker size="medium" label="Select a color"></wa-color-picker>
|
<sl-color-picker size="medium" label="Select a color"></sl-color-picker>
|
||||||
<wa-color-picker size="large" label="Select a color"></wa-color-picker>
|
<sl-color-picker size="large" label="Select a color"></sl-color-picker>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<WaColorPicker size="small" label="Select a color" />
|
<SlColorPicker size="small" label="Select a color" />
|
||||||
<WaColorPicker size="medium" label="Select a color" />
|
<SlColorPicker size="medium" label="Select a color" />
|
||||||
<WaColorPicker size="large" label="Select a color" />
|
<SlColorPicker size="large" label="Select a color" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
@@ -129,11 +130,11 @@ const App = () => (
|
|||||||
The color picker can be rendered inline instead of in a dropdown using the `inline` attribute.
|
The color picker can be rendered inline instead of in a dropdown using the `inline` attribute.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-color-picker inline label="Select a color"></wa-color-picker>
|
<sl-color-picker inline label="Select a color"></sl-color-picker>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
||||||
|
|
||||||
const App = () => <WaColorPicker inline label="Select a color" />;
|
const App = () => <SlColorPicker inline label="Select a color" />;
|
||||||
```
|
```
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
---
|
---
|
||||||
title: Copy Button
|
meta:
|
||||||
description: Copies data to the clipboard when the user clicks the button.
|
title: Copy Button
|
||||||
layout: ../../../layouts/ComponentLayout.astro
|
description: Copies data to the clipboard when the user clicks the button.
|
||||||
|
layout: component
|
||||||
---
|
---
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-copy-button value="Web Awesome rocks!"></wa-copy-button>
|
<sl-copy-button value="Shoelace rocks!"></sl-copy-button>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { WaCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<WaCopyButton value="Web Awesome rocks!" />
|
<SlCopyButton value="Shoelace rocks!" />
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -23,19 +24,19 @@ const App = () => (
|
|||||||
Copy Buttons display feedback in a tooltip. You can customize the labels using the `copy-label`, `success-label`, and `error-label` attributes.
|
Copy Buttons display feedback in a tooltip. You can customize the labels using the `copy-label`, `success-label`, and `error-label` attributes.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-copy-button
|
<sl-copy-button
|
||||||
value="Custom labels are easy"
|
value="Custom labels are easy"
|
||||||
copy-label="Click to copy"
|
copy-label="Click to copy"
|
||||||
success-label="You did it!"
|
success-label="You did it!"
|
||||||
error-label="Whoops, your browser doesn't support this!"
|
error-label="Whoops, your browser doesn't support this!"
|
||||||
></wa-copy-button>
|
></sl-copy-button>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { WaCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<WaCopyButton
|
<SlCopyButton
|
||||||
value="Custom labels are easy"
|
value="Custom labels are easy"
|
||||||
copy-label="Click to copy"
|
copy-label="Click to copy"
|
||||||
success-label="You did it!"
|
success-label="You did it!"
|
||||||
@@ -46,27 +47,27 @@ const App = () => (
|
|||||||
|
|
||||||
### Custom Icons
|
### Custom Icons
|
||||||
|
|
||||||
Use the `copy-icon`, `success-icon`, and `error-icon` slots to customize the icons that get displayed for each state. You can use [`<wa-icon>`](/components/icon) or your own images.
|
Use the `copy-icon`, `success-icon`, and `error-icon` slots to customize the icons that get displayed for each state. You can use [`<sl-icon>`](/components/icon) or your own images.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-copy-button value="Copied from a custom button">
|
<sl-copy-button value="Copied from a custom button">
|
||||||
<wa-icon slot="copy-icon" name="clipboard" variant="regular"></wa-icon>
|
<sl-icon slot="copy-icon" name="clipboard"></sl-icon>
|
||||||
<wa-icon slot="success-icon" name="thumbs-up" variant="solid"></wa-icon>
|
<sl-icon slot="success-icon" name="clipboard-check"></sl-icon>
|
||||||
<wa-icon slot="error-icon" name="xmark" variant="solid"></wa-icon>
|
<sl-icon slot="error-icon" name="clipboard-x"></sl-icon>
|
||||||
</wa-copy-button>
|
</sl-copy-button>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { WaCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
||||||
import { WaIcon } from '@shoelace-style/shoelace/dist/react/icon';
|
import { SlIcon } from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<WaCopyButton value="Copied from a custom button">
|
<SlCopyButton value="Copied from a custom button">
|
||||||
<WaIcon slot="copy-icon" name="clipboard" variant="regular" />
|
<SlIcon slot="copy-icon" name="clipboard" />
|
||||||
<WaIcon slot="success-icon" name="check" variant="solid" />
|
<SlIcon slot="success-icon" name="clipboard-check" />
|
||||||
<WaIcon slot="error-icon" name="xmark" variant="solid" />
|
<SlIcon slot="error-icon" name="clipboard-x" />
|
||||||
</WaCopyButton>
|
</SlCopyButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
@@ -82,61 +83,61 @@ To copy data from an attribute, use `from="id[attr]"` where `id` is the id of th
|
|||||||
```html:preview
|
```html:preview
|
||||||
<!-- Copies the span's textContent -->
|
<!-- Copies the span's textContent -->
|
||||||
<span id="my-phone">+1 (234) 456-7890</span>
|
<span id="my-phone">+1 (234) 456-7890</span>
|
||||||
<wa-copy-button from="my-phone"></wa-copy-button>
|
<sl-copy-button from="my-phone"></sl-copy-button>
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
<!-- Copies the input's "value" property -->
|
<!-- Copies the input's "value" property -->
|
||||||
<wa-input id="my-input" type="text" value="User input" style="display: inline-block; max-width: 300px;"></wa-input>
|
<sl-input id="my-input" type="text" value="User input" style="display: inline-block; max-width: 300px;"></sl-input>
|
||||||
<wa-copy-button from="my-input.value"></wa-copy-button>
|
<sl-copy-button from="my-input.value"></sl-copy-button>
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
<!-- Copies the link's "href" attribute -->
|
<!-- Copies the link's "href" attribute -->
|
||||||
<a id="my-link" href="https://shoelace.style/">Web Awesome Website</a>
|
<a id="my-link" href="https://shoelace.style/">Shoelace Website</a>
|
||||||
<wa-copy-button from="my-link[href]"></wa-copy-button>
|
<sl-copy-button from="my-link[href]"></sl-copy-button>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { WaCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
||||||
import { WaInput } from '@shoelace-style/shoelace/dist/react/input';
|
import { SlInput } from '@shoelace-style/shoelace/dist/react/input';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
{/* Copies the span's textContent */}
|
{/* Copies the span's textContent */}
|
||||||
<span id="my-phone">+1 (234) 456-7890</span>
|
<span id="my-phone">+1 (234) 456-7890</span>
|
||||||
<WaCopyButton from="my-phone" />
|
<SlCopyButton from="my-phone" />
|
||||||
|
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
{/* Copies the input's "value" property */}
|
{/* Copies the input's "value" property */}
|
||||||
<WaInput id="my-input" type="text" />
|
<SlInput id="my-input" type="text" />
|
||||||
<WaCopyButton from="my-input.value" />
|
<SlCopyButton from="my-input.value" />
|
||||||
|
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
{/* Copies the link's "href" attribute */}
|
{/* Copies the link's "href" attribute */}
|
||||||
<a id="my-link" href="https://shoelace.style/">Web Awesome Website</a>
|
<a id="my-link" href="https://shoelace.style/">Shoelace Website</a>
|
||||||
<WaCopyButton from="my-link[href]" />
|
<SlCopyButton from="my-link[href]" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Handling Errors
|
### Handling Errors
|
||||||
|
|
||||||
A copy error will occur if the value is an empty string, if the `from` attribute points to an id that doesn't exist, or if the browser rejects the operation for any reason. When this happens, the `wa-error` event will be emitted.
|
A copy error will occur if the value is an empty string, if the `from` attribute points to an id that doesn't exist, or if the browser rejects the operation for any reason. When this happens, the `sl-error` event will be emitted.
|
||||||
|
|
||||||
This example demonstrates what happens when a copy error occurs. You can customize the error label and icon using the `error-label` attribute and the `error-icon` slot, respectively.
|
This example demonstrates what happens when a copy error occurs. You can customize the error label and icon using the `error-label` attribute and the `error-icon` slot, respectively.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-copy-button from="i-do-not-exist"></wa-copy-button>
|
<sl-copy-button from="i-do-not-exist"></sl-copy-button>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { WaCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<WaCopyButton from="i-do-not-exist" />
|
<SlCopyButton from="i-do-not-exist" />
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -145,14 +146,14 @@ const App = () => (
|
|||||||
Copy buttons can be disabled by adding the `disabled` attribute.
|
Copy buttons can be disabled by adding the `disabled` attribute.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-copy-button value="You can't copy me" disabled></wa-copy-button>
|
<sl-copy-button value="You can't copy me" disabled></sl-copy-button>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { WaCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<WaCopyButton value="You can't copy me" disabled />
|
<SlCopyButton value="You can't copy me" disabled />
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -161,14 +162,14 @@ const App = () => (
|
|||||||
A success indicator is briefly shown after copying. You can customize the length of time the indicator is shown using the `feedback-duration` attribute.
|
A success indicator is briefly shown after copying. You can customize the length of time the indicator is shown using the `feedback-duration` attribute.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-copy-button value="Web Awesome rocks!" feedback-duration="250"></wa-copy-button>
|
<sl-copy-button value="Shoelace rocks!" feedback-duration="250"></sl-copy-button>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { WaCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<WaCopyButton value="Web Awesome rocks!" feedback-duration={250} />
|
<SlCopyButton value="Shoelace rocks!" feedback-duration={250} />
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -177,11 +178,11 @@ const App = () => (
|
|||||||
You can customize the button to your liking with CSS.
|
You can customize the button to your liking with CSS.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-copy-button value="I'm so stylish" class="custom-styles">
|
<sl-copy-button value="I'm so stylish" class="custom-styles">
|
||||||
<wa-icon slot="copy-icon" name="clipboard"></wa-icon>
|
<sl-icon slot="copy-icon" name="asterisk"></sl-icon>
|
||||||
<wa-icon slot="success-icon" name="thumbs-up"></wa-icon>
|
<sl-icon slot="success-icon" name="check-lg"></sl-icon>
|
||||||
<wa-icon slot="error-icon" name="thumbs-down"></wa-icon>
|
<sl-icon slot="error-icon" name="x-lg"></sl-icon>
|
||||||
</wa-copy-button>
|
</sl-copy-button>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.custom-styles {
|
.custom-styles {
|
||||||
@@ -192,19 +193,19 @@ You can customize the button to your liking with CSS.
|
|||||||
|
|
||||||
.custom-styles::part(button) {
|
.custom-styles::part(button) {
|
||||||
background-color: #ff1493;
|
background-color: #ff1493;
|
||||||
border: solid 2px #ff7ac1;
|
border: solid 4px #ff7ac1;
|
||||||
border-right-color: #ad005c;
|
border-right-color: #ad005c;
|
||||||
border-bottom-color: #ad005c;
|
border-bottom-color: #ad005c;
|
||||||
border-radius: 6px;
|
border-radius: 0;
|
||||||
transition: var(--wa-transition-normal) all;
|
transition: 100ms scale ease-in-out, 100ms translate ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-styles::part(button):hover {
|
.custom-styles::part(button):hover {
|
||||||
transform: scale(1.05);
|
scale: 1.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-styles::part(button):active {
|
.custom-styles::part(button):active {
|
||||||
transform: translateY(1px);
|
translate: 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-styles::part(button):focus-visible {
|
.custom-styles::part(button):focus-visible {
|
||||||
@@ -215,7 +216,7 @@ You can customize the button to your liking with CSS.
|
|||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { WaCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
||||||
|
|
||||||
const css = `
|
const css = `
|
||||||
.custom-styles {
|
.custom-styles {
|
||||||
@@ -249,7 +250,7 @@ const css = `
|
|||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<WaCopyButton value="I'm so stylish" className="custom-styles" />
|
<SlCopyButton value="I'm so stylish" className="custom-styles" />
|
||||||
|
|
||||||
<style>{css}</style>
|
<style>{css}</style>
|
||||||
</>
|
</>
|
||||||
@@ -1,26 +1,27 @@
|
|||||||
---
|
---
|
||||||
title: Details
|
meta:
|
||||||
description: Details show a brief summary and expand to show additional content.
|
title: Details
|
||||||
layout: ../../../layouts/ComponentLayout.astro
|
description: Details show a brief summary and expand to show additional content.
|
||||||
|
layout: component
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- cspell:dictionaries lorem-ipsum -->
|
<!-- cspell:dictionaries lorem-ipsum -->
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-details summary="Toggle Me">
|
<sl-details summary="Toggle Me">
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
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.
|
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||||
</wa-details>
|
</sl-details>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaDetails from '@shoelace-style/shoelace/dist/react/details';
|
import SlDetails from '@shoelace-style/shoelace/dist/react/details';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<WaDetails summary="Toggle Me">
|
<SlDetails summary="Toggle Me">
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
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.
|
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||||
</WaDetails>
|
</SlDetails>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -31,20 +32,20 @@ const App = () => (
|
|||||||
Use the `disable` attribute to prevent the details from expanding.
|
Use the `disable` attribute to prevent the details from expanding.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-details summary="Disabled" disabled>
|
<sl-details summary="Disabled" disabled>
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
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.
|
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||||
</wa-details>
|
</sl-details>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaDetails from '@shoelace-style/shoelace/dist/react/details';
|
import SlDetails from '@shoelace-style/shoelace/dist/react/details';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<WaDetails summary="Disabled" disabled>
|
<SlDetails summary="Disabled" disabled>
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
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.
|
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||||
</WaDetails>
|
</SlDetails>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -53,16 +54,16 @@ const App = () => (
|
|||||||
Use the `expand-icon` and `collapse-icon` slots to change the expand and collapse icons, respectively. To disable the animation, override the `rotate` property on the `summary-icon` part as shown below.
|
Use the `expand-icon` and `collapse-icon` slots to change the expand and collapse icons, respectively. To disable the animation, override the `rotate` property on the `summary-icon` part as shown below.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-details summary="Toggle Me" class="custom-icons">
|
<sl-details summary="Toggle Me" class="custom-icons">
|
||||||
<wa-icon name="square-plus" slot="expand-icon" variant="regular"></wa-icon>
|
<sl-icon name="plus-square" slot="expand-icon"></sl-icon>
|
||||||
<wa-icon name="square-minus" slot="collapse-icon" variant="regular"></wa-icon>
|
<sl-icon name="dash-square" slot="collapse-icon"></sl-icon>
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
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.
|
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||||
</wa-details>
|
</sl-details>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
wa-details.custom-icons::part(summary-icon) {
|
sl-details.custom-icons::part(summary-icon) {
|
||||||
/* Disable the expand/collapse animation */
|
/* Disable the expand/collapse animation */
|
||||||
rotate: none;
|
rotate: none;
|
||||||
}
|
}
|
||||||
@@ -70,11 +71,11 @@ Use the `expand-icon` and `collapse-icon` slots to change the expand and collaps
|
|||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaDetails from '@shoelace-style/shoelace/dist/react/details';
|
import SlDetails from '@shoelace-style/shoelace/dist/react/details';
|
||||||
import WaIcon from '@shoelace-style/shoelace/dist/react/icon';
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
const css = `
|
const css = `
|
||||||
wa-details.custom-icon::part(summary-icon) {
|
sl-details.custom-icon::part(summary-icon) {
|
||||||
/* Disable the expand/collapse animation */
|
/* Disable the expand/collapse animation */
|
||||||
rotate: none;
|
rotate: none;
|
||||||
}
|
}
|
||||||
@@ -82,13 +83,13 @@ const css = `
|
|||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<WaDetails summary="Toggle Me" class="custom-icon">
|
<SlDetails summary="Toggle Me" class="custom-icon">
|
||||||
<WaIcon name="square-plus" slot="expand-icon" />
|
<SlIcon name="plus-square" slot="expand-icon" />
|
||||||
<WaIcon name="square-minus" slot="collapse-icon" />
|
<SlIcon name="dash-square" slot="collapse-icon" />
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
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
|
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
consequat.
|
consequat.
|
||||||
</WaDetails>
|
</SlDetails>
|
||||||
|
|
||||||
<style>{css}</style>
|
<style>{css}</style>
|
||||||
</>
|
</>
|
||||||
@@ -97,40 +98,38 @@ const App = () => (
|
|||||||
|
|
||||||
### Grouping Details
|
### Grouping Details
|
||||||
|
|
||||||
Details are designed to function independently, but you can simulate a group or "accordion" where only one is shown at a time by listening for the `wa-show` event.
|
Details are designed to function independently, but you can simulate a group or "accordion" where only one is shown at a time by listening for the `sl-show` event.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<div class="details-group-example">
|
<div class="details-group-example">
|
||||||
<wa-details summary="First" open>
|
<sl-details summary="First" open>
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
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.
|
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||||
</wa-details>
|
</sl-details>
|
||||||
|
|
||||||
<wa-details summary="Second">
|
<sl-details summary="Second">
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
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.
|
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||||
</wa-details>
|
</sl-details>
|
||||||
|
|
||||||
<wa-details summary="Third">
|
<sl-details summary="Third">
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
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.
|
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||||
</wa-details>
|
</sl-details>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const container = document.querySelector('.details-group-example');
|
const container = document.querySelector('.details-group-example');
|
||||||
|
|
||||||
// Close all other details when one is shown
|
// Close all other details when one is shown
|
||||||
container.addEventListener('wa-show', event => {
|
container.addEventListener('sl-show', event => {
|
||||||
if (event.target.localName === 'wa-details') {
|
[...container.querySelectorAll('sl-details')].map(details => (details.open = event.target === details));
|
||||||
[...container.querySelectorAll('wa-details')].map(details => (details.open = event.target === details));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.details-group-example wa-details:not(:last-of-type) {
|
.details-group-example sl-details:not(:last-of-type) {
|
||||||
margin-bottom: var(--wa-space-2xs);
|
margin-bottom: var(--sl-spacing-2x-small);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
```
|
```
|
||||||
@@ -1,23 +1,24 @@
|
|||||||
---
|
---
|
||||||
title: Dialog
|
meta:
|
||||||
description: 'Dialogs, sometimes called "modals", appear above the page and require the user''s immediate attention.'
|
title: Dialog
|
||||||
layout: ../../../layouts/ComponentLayout.astro
|
description: 'Dialogs, sometimes called "modals", appear above the page and require the user''s immediate attention.'
|
||||||
|
layout: component
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- cspell:dictionaries lorem-ipsum -->
|
<!-- cspell:dictionaries lorem-ipsum -->
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-dialog label="Dialog" class="dialog-overview">
|
<sl-dialog label="Dialog" class="dialog-overview">
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||||
</wa-dialog>
|
</sl-dialog>
|
||||||
|
|
||||||
<wa-button>Open Dialog</wa-button>
|
<sl-button>Open Dialog</sl-button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const dialog = document.querySelector('.dialog-overview');
|
const dialog = document.querySelector('.dialog-overview');
|
||||||
const openButton = dialog.nextElementSibling;
|
const openButton = dialog.nextElementSibling;
|
||||||
const closeButton = dialog.querySelector('wa-button[slot="footer"]');
|
const closeButton = dialog.querySelector('sl-button[slot="footer"]');
|
||||||
|
|
||||||
openButton.addEventListener('click', () => dialog.show());
|
openButton.addEventListener('click', () => dialog.show());
|
||||||
closeButton.addEventListener('click', () => dialog.hide());
|
closeButton.addEventListener('click', () => dialog.hide());
|
||||||
@@ -26,22 +27,22 @@ layout: ../../../layouts/ComponentLayout.astro
|
|||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WaDialog label="Dialog" open={open} onWaAfterHide={() => setOpen(false)}>
|
<SlDialog label="Dialog" open={open} onSlAfterHide={() => setOpen(false)}>
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
|
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaDialog>
|
</SlDialog>
|
||||||
|
|
||||||
<WaButton onClick={() => setOpen(true)}>Open Dialog</WaButton>
|
<SlButton onClick={() => setOpen(true)}>Open Dialog</SlButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -54,119 +55,127 @@ const App = () => {
|
|||||||
Use the `--width` custom property to set the dialog's width.
|
Use the `--width` custom property to set the dialog's width.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-dialog label="Dialog" class="dialog-width" style="--width: 50vw;">
|
<sl-dialog label="Dialog" class="dialog-width" style="--width: 50vw;">
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||||
</wa-dialog>
|
</sl-dialog>
|
||||||
|
|
||||||
<wa-button>Open Dialog</wa-button>
|
<sl-button>Open Dialog</sl-button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const dialog = document.querySelector('.dialog-width');
|
const dialog = document.querySelector('.dialog-width');
|
||||||
const openButton = dialog.nextElementSibling;
|
const openButton = dialog.nextElementSibling;
|
||||||
const closeButton = dialog.querySelector('wa-button[slot="footer"]');
|
const closeButton = dialog.querySelector('sl-button[slot="footer"]');
|
||||||
|
|
||||||
openButton.addEventListener('click', () => dialog.show());
|
openButton.addEventListener('click', () => dialog.show());
|
||||||
closeButton.addEventListener('click', () => dialog.hide());
|
closeButton.addEventListener('click', () => dialog.hide());
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WaDialog label="Dialog" open={open} style={{ '--width': '50vw' }} onWaAfterHide={() => setOpen(false)}>
|
<SlDialog label="Dialog" open={open} style={{ '--width': '50vw' }} onSlAfterHide={() => setOpen(false)}>
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
|
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaDialog>
|
</SlDialog>
|
||||||
|
|
||||||
<WaButton onClick={() => setOpen(true)}>Open Dialog</WaButton>
|
<SlButton onClick={() => setOpen(true)}>Open Dialog</SlButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
### Scrolling
|
### 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.
|
By design, a dialog's height will never exceed that of the viewport. As such, dialogs will not scroll with the page ensuring the header and footer are always accessible to the user.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-dialog label="Dialog" class="dialog-scrolling">
|
<sl-dialog label="Dialog" class="dialog-scrolling">
|
||||||
<div style="height: 150vh; border: dashed 2px var(--wa-color-surface-border); padding: 0 1rem;">
|
<div style="height: 150vh; border: dashed 2px var(--sl-color-neutral-200); padding: 0 1rem;">
|
||||||
<p>Scroll down and give it a try! 👇</p>
|
<p>Scroll down and give it a try! 👇</p>
|
||||||
</div>
|
</div>
|
||||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||||
</wa-dialog>
|
</sl-dialog>
|
||||||
|
|
||||||
<wa-button>Open Dialog</wa-button>
|
<sl-button>Open Dialog</sl-button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const dialog = document.querySelector('.dialog-scrolling');
|
const dialog = document.querySelector('.dialog-scrolling');
|
||||||
const openButton = dialog.nextElementSibling;
|
const openButton = dialog.nextElementSibling;
|
||||||
const closeButton = dialog.querySelector('wa-button[slot="footer"]');
|
const closeButton = dialog.querySelector('sl-button[slot="footer"]');
|
||||||
|
|
||||||
openButton.addEventListener('click', () => dialog.show());
|
openButton.addEventListener('click', () => dialog.show());
|
||||||
closeButton.addEventListener('click', () => dialog.hide());
|
closeButton.addEventListener('click', () => dialog.hide());
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WaDialog label="Dialog" open={open} onWaAfterHide={() => setOpen(false)}>
|
<SlDialog label="Dialog" open={open} onSlAfterHide={() => setOpen(false)}>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
height: '150vh',
|
height: '150vh',
|
||||||
border: 'dashed 2px var(--wa-color-surface-border)',
|
border: 'dashed 2px var(--sl-color-neutral-200)',
|
||||||
padding: '0 1rem'
|
padding: '0 1rem'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p>Scroll down and give it a try! 👇</p>
|
<p>Scroll down and give it a try! 👇</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
|
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaDialog>
|
</SlDialog>
|
||||||
|
|
||||||
<WaButton onClick={() => setOpen(true)}>Open Dialog</WaButton>
|
<SlButton onClick={() => setOpen(true)}>Open Dialog</SlButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
### Header Actions
|
### 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.
|
The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/components/icon-button) if needed.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-dialog label="Dialog" class="dialog-header-actions">
|
<sl-dialog label="Dialog" class="dialog-header-actions">
|
||||||
<wa-icon-button class="new-window" slot="header-actions" name="arrow-up-right-from-square" variant="solid"></wa-icon-button>
|
<sl-icon-button class="new-window" slot="header-actions" name="box-arrow-up-right"></sl-icon-button>
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||||
</wa-dialog>
|
</sl-dialog>
|
||||||
|
|
||||||
<wa-button>Open Dialog</wa-button>
|
<sl-button>Open Dialog</sl-button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const dialog = document.querySelector('.dialog-header-actions');
|
const dialog = document.querySelector('.dialog-header-actions');
|
||||||
const openButton = dialog.nextElementSibling;
|
const openButton = dialog.nextElementSibling;
|
||||||
const closeButton = dialog.querySelector('wa-button[slot="footer"]');
|
const closeButton = dialog.querySelector('sl-button[slot="footer"]');
|
||||||
const newWindowButton = dialog.querySelector('.new-window');
|
const newWindowButton = dialog.querySelector('.new-window');
|
||||||
|
|
||||||
openButton.addEventListener('click', () => dialog.show());
|
openButton.addEventListener('click', () => dialog.show());
|
||||||
@@ -177,29 +186,29 @@ The header shows a functional close button by default. You can use the `header-a
|
|||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
||||||
import WaIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
|
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WaDialog label="Dialog" open={open} onWaAfterHide={() => setOpen(false)}>
|
<SlDialog label="Dialog" open={open} onSlAfterHide={() => setOpen(false)}>
|
||||||
<WaIconButton
|
<SlIconButton
|
||||||
class="new-window"
|
class="new-window"
|
||||||
slot="header-actions"
|
slot="header-actions"
|
||||||
name="arrow-up-right-from-square"
|
name="box-arrow-up-right"
|
||||||
onClick={() => window.open(location.href)}
|
onClick={() => window.open(location.href)}
|
||||||
/>
|
/>
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
|
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaDialog>
|
</SlDialog>
|
||||||
|
|
||||||
<WaButton onClick={() => setOpen(true)}>Open Dialog</WaButton>
|
<SlButton onClick={() => setOpen(true)}>Open Dialog</SlButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -209,28 +218,28 @@ const App = () => {
|
|||||||
|
|
||||||
By default, dialogs will close when the user clicks the close button, clicks the overlay, or presses the [[Escape]] key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur.
|
By default, dialogs will close when the user clicks the close button, clicks the overlay, or presses the [[Escape]] key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur.
|
||||||
|
|
||||||
To keep the dialog open in such cases, you can cancel the `wa-request-close` event. When canceled, the dialog will remain open and pulse briefly to draw the user's attention to it.
|
To keep the dialog open in such cases, you can cancel the `sl-request-close` event. When canceled, the dialog will remain open and pulse briefly to draw the user's attention to it.
|
||||||
|
|
||||||
You can use `event.detail.source` to determine what triggered the request to close. This example prevents the dialog from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it.
|
You can use `event.detail.source` to determine what triggered the request to close. This example prevents the dialog from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-dialog label="Dialog" class="dialog-deny-close">
|
<sl-dialog label="Dialog" class="dialog-deny-close">
|
||||||
This dialog will not close when you click on the overlay.
|
This dialog will not close when you click on the overlay.
|
||||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||||
</wa-dialog>
|
</sl-dialog>
|
||||||
|
|
||||||
<wa-button>Open Dialog</wa-button>
|
<sl-button>Open Dialog</sl-button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const dialog = document.querySelector('.dialog-deny-close');
|
const dialog = document.querySelector('.dialog-deny-close');
|
||||||
const openButton = dialog.nextElementSibling;
|
const openButton = dialog.nextElementSibling;
|
||||||
const closeButton = dialog.querySelector('wa-button[slot="footer"]');
|
const closeButton = dialog.querySelector('sl-button[slot="footer"]');
|
||||||
|
|
||||||
openButton.addEventListener('click', () => dialog.show());
|
openButton.addEventListener('click', () => dialog.show());
|
||||||
closeButton.addEventListener('click', () => dialog.hide());
|
closeButton.addEventListener('click', () => dialog.hide());
|
||||||
|
|
||||||
// Prevent the dialog from closing when the user clicks on the overlay
|
// Prevent the dialog from closing when the user clicks on the overlay
|
||||||
dialog.addEventListener('wa-request-close', event => {
|
dialog.addEventListener('sl-request-close', event => {
|
||||||
if (event.detail.source === 'overlay') {
|
if (event.detail.source === 'overlay') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
@@ -240,8 +249,8 @@ You can use `event.detail.source` to determine what triggered the request to clo
|
|||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -255,14 +264,14 @@ const App = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WaDialog label="Dialog" open={open} onWaRequestClose={handleRequestClose} onWaAfterHide={() => setOpen(false)}>
|
<SlDialog label="Dialog" open={open} onSlRequestClose={handleRequestClose} onSlAfterHide={() => setOpen(false)}>
|
||||||
This dialog will not close when you click on the overlay.
|
This dialog will not close when you click on the overlay.
|
||||||
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
|
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaDialog>
|
</SlDialog>
|
||||||
|
|
||||||
<WaButton onClick={() => setOpen(true)}>Open Dialog</WaButton>
|
<SlButton onClick={() => setOpen(true)}>Open Dialog</SlButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -273,18 +282,18 @@ const App = () => {
|
|||||||
By default, the dialog's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the dialog. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.
|
By default, the dialog's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the dialog. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-dialog label="Dialog" class="dialog-focus">
|
<sl-dialog label="Dialog" class="dialog-focus">
|
||||||
<wa-input autofocus placeholder="I will have focus when the dialog is opened"></wa-input>
|
<sl-input autofocus placeholder="I will have focus when the dialog is opened"></sl-input>
|
||||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||||
</wa-dialog>
|
</sl-dialog>
|
||||||
|
|
||||||
<wa-button>Open Dialog</wa-button>
|
<sl-button>Open Dialog</sl-button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const dialog = document.querySelector('.dialog-focus');
|
const dialog = document.querySelector('.dialog-focus');
|
||||||
const input = dialog.querySelector('wa-input');
|
const input = dialog.querySelector('sl-input');
|
||||||
const openButton = dialog.nextElementSibling;
|
const openButton = dialog.nextElementSibling;
|
||||||
const closeButton = dialog.querySelector('wa-button[slot="footer"]');
|
const closeButton = dialog.querySelector('sl-button[slot="footer"]');
|
||||||
|
|
||||||
openButton.addEventListener('click', () => dialog.show());
|
openButton.addEventListener('click', () => dialog.show());
|
||||||
closeButton.addEventListener('click', () => dialog.hide());
|
closeButton.addEventListener('click', () => dialog.hide());
|
||||||
@@ -293,28 +302,28 @@ By default, the dialog's panel will gain focus when opened. This allows a subseq
|
|||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
||||||
import WaInput from '@shoelace-style/shoelace/dist/react/input';
|
import SlInput from '@shoelace-style/shoelace/dist/react/input';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WaDialog label="Dialog" open={open} onWaAfterHide={() => setOpen(false)}>
|
<SlDialog label="Dialog" open={open} onSlAfterHide={() => setOpen(false)}>
|
||||||
<WaInput autofocus placeholder="I will have focus when the dialog is opened" />
|
<SlInput autofocus placeholder="I will have focus when the dialog is opened" />
|
||||||
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
|
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaDialog>
|
</SlDialog>
|
||||||
|
|
||||||
<WaButton onClick={() => setOpen(true)}>Open Dialog</WaButton>
|
<SlButton onClick={() => setOpen(true)}>Open Dialog</SlButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
You can further customize initial focus behavior by canceling the `wa-initial-focus` event and setting focus yourself inside the event handler.
|
You can further customize initial focus behavior by canceling the `sl-initial-focus` event and setting focus yourself inside the event handler.
|
||||||
:::
|
:::
|
||||||
158
docs/pages/components/divider.md
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Divider
|
||||||
|
description: Dividers are used to visually separate or group elements.
|
||||||
|
layout: component
|
||||||
|
---
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||||
|
|
||||||
|
const App = () => <SlDivider />;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Width
|
||||||
|
|
||||||
|
Use the `--width` custom property to change the width of the divider.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-divider style="--width: 4px;"></sl-divider>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||||
|
|
||||||
|
const App = () => <SlDivider style={{ '--width': '4px' }} />;
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
### Color
|
||||||
|
|
||||||
|
Use the `--color` custom property to change the color of the divider.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-divider style="--color: tomato;"></sl-divider>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||||
|
|
||||||
|
const App = () => <SlDivider style={{ '--color': 'tomato' }} />;
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
### Spacing
|
||||||
|
|
||||||
|
Use the `--spacing` custom property to change the amount of space between the divider and it's neighboring elements.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<div style="text-align: center;">
|
||||||
|
Above
|
||||||
|
<sl-divider style="--spacing: 2rem;"></sl-divider>
|
||||||
|
Below
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
Above
|
||||||
|
<SlDivider 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.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<div style="display: flex; align-items: center; height: 2rem;">
|
||||||
|
First
|
||||||
|
<sl-divider vertical></sl-divider>
|
||||||
|
Middle
|
||||||
|
<sl-divider vertical></sl-divider>
|
||||||
|
Last
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '2rem'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
First
|
||||||
|
<SlDivider vertical />
|
||||||
|
Middle
|
||||||
|
<SlDivider vertical />
|
||||||
|
Last
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
### Menu Dividers
|
||||||
|
|
||||||
|
Use dividers in [menus](/components/menu) to visually group menu items.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-menu style="max-width: 200px;">
|
||||||
|
<sl-menu-item value="1">Option 1</sl-menu-item>
|
||||||
|
<sl-menu-item value="2">Option 2</sl-menu-item>
|
||||||
|
<sl-menu-item value="3">Option 3</sl-menu-item>
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
<sl-menu-item value="4">Option 4</sl-menu-item>
|
||||||
|
<sl-menu-item value="5">Option 5</sl-menu-item>
|
||||||
|
<sl-menu-item value="6">Option 6</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlMenu style={{ maxWidth: '200px' }}>
|
||||||
|
<SlMenuItem value="1">Option 1</SlMenuItem>
|
||||||
|
<SlMenuItem value="2">Option 2</SlMenuItem>
|
||||||
|
<SlMenuItem value="3">Option 3</SlMenuItem>
|
||||||
|
<sl-divider />
|
||||||
|
<SlMenuItem value="4">Option 4</SlMenuItem>
|
||||||
|
<SlMenuItem value="5">Option 5</SlMenuItem>
|
||||||
|
<SlMenuItem value="6">Option 6</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
@@ -1,23 +1,24 @@
|
|||||||
---
|
---
|
||||||
title: Drawer
|
meta:
|
||||||
description: Drawers slide in from a container to expose additional options and information.
|
title: Drawer
|
||||||
layout: ../../../layouts/ComponentLayout.astro
|
description: Drawers slide in from a container to expose additional options and information.
|
||||||
|
layout: component
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- cspell:dictionaries lorem-ipsum -->
|
<!-- cspell:dictionaries lorem-ipsum -->
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-drawer label="Drawer" class="drawer-overview">
|
<sl-drawer label="Drawer" class="drawer-overview">
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||||
</wa-drawer>
|
</sl-drawer>
|
||||||
|
|
||||||
<wa-button>Open Drawer</wa-button>
|
<sl-button>Open Drawer</sl-button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const drawer = document.querySelector('.drawer-overview');
|
const drawer = document.querySelector('.drawer-overview');
|
||||||
const openButton = drawer.nextElementSibling;
|
const openButton = drawer.nextElementSibling;
|
||||||
const closeButton = drawer.querySelector('wa-button[variant="brand"]');
|
const closeButton = drawer.querySelector('sl-button[variant="primary"]');
|
||||||
|
|
||||||
openButton.addEventListener('click', () => drawer.show());
|
openButton.addEventListener('click', () => drawer.show());
|
||||||
closeButton.addEventListener('click', () => drawer.hide());
|
closeButton.addEventListener('click', () => drawer.hide());
|
||||||
@@ -26,22 +27,22 @@ layout: ../../../layouts/ComponentLayout.astro
|
|||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)}>
|
<SlDrawer label="Drawer" open={open} onSlAfterHide={() => setOpen(false)}>
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
|
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaDrawer>
|
</SlDrawer>
|
||||||
|
|
||||||
<WaButton onClick={() => setOpen(true)}>Open Drawer</WaButton>
|
<SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -54,17 +55,17 @@ const App = () => {
|
|||||||
By default, drawers slide in from the end. To make the drawer slide in from the start, set the `placement` attribute to `start`.
|
By default, drawers slide in from the end. To make the drawer slide in from the start, set the `placement` attribute to `start`.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-drawer label="Drawer" placement="start" class="drawer-placement-start">
|
<sl-drawer label="Drawer" placement="start" class="drawer-placement-start">
|
||||||
This drawer slides in from the start.
|
This drawer slides in from the start.
|
||||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||||
</wa-drawer>
|
</sl-drawer>
|
||||||
|
|
||||||
<wa-button>Open Drawer</wa-button>
|
<sl-button>Open Drawer</sl-button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const drawer = document.querySelector('.drawer-placement-start');
|
const drawer = document.querySelector('.drawer-placement-start');
|
||||||
const openButton = drawer.nextElementSibling;
|
const openButton = drawer.nextElementSibling;
|
||||||
const closeButton = drawer.querySelector('wa-button[variant="brand"]');
|
const closeButton = drawer.querySelector('sl-button[variant="primary"]');
|
||||||
|
|
||||||
openButton.addEventListener('click', () => drawer.show());
|
openButton.addEventListener('click', () => drawer.show());
|
||||||
closeButton.addEventListener('click', () => drawer.hide());
|
closeButton.addEventListener('click', () => drawer.hide());
|
||||||
@@ -73,22 +74,22 @@ By default, drawers slide in from the end. To make the drawer slide in from the
|
|||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WaDrawer label="Drawer" placement="start" open={open} onWaAfterHide={() => setOpen(false)}>
|
<SlDrawer label="Drawer" placement="start" open={open} onSlAfterHide={() => setOpen(false)}>
|
||||||
This drawer slides in from the start.
|
This drawer slides in from the start.
|
||||||
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
|
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaDrawer>
|
</SlDrawer>
|
||||||
|
|
||||||
<WaButton onClick={() => setOpen(true)}>Open Drawer</WaButton>
|
<SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -99,17 +100,17 @@ const App = () => {
|
|||||||
To make the drawer slide in from the top, set the `placement` attribute to `top`.
|
To make the drawer slide in from the top, set the `placement` attribute to `top`.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-drawer label="Drawer" placement="top" class="drawer-placement-top">
|
<sl-drawer label="Drawer" placement="top" class="drawer-placement-top">
|
||||||
This drawer slides in from the top.
|
This drawer slides in from the top.
|
||||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||||
</wa-drawer>
|
</sl-drawer>
|
||||||
|
|
||||||
<wa-button>Open Drawer</wa-button>
|
<sl-button>Open Drawer</sl-button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const drawer = document.querySelector('.drawer-placement-top');
|
const drawer = document.querySelector('.drawer-placement-top');
|
||||||
const openButton = drawer.nextElementSibling;
|
const openButton = drawer.nextElementSibling;
|
||||||
const closeButton = drawer.querySelector('wa-button[variant="brand"]');
|
const closeButton = drawer.querySelector('sl-button[variant="primary"]');
|
||||||
|
|
||||||
openButton.addEventListener('click', () => drawer.show());
|
openButton.addEventListener('click', () => drawer.show());
|
||||||
closeButton.addEventListener('click', () => drawer.hide());
|
closeButton.addEventListener('click', () => drawer.hide());
|
||||||
@@ -118,22 +119,22 @@ To make the drawer slide in from the top, set the `placement` attribute to `top`
|
|||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WaDrawer label="Drawer" placement="top" open={open} onWaAfterHide={() => setOpen(false)}>
|
<SlDrawer label="Drawer" placement="top" open={open} onSlAfterHide={() => setOpen(false)}>
|
||||||
This drawer slides in from the top.
|
This drawer slides in from the top.
|
||||||
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
|
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaDrawer>
|
</SlDrawer>
|
||||||
|
|
||||||
<WaButton onClick={() => setOpen(true)}>Open Drawer</WaButton>
|
<SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -144,17 +145,17 @@ const App = () => {
|
|||||||
To make the drawer slide in from the bottom, set the `placement` attribute to `bottom`.
|
To make the drawer slide in from the bottom, set the `placement` attribute to `bottom`.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-drawer label="Drawer" placement="bottom" class="drawer-placement-bottom">
|
<sl-drawer label="Drawer" placement="bottom" class="drawer-placement-bottom">
|
||||||
This drawer slides in from the bottom.
|
This drawer slides in from the bottom.
|
||||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||||
</wa-drawer>
|
</sl-drawer>
|
||||||
|
|
||||||
<wa-button>Open Drawer</wa-button>
|
<sl-button>Open Drawer</sl-button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const drawer = document.querySelector('.drawer-placement-bottom');
|
const drawer = document.querySelector('.drawer-placement-bottom');
|
||||||
const openButton = drawer.nextElementSibling;
|
const openButton = drawer.nextElementSibling;
|
||||||
const closeButton = drawer.querySelector('wa-button[variant="brand"]');
|
const closeButton = drawer.querySelector('sl-button[variant="primary"]');
|
||||||
|
|
||||||
openButton.addEventListener('click', () => drawer.show());
|
openButton.addEventListener('click', () => drawer.show());
|
||||||
closeButton.addEventListener('click', () => drawer.hide());
|
closeButton.addEventListener('click', () => drawer.hide());
|
||||||
@@ -163,22 +164,22 @@ To make the drawer slide in from the bottom, set the `placement` attribute to `b
|
|||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WaDrawer label="Drawer" placement="bottom" open={open} onWaAfterHide={() => setOpen(false)}>
|
<SlDrawer label="Drawer" placement="bottom" open={open} onSlAfterHide={() => setOpen(false)}>
|
||||||
This drawer slides in from the bottom.
|
This drawer slides in from the bottom.
|
||||||
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
|
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaDrawer>
|
</SlDrawer>
|
||||||
|
|
||||||
<WaButton onClick={() => setOpen(true)}>Open Drawer</WaButton>
|
<SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -192,32 +193,34 @@ Unlike normal drawers, contained drawers are not modal. This means they do not s
|
|||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<div
|
<div
|
||||||
style="position: relative; border: solid 2px var(--wa-color-surface-border); height: 300px; padding: 1rem; margin-bottom: 1rem;"
|
style="position: relative; border: solid 2px var(--sl-panel-border-color); 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.
|
The drawer will be contained to this box. This content won't shift or be affected in any way when the drawer opens.
|
||||||
|
|
||||||
<wa-drawer label="Drawer" contained class="drawer-contained" style="--size: 50%;">
|
<sl-drawer label="Drawer" contained class="drawer-contained" style="--size: 50%;">
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||||
</wa-drawer>
|
</sl-drawer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<wa-button>Toggle Drawer</wa-button>
|
<sl-button>Toggle Drawer</sl-button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const drawer = document.querySelector('.drawer-contained');
|
const drawer = document.querySelector('.drawer-contained');
|
||||||
const openButton = drawer.parentElement.nextElementSibling;
|
const openButton = drawer.parentElement.nextElementSibling;
|
||||||
const closeButton = drawer.querySelector('wa-button[variant="brand"]');
|
const closeButton = drawer.querySelector('sl-button[variant="primary"]');
|
||||||
|
|
||||||
openButton.addEventListener('click', () => (drawer.open = !drawer.open));
|
openButton.addEventListener('click', () => (drawer.open = !drawer.open));
|
||||||
closeButton.addEventListener('click', () => drawer.hide());
|
closeButton.addEventListener('click', () => drawer.hide());
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -227,7 +230,7 @@ const App = () => {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
border: 'solid 2px var(--wa-color-surface-border)',
|
border: 'solid 2px var(--sl-panel-border-color)',
|
||||||
height: '300px',
|
height: '300px',
|
||||||
padding: '1rem',
|
padding: '1rem',
|
||||||
marginBottom: '1rem'
|
marginBottom: '1rem'
|
||||||
@@ -235,144 +238,154 @@ const App = () => {
|
|||||||
>
|
>
|
||||||
The drawer will be contained to this box. This content won't shift or be affected in any way when the drawer
|
The drawer will be contained to this box. This content won't shift or be affected in any way when the drawer
|
||||||
opens.
|
opens.
|
||||||
<WaDrawer
|
<SlDrawer
|
||||||
label="Drawer"
|
label="Drawer"
|
||||||
contained
|
contained
|
||||||
no-modal
|
no-modal
|
||||||
open={open}
|
open={open}
|
||||||
onWaAfterHide={() => setOpen(false)}
|
onSlAfterHide={() => setOpen(false)}
|
||||||
style={{ '--size': '50%' }}
|
style={{ '--size': '50%' }}
|
||||||
>
|
>
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
|
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaDrawer>
|
</SlDrawer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<WaButton onClick={() => setOpen(true)}>Open Drawer</WaButton>
|
<SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
### Custom Size
|
### 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`.
|
Use the `--size` custom property to set the drawer's size. This will be applied to the drawer's width or height depending on its `placement`.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-drawer label="Drawer" class="drawer-custom-size" style="--size: 50vw;">
|
<sl-drawer label="Drawer" class="drawer-custom-size" style="--size: 50vw;">
|
||||||
This drawer is always 50% of the viewport.
|
This drawer is always 50% of the viewport.
|
||||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||||
</wa-drawer>
|
</sl-drawer>
|
||||||
|
|
||||||
<wa-button>Open Drawer</wa-button>
|
<sl-button>Open Drawer</sl-button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const drawer = document.querySelector('.drawer-custom-size');
|
const drawer = document.querySelector('.drawer-custom-size');
|
||||||
const openButton = drawer.nextElementSibling;
|
const openButton = drawer.nextElementSibling;
|
||||||
const closeButton = drawer.querySelector('wa-button[variant="brand"]');
|
const closeButton = drawer.querySelector('sl-button[variant="primary"]');
|
||||||
|
|
||||||
openButton.addEventListener('click', () => drawer.show());
|
openButton.addEventListener('click', () => drawer.show());
|
||||||
closeButton.addEventListener('click', () => drawer.hide());
|
closeButton.addEventListener('click', () => drawer.hide());
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)} style={{ '--size': '50vw' }}>
|
<SlDrawer label="Drawer" open={open} onSlAfterHide={() => setOpen(false)} style={{ '--size': '50vw' }}>
|
||||||
This drawer is always 50% of the viewport.
|
This drawer is always 50% of the viewport.
|
||||||
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
|
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaDrawer>
|
</SlDrawer>
|
||||||
|
|
||||||
<WaButton onClick={() => setOpen(true)}>Open Drawer</WaButton>
|
<SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
### Scrolling
|
### 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.
|
By design, a drawer's height will never exceed 100% of its container. As such, drawers will not scroll with the page to ensure the header and footer are always accessible to the user.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-drawer label="Drawer" class="drawer-scrolling">
|
<sl-drawer label="Drawer" class="drawer-scrolling">
|
||||||
<div style="height: 150vh; border: dashed 2px var(--wa-color-surface-border); padding: 0 1rem;">
|
<div style="height: 150vh; border: dashed 2px var(--sl-color-neutral-200); padding: 0 1rem;">
|
||||||
<p>Scroll down and give it a try! 👇</p>
|
<p>Scroll down and give it a try! 👇</p>
|
||||||
</div>
|
</div>
|
||||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||||
</wa-drawer>
|
</sl-drawer>
|
||||||
|
|
||||||
<wa-button>Open Drawer</wa-button>
|
<sl-button>Open Drawer</sl-button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const drawer = document.querySelector('.drawer-scrolling');
|
const drawer = document.querySelector('.drawer-scrolling');
|
||||||
const openButton = drawer.nextElementSibling;
|
const openButton = drawer.nextElementSibling;
|
||||||
const closeButton = drawer.querySelector('wa-button[variant="brand"]');
|
const closeButton = drawer.querySelector('sl-button[variant="primary"]');
|
||||||
|
|
||||||
openButton.addEventListener('click', () => drawer.show());
|
openButton.addEventListener('click', () => drawer.show());
|
||||||
closeButton.addEventListener('click', () => drawer.hide());
|
closeButton.addEventListener('click', () => drawer.hide());
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)}>
|
<SlDrawer label="Drawer" open={open} onSlAfterHide={() => setOpen(false)}>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
height: '150vh',
|
height: '150vh',
|
||||||
border: 'dashed 2px var(--wa-color-surface-border)',
|
border: 'dashed 2px var(--sl-color-neutral-200)',
|
||||||
padding: '0 1rem'
|
padding: '0 1rem'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p>Scroll down and give it a try! 👇</p>
|
<p>Scroll down and give it a try! 👇</p>
|
||||||
</div>
|
</div>
|
||||||
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
|
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaDrawer>
|
</SlDrawer>
|
||||||
|
|
||||||
<WaButton onClick={() => setOpen(true)}>Open Drawer</WaButton>
|
<SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
### Header Actions
|
### 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.
|
The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/components/icon-button) if needed.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-drawer label="Drawer" class="drawer-header-actions">
|
<sl-drawer label="Drawer" class="drawer-header-actions">
|
||||||
<wa-icon-button class="new-window" slot="header-actions" name="arrow-up-right-from-square" variant="solid"></wa-icon-button>
|
<sl-icon-button class="new-window" slot="header-actions" name="box-arrow-up-right"></sl-icon-button>
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||||
</wa-drawer>
|
</sl-drawer>
|
||||||
|
|
||||||
<wa-button>Open Drawer</wa-button>
|
<sl-button>Open Drawer</sl-button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const drawer = document.querySelector('.drawer-header-actions');
|
const drawer = document.querySelector('.drawer-header-actions');
|
||||||
const openButton = drawer.nextElementSibling;
|
const openButton = drawer.nextElementSibling;
|
||||||
const closeButton = drawer.querySelector('wa-button[variant="brand"]');
|
const closeButton = drawer.querySelector('sl-button[variant="primary"]');
|
||||||
const newWindowButton = drawer.querySelector('.new-window');
|
const newWindowButton = drawer.querySelector('.new-window');
|
||||||
|
|
||||||
openButton.addEventListener('click', () => drawer.show());
|
openButton.addEventListener('click', () => drawer.show());
|
||||||
@@ -383,24 +396,24 @@ The header shows a functional close button by default. You can use the `header-a
|
|||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
||||||
import WaIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
|
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)}>
|
<SlDrawer label="Drawer" open={open} onSlAfterHide={() => setOpen(false)}>
|
||||||
<WaIconButton slot="header-actions" name="arrow-up-right-from-square" onClick={() => window.open(location.href)} />
|
<SlIconButton slot="header-actions" name="box-arrow-up-right" onClick={() => window.open(location.href)} />
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
|
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaDrawer>
|
</SlDrawer>
|
||||||
|
|
||||||
<WaButton onClick={() => setOpen(true)}>Open Drawer</WaButton>
|
<SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -410,28 +423,28 @@ const App = () => {
|
|||||||
|
|
||||||
By default, drawers will close when the user clicks the close button, clicks the overlay, or presses the [[Escape]] key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur.
|
By default, drawers will close when the user clicks the close button, clicks the overlay, or presses the [[Escape]] key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur.
|
||||||
|
|
||||||
To keep the drawer open in such cases, you can cancel the `wa-request-close` event. When canceled, the drawer will remain open and pulse briefly to draw the user's attention to it.
|
To keep the drawer open in such cases, you can cancel the `sl-request-close` event. When canceled, the drawer will remain open and pulse briefly to draw the user's attention to it.
|
||||||
|
|
||||||
You can use `event.detail.source` to determine what triggered the request to close. This example prevents the drawer from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it.
|
You can use `event.detail.source` to determine what triggered the request to close. This example prevents the drawer from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-drawer label="Drawer" class="drawer-deny-close">
|
<sl-drawer label="Drawer" class="drawer-deny-close">
|
||||||
This drawer will not close when you click on the overlay.
|
This drawer will not close when you click on the overlay.
|
||||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||||
</wa-drawer>
|
</sl-drawer>
|
||||||
|
|
||||||
<wa-button>Open Drawer</wa-button>
|
<sl-button>Open Drawer</sl-button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const drawer = document.querySelector('.drawer-deny-close');
|
const drawer = document.querySelector('.drawer-deny-close');
|
||||||
const openButton = drawer.nextElementSibling;
|
const openButton = drawer.nextElementSibling;
|
||||||
const closeButton = drawer.querySelector('wa-button[variant="brand"]');
|
const closeButton = drawer.querySelector('sl-button[variant="primary"]');
|
||||||
|
|
||||||
openButton.addEventListener('click', () => drawer.show());
|
openButton.addEventListener('click', () => drawer.show());
|
||||||
closeButton.addEventListener('click', () => drawer.hide());
|
closeButton.addEventListener('click', () => drawer.hide());
|
||||||
|
|
||||||
// Prevent the drawer from closing when the user clicks on the overlay
|
// Prevent the drawer from closing when the user clicks on the overlay
|
||||||
drawer.addEventListener('wa-request-close', event => {
|
drawer.addEventListener('sl-request-close', event => {
|
||||||
if (event.detail.source === 'overlay') {
|
if (event.detail.source === 'overlay') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
@@ -441,8 +454,8 @@ You can use `event.detail.source` to determine what triggered the request to clo
|
|||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -456,14 +469,14 @@ const App = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WaDrawer label="Drawer" open={open} onWaRequestClose={handleRequestClose} onWaAfterHide={() => setOpen(false)}>
|
<SlDrawer label="Drawer" open={open} onSlRequestClose={handleRequestClose} onSlAfterHide={() => setOpen(false)}>
|
||||||
This drawer will not close when you click on the overlay.
|
This drawer will not close when you click on the overlay.
|
||||||
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
|
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||||
Save & Close
|
Save & Close
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaDrawer>
|
</SlDrawer>
|
||||||
|
|
||||||
<WaButton onClick={() => setOpen(true)}>Open Drawer</WaButton>
|
<SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -474,18 +487,18 @@ const App = () => {
|
|||||||
By default, the drawer's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the drawer. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.
|
By default, the drawer's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the drawer. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-drawer label="Drawer" class="drawer-focus">
|
<sl-drawer label="Drawer" class="drawer-focus">
|
||||||
<wa-input autofocus placeholder="I will have focus when the drawer is opened"></wa-input>
|
<sl-input autofocus placeholder="I will have focus when the drawer is opened"></sl-input>
|
||||||
<wa-button slot="footer" variant="brand">Close</wa-button>
|
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||||
</wa-drawer>
|
</sl-drawer>
|
||||||
|
|
||||||
<wa-button>Open Drawer</wa-button>
|
<sl-button>Open Drawer</sl-button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const drawer = document.querySelector('.drawer-focus');
|
const drawer = document.querySelector('.drawer-focus');
|
||||||
const input = drawer.querySelector('wa-input');
|
const input = drawer.querySelector('sl-input');
|
||||||
const openButton = drawer.nextElementSibling;
|
const openButton = drawer.nextElementSibling;
|
||||||
const closeButton = drawer.querySelector('wa-button[variant="brand"]');
|
const closeButton = drawer.querySelector('sl-button[variant="primary"]');
|
||||||
|
|
||||||
openButton.addEventListener('click', () => drawer.show());
|
openButton.addEventListener('click', () => drawer.show());
|
||||||
closeButton.addEventListener('click', () => drawer.hide());
|
closeButton.addEventListener('click', () => drawer.hide());
|
||||||
@@ -494,28 +507,28 @@ By default, the drawer's panel will gain focus when opened. This allows a subseq
|
|||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
import SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';
|
||||||
import WaInput from '@shoelace-style/shoelace/dist/react/input';
|
import SlInput from '@shoelace-style/shoelace/dist/react/input';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)}>
|
<SlDrawer label="Drawer" open={open} onSlAfterHide={() => setOpen(false)}>
|
||||||
<WaInput autofocus placeholder="I will have focus when the drawer is opened" />
|
<SlInput autofocus placeholder="I will have focus when the drawer is opened" />
|
||||||
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
|
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaDrawer>
|
</SlDrawer>
|
||||||
|
|
||||||
<WaButton onClick={() => setOpen(true)}>Open Drawer</WaButton>
|
<SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
You can further customize initial focus behavior by canceling the `wa-initial-focus` event and setting focus yourself inside the event handler.
|
You can further customize initial focus behavior by canceling the `sl-initial-focus` event and setting focus yourself inside the event handler.
|
||||||
:::
|
:::
|
||||||
393
docs/pages/components/dropdown.md
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Dropdown
|
||||||
|
description: 'Dropdowns expose additional content that "drops down" in a panel.'
|
||||||
|
layout: component
|
||||||
|
---
|
||||||
|
|
||||||
|
Dropdowns consist of a trigger and a panel. By default, activating the trigger will expose the panel and interacting outside of the panel will close it.
|
||||||
|
|
||||||
|
Dropdowns are designed to work well with [menus](/components/menu) to provide a list of options the user can select from. However, dropdowns can also be used in lower-level applications (e.g. [color picker](/components/color-picker)). The API gives you complete control over showing, hiding, and positioning the panel.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-dropdown>
|
||||||
|
<sl-button slot="trigger" caret>Dropdown</sl-button>
|
||||||
|
<sl-menu>
|
||||||
|
<sl-menu-item>Dropdown Item 1</sl-menu-item>
|
||||||
|
<sl-menu-item>Dropdown Item 2</sl-menu-item>
|
||||||
|
<sl-menu-item>Dropdown Item 3</sl-menu-item>
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
<sl-menu-item type="checkbox" checked>Checkbox</sl-menu-item>
|
||||||
|
<sl-menu-item disabled>Disabled</sl-menu-item>
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
<sl-menu-item>
|
||||||
|
Prefix
|
||||||
|
<sl-icon slot="prefix" name="gift"></sl-icon>
|
||||||
|
</sl-menu-item>
|
||||||
|
<sl-menu-item>
|
||||||
|
Suffix Icon
|
||||||
|
<sl-icon slot="suffix" name="heart"></sl-icon>
|
||||||
|
</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
</sl-dropdown>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||||
|
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
|
||||||
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlDropdown>
|
||||||
|
<SlButton slot="trigger" caret>
|
||||||
|
Dropdown
|
||||||
|
</SlButton>
|
||||||
|
<SlMenu>
|
||||||
|
<SlMenuItem>Dropdown Item 1</SlMenuItem>
|
||||||
|
<SlMenuItem>Dropdown Item 2</SlMenuItem>
|
||||||
|
<SlMenuItem>Dropdown Item 3</SlMenuItem>
|
||||||
|
<SlDivider />
|
||||||
|
<SlMenuItem type="checkbox" checked>
|
||||||
|
Checkbox
|
||||||
|
</SlMenuItem>
|
||||||
|
<SlMenuItem disabled>Disabled</SlMenuItem>
|
||||||
|
<SlDivider />
|
||||||
|
<SlMenuItem>
|
||||||
|
Prefix
|
||||||
|
<SlIcon slot="prefix" name="gift" />
|
||||||
|
</SlMenuItem>
|
||||||
|
<SlMenuItem>
|
||||||
|
Suffix Icon
|
||||||
|
<SlIcon slot="suffix" name="heart" />
|
||||||
|
</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
</SlDropdown>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Getting the Selected Item
|
||||||
|
|
||||||
|
When dropdowns are used with [menus](/components/menu), you can listen for the [`sl-select`](/components/menu#events) event to determine which menu item was selected. The menu item element will be exposed in `event.detail.item`. You can set `value` props to make it easier to identify commands.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<div class="dropdown-selection">
|
||||||
|
<sl-dropdown>
|
||||||
|
<sl-button slot="trigger" caret>Edit</sl-button>
|
||||||
|
<sl-menu>
|
||||||
|
<sl-menu-item value="cut">Cut</sl-menu-item>
|
||||||
|
<sl-menu-item value="copy">Copy</sl-menu-item>
|
||||||
|
<sl-menu-item value="paste">Paste</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
</sl-dropdown>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const container = document.querySelector('.dropdown-selection');
|
||||||
|
const dropdown = container.querySelector('sl-dropdown');
|
||||||
|
|
||||||
|
dropdown.addEventListener('sl-select', event => {
|
||||||
|
const selectedItem = event.detail.item;
|
||||||
|
console.log(selectedItem.value);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
function handleSelect(event) {
|
||||||
|
const selectedItem = event.detail.item;
|
||||||
|
console.log(selectedItem.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SlDropdown>
|
||||||
|
<SlButton slot="trigger" caret>
|
||||||
|
Edit
|
||||||
|
</SlButton>
|
||||||
|
<SlMenu onSlSelect={handleSelect}>
|
||||||
|
<SlMenuItem value="cut">Cut</SlMenuItem>
|
||||||
|
<SlMenuItem value="copy">Copy</SlMenuItem>
|
||||||
|
<SlMenuItem value="paste">Paste</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
</SlDropdown>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can listen for the `click` event on individual menu items. Note that, using this approach, disabled menu items will still emit a `click` event.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<div class="dropdown-selection-alt">
|
||||||
|
<sl-dropdown>
|
||||||
|
<sl-button slot="trigger" caret>Edit</sl-button>
|
||||||
|
<sl-menu>
|
||||||
|
<sl-menu-item value="cut">Cut</sl-menu-item>
|
||||||
|
<sl-menu-item value="copy">Copy</sl-menu-item>
|
||||||
|
<sl-menu-item value="paste">Paste</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
</sl-dropdown>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const container = document.querySelector('.dropdown-selection-alt');
|
||||||
|
const cut = container.querySelector('sl-menu-item[value="cut"]');
|
||||||
|
const copy = container.querySelector('sl-menu-item[value="copy"]');
|
||||||
|
const paste = container.querySelector('sl-menu-item[value="paste"]');
|
||||||
|
|
||||||
|
cut.addEventListener('click', () => console.log('cut'));
|
||||||
|
copy.addEventListener('click', () => console.log('copy'));
|
||||||
|
paste.addEventListener('click', () => console.log('paste'));
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
function handleCut() {
|
||||||
|
console.log('cut');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCopy() {
|
||||||
|
console.log('copy');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePaste() {
|
||||||
|
console.log('paste');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SlDropdown>
|
||||||
|
<SlButton slot="trigger" caret>
|
||||||
|
Edit
|
||||||
|
</SlButton>
|
||||||
|
<SlMenu>
|
||||||
|
<SlMenuItem onClick={handleCut}>Cut</SlMenuItem>
|
||||||
|
<SlMenuItem onClick={handleCopy}>Copy</SlMenuItem>
|
||||||
|
<SlMenuItem onClick={handlePaste}>Paste</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
</SlDropdown>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Placement
|
||||||
|
|
||||||
|
The preferred placement of the dropdown can be set with the `placement` attribute. Note that the actual position may vary to ensure the panel remains in the viewport.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-dropdown placement="top-start">
|
||||||
|
<sl-button slot="trigger" caret>Edit</sl-button>
|
||||||
|
<sl-menu>
|
||||||
|
<sl-menu-item>Cut</sl-menu-item>
|
||||||
|
<sl-menu-item>Copy</sl-menu-item>
|
||||||
|
<sl-menu-item>Paste</sl-menu-item>
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
<sl-menu-item>Find</sl-menu-item>
|
||||||
|
<sl-menu-item>Replace</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
</sl-dropdown>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||||
|
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlDropdown placement="top-start">
|
||||||
|
<SlButton slot="trigger" caret>
|
||||||
|
Edit
|
||||||
|
</SlButton>
|
||||||
|
<SlMenu>
|
||||||
|
<SlMenuItem>Cut</SlMenuItem>
|
||||||
|
<SlMenuItem>Copy</SlMenuItem>
|
||||||
|
<SlMenuItem>Paste</SlMenuItem>
|
||||||
|
<SlDivider />
|
||||||
|
<SlMenuItem>Find</SlMenuItem>
|
||||||
|
<SlMenuItem>Replace</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
</SlDropdown>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Distance
|
||||||
|
|
||||||
|
The distance from the panel to the trigger can be customized using the `distance` attribute. This value is specified in pixels.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-dropdown distance="30">
|
||||||
|
<sl-button slot="trigger" caret>Edit</sl-button>
|
||||||
|
<sl-menu>
|
||||||
|
<sl-menu-item>Cut</sl-menu-item>
|
||||||
|
<sl-menu-item>Copy</sl-menu-item>
|
||||||
|
<sl-menu-item>Paste</sl-menu-item>
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
<sl-menu-item>Find</sl-menu-item>
|
||||||
|
<sl-menu-item>Replace</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
</sl-dropdown>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||||
|
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlDropdown distance={30}>
|
||||||
|
<SlButton slot="trigger" caret>
|
||||||
|
Edit
|
||||||
|
</SlButton>
|
||||||
|
<SlMenu>
|
||||||
|
<SlMenuItem>Cut</SlMenuItem>
|
||||||
|
<SlMenuItem>Copy</SlMenuItem>
|
||||||
|
<SlMenuItem>Paste</SlMenuItem>
|
||||||
|
<SlDivider />
|
||||||
|
<SlMenuItem>Find</SlMenuItem>
|
||||||
|
<SlMenuItem>Replace</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
</SlDropdown>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Skidding
|
||||||
|
|
||||||
|
The offset of the panel along the trigger can be customized using the `skidding` attribute. This value is specified in pixels.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-dropdown skidding="30">
|
||||||
|
<sl-button slot="trigger" caret>Edit</sl-button>
|
||||||
|
<sl-menu>
|
||||||
|
<sl-menu-item>Cut</sl-menu-item>
|
||||||
|
<sl-menu-item>Copy</sl-menu-item>
|
||||||
|
<sl-menu-item>Paste</sl-menu-item>
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
<sl-menu-item>Find</sl-menu-item>
|
||||||
|
<sl-menu-item>Replace</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
</sl-dropdown>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||||
|
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlDropdown skidding={30}>
|
||||||
|
<SlButton slot="trigger" caret>
|
||||||
|
Edit
|
||||||
|
</SlButton>
|
||||||
|
<SlMenu>
|
||||||
|
<SlMenuItem>Cut</SlMenuItem>
|
||||||
|
<SlMenuItem>Copy</SlMenuItem>
|
||||||
|
<SlMenuItem>Paste</SlMenuItem>
|
||||||
|
<SlDivider />
|
||||||
|
<SlMenuItem>Find</SlMenuItem>
|
||||||
|
<SlMenuItem>Replace</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
</SlDropdown>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hoisting
|
||||||
|
|
||||||
|
Dropdown panels will be clipped if they're inside a container that has `overflow: auto|hidden`. The `hoist` attribute forces the panel to use a fixed positioning strategy, allowing it to break out of the container. In this case, the panel will be positioned relative to its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<div class="dropdown-hoist">
|
||||||
|
<sl-dropdown>
|
||||||
|
<sl-button slot="trigger" caret>No Hoist</sl-button>
|
||||||
|
<sl-menu>
|
||||||
|
<sl-menu-item>Item 1</sl-menu-item>
|
||||||
|
<sl-menu-item>Item 2</sl-menu-item>
|
||||||
|
<sl-menu-item>Item 3</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
</sl-dropdown>
|
||||||
|
|
||||||
|
<sl-dropdown hoist>
|
||||||
|
<sl-button slot="trigger" caret>Hoist</sl-button>
|
||||||
|
<sl-menu>
|
||||||
|
<sl-menu-item>Item 1</sl-menu-item>
|
||||||
|
<sl-menu-item>Item 2</sl-menu-item>
|
||||||
|
<sl-menu-item>Item 3</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
</sl-dropdown>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dropdown-hoist {
|
||||||
|
position: relative;
|
||||||
|
border: solid 2px var(--sl-panel-border-color);
|
||||||
|
padding: var(--sl-spacing-medium);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||||
|
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
|
||||||
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
|
||||||
|
const css = `
|
||||||
|
.dropdown-hoist {
|
||||||
|
border: solid 2px var(--sl-panel-border-color);
|
||||||
|
padding: var(--sl-spacing-medium);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<div className="dropdown-hoist">
|
||||||
|
<SlDropdown>
|
||||||
|
<SlButton slot="trigger" caret>
|
||||||
|
No Hoist
|
||||||
|
</SlButton>
|
||||||
|
<SlMenu>
|
||||||
|
<SlMenuItem>Item 1</SlMenuItem>
|
||||||
|
<SlMenuItem>Item 2</SlMenuItem>
|
||||||
|
<SlMenuItem>Item 3</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
</SlDropdown>
|
||||||
|
|
||||||
|
<SlDropdown hoist>
|
||||||
|
<SlButton slot="trigger" caret>
|
||||||
|
Hoist
|
||||||
|
</SlButton>
|
||||||
|
<SlMenu>
|
||||||
|
<SlMenuItem>Item 1</SlMenuItem>
|
||||||
|
<SlMenuItem>Item 2</SlMenuItem>
|
||||||
|
<SlMenuItem>Item 3</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
</SlDropdown>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>{css}</style>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
134
docs/pages/components/format-bytes.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Format Bytes
|
||||||
|
description: Formats a number as a human readable bytes value.
|
||||||
|
layout: component
|
||||||
|
---
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<div class="format-bytes-overview">
|
||||||
|
The file is <sl-format-bytes value="1000"></sl-format-bytes> in size. <br /><br />
|
||||||
|
<sl-input type="number" value="1000" label="Number to Format" style="max-width: 180px;"></sl-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const container = document.querySelector('.format-bytes-overview');
|
||||||
|
const formatter = container.querySelector('sl-format-bytes');
|
||||||
|
const input = container.querySelector('sl-input');
|
||||||
|
|
||||||
|
input.addEventListener('sl-input', () => (formatter.value = input.value || 0));
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import { useState } from 'react';
|
||||||
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
|
import SlFormatBytes from '@shoelace-style/shoelace/dist/react/format-bytes';
|
||||||
|
import SlInput from '@shoelace-style/shoelace/dist/react/input';
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const [value, setValue] = useState(1000);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
The file is <SlFormatBytes value={value} /> in size.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<SlInput
|
||||||
|
type="number"
|
||||||
|
value={value}
|
||||||
|
label="Number to Format"
|
||||||
|
style={{ maxWidth: '180px' }}
|
||||||
|
onSlInput={event => setValue(event.target.value)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Formatting Bytes
|
||||||
|
|
||||||
|
Set the `value` attribute to a number to get the value in bytes.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-format-bytes value="12"></sl-format-bytes><br />
|
||||||
|
<sl-format-bytes value="1200"></sl-format-bytes><br />
|
||||||
|
<sl-format-bytes value="1200000"></sl-format-bytes><br />
|
||||||
|
<sl-format-bytes value="1200000000"></sl-format-bytes>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlFormatBytes from '@shoelace-style/shoelace/dist/react/format-bytes';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlFormatBytes value="12" />
|
||||||
|
<br />
|
||||||
|
<SlFormatBytes value="1200" />
|
||||||
|
<br />
|
||||||
|
<SlFormatBytes value="1200000" />
|
||||||
|
<br />
|
||||||
|
<SlFormatBytes value="1200000000" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Formatting Bits
|
||||||
|
|
||||||
|
To get the value in bits, set the `unit` attribute to `bit`.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-format-bytes value="12" unit="bit"></sl-format-bytes><br />
|
||||||
|
<sl-format-bytes value="1200" unit="bit"></sl-format-bytes><br />
|
||||||
|
<sl-format-bytes value="1200000" unit="bit"></sl-format-bytes><br />
|
||||||
|
<sl-format-bytes value="1200000000" unit="bit"></sl-format-bytes>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlFormatBytes from '@shoelace-style/shoelace/dist/react/format-bytes';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlFormatBytes value="12" unit="bit" />
|
||||||
|
<br />
|
||||||
|
<SlFormatBytes value="1200" unit="bit" />
|
||||||
|
<br />
|
||||||
|
<SlFormatBytes value="1200000" unit="bit" />
|
||||||
|
<br />
|
||||||
|
<SlFormatBytes value="1200000000" unit="bit" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Localization
|
||||||
|
|
||||||
|
Use the `lang` attribute to set the number formatting locale.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-format-bytes value="12" lang="de"></sl-format-bytes><br />
|
||||||
|
<sl-format-bytes value="1200" lang="de"></sl-format-bytes><br />
|
||||||
|
<sl-format-bytes value="1200000" lang="de"></sl-format-bytes><br />
|
||||||
|
<sl-format-bytes value="1200000000" lang="de"></sl-format-bytes>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlFormatBytes from '@shoelace-style/shoelace/dist/react/format-bytes';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlFormatBytes value="12" lang="de" />
|
||||||
|
<br />
|
||||||
|
<SlFormatBytes value="1200" lang="de" />
|
||||||
|
<br />
|
||||||
|
<SlFormatBytes value="1200000" lang="de" />
|
||||||
|
<br />
|
||||||
|
<SlFormatBytes value="1200000000" lang="de" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
@@ -1,20 +1,21 @@
|
|||||||
---
|
---
|
||||||
title: Format Date
|
meta:
|
||||||
description: Formats a date/time using the specified locale and options.
|
title: Format Date
|
||||||
layout: ../../../layouts/ComponentLayout.astro
|
description: Formats a date/time using the specified locale and options.
|
||||||
|
layout: component
|
||||||
---
|
---
|
||||||
|
|
||||||
Localization is handled by the browser's [`Intl.DateTimeFormat` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat). No language packs are required.
|
Localization is handled by the browser's [`Intl.DateTimeFormat` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat). No language packs are required.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<!-- Web Awesome 2 release date 🎉 -->
|
<!-- Shoelace 2 release date 🎉 -->
|
||||||
<wa-format-date date="2020-07-15T09:17:00-04:00"></wa-format-date>
|
<sl-format-date date="2020-07-15T09:17:00-04:00"></sl-format-date>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaFormatDate from '@shoelace-style/shoelace/dist/react/format-date';
|
import SlFormatDate from '@shoelace-style/shoelace/dist/react/format-date';
|
||||||
|
|
||||||
const App = () => <WaFormatDate date="2020-07-15T09:17:00-04:00" />;
|
const App = () => <SlFormatDate date="2020-07-15T09:17:00-04:00" />;
|
||||||
```
|
```
|
||||||
|
|
||||||
The `date` attribute determines the date/time to use when formatting. It must be a string that [`Date.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) can interpret or a [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object set via JavaScript. If omitted, the current date/time will be assumed.
|
The `date` attribute determines the date/time to use when formatting. It must be a string that [`Date.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) can interpret or a [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object set via JavaScript. If omitted, the current date/time will be assumed.
|
||||||
@@ -31,51 +32,51 @@ Formatting options are based on those found in the [`Intl.DateTimeFormat` API](h
|
|||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<!-- Human-readable date -->
|
<!-- Human-readable date -->
|
||||||
<wa-format-date month="long" day="numeric" year="numeric"></wa-format-date><br />
|
<sl-format-date month="long" day="numeric" year="numeric"></sl-format-date><br />
|
||||||
|
|
||||||
<!-- Time -->
|
<!-- Time -->
|
||||||
<wa-format-date hour="numeric" minute="numeric"></wa-format-date><br />
|
<sl-format-date hour="numeric" minute="numeric"></sl-format-date><br />
|
||||||
|
|
||||||
<!-- Weekday -->
|
<!-- Weekday -->
|
||||||
<wa-format-date weekday="long"></wa-format-date><br />
|
<sl-format-date weekday="long"></sl-format-date><br />
|
||||||
|
|
||||||
<!-- Month -->
|
<!-- Month -->
|
||||||
<wa-format-date month="long"></wa-format-date><br />
|
<sl-format-date month="long"></sl-format-date><br />
|
||||||
|
|
||||||
<!-- Year -->
|
<!-- Year -->
|
||||||
<wa-format-date year="numeric"></wa-format-date><br />
|
<sl-format-date year="numeric"></sl-format-date><br />
|
||||||
|
|
||||||
<!-- No formatting options -->
|
<!-- No formatting options -->
|
||||||
<wa-format-date></wa-format-date>
|
<sl-format-date></sl-format-date>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaFormatDate from '@shoelace-style/shoelace/dist/react/format-date';
|
import SlFormatDate from '@shoelace-style/shoelace/dist/react/format-date';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
{/* Human-readable date */}
|
{/* Human-readable date */}
|
||||||
<WaFormatDate month="long" day="numeric" year="numeric" />
|
<SlFormatDate month="long" day="numeric" year="numeric" />
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
{/* Time */}
|
{/* Time */}
|
||||||
<WaFormatDate hour="numeric" minute="numeric" />
|
<SlFormatDate hour="numeric" minute="numeric" />
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
{/* Weekday */}
|
{/* Weekday */}
|
||||||
<WaFormatDate weekday="long" />
|
<SlFormatDate weekday="long" />
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
{/* Month */}
|
{/* Month */}
|
||||||
<WaFormatDate month="long" />
|
<SlFormatDate month="long" />
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
{/* Year */}
|
{/* Year */}
|
||||||
<WaFormatDate year="numeric" />
|
<SlFormatDate year="numeric" />
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
{/* No formatting options */}
|
{/* No formatting options */}
|
||||||
<WaFormatDate />
|
<SlFormatDate />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
@@ -85,18 +86,18 @@ const App = () => (
|
|||||||
By default, the browser will determine whether to use 12-hour or 24-hour time. To force one or the other, set the `hour-format` attribute to `12` or `24`.
|
By default, the browser will determine whether to use 12-hour or 24-hour time. To force one or the other, set the `hour-format` attribute to `12` or `24`.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-format-date hour="numeric" minute="numeric" hour-format="12"></wa-format-date><br />
|
<sl-format-date hour="numeric" minute="numeric" hour-format="12"></sl-format-date><br />
|
||||||
<wa-format-date hour="numeric" minute="numeric" hour-format="24"></wa-format-date>
|
<sl-format-date hour="numeric" minute="numeric" hour-format="24"></sl-format-date>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaFormatDate from '@shoelace-style/shoelace/dist/react/format-date';
|
import SlFormatDate from '@shoelace-style/shoelace/dist/react/format-date';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
<WaFormatDate hour="numeric" minute="numeric" hour-format="12" />
|
<SlFormatDate hour="numeric" minute="numeric" hour-format="12" />
|
||||||
<br />
|
<br />
|
||||||
<WaFormatDate hour="numeric" minute="numeric" hour-format="24" />
|
<SlFormatDate hour="numeric" minute="numeric" hour-format="24" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
@@ -106,21 +107,21 @@ const App = () => (
|
|||||||
Use the `lang` attribute to set the date/time formatting locale.
|
Use the `lang` attribute to set the date/time formatting locale.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
English: <wa-format-date lang="en"></wa-format-date><br />
|
English: <sl-format-date lang="en"></sl-format-date><br />
|
||||||
French: <wa-format-date lang="fr"></wa-format-date><br />
|
French: <sl-format-date lang="fr"></sl-format-date><br />
|
||||||
Russian: <wa-format-date lang="ru"></wa-format-date>
|
Russian: <sl-format-date lang="ru"></sl-format-date>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaFormatDate from '@shoelace-style/shoelace/dist/react/format-date';
|
import SlFormatDate from '@shoelace-style/shoelace/dist/react/format-date';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<>
|
<>
|
||||||
English: <WaFormatDate lang="en" />
|
English: <SlFormatDate lang="en" />
|
||||||
<br />
|
<br />
|
||||||
French: <WaFormatDate lang="fr" />
|
French: <SlFormatDate lang="fr" />
|
||||||
<br />
|
<br />
|
||||||
Russian: <WaFormatDate lang="ru" />
|
Russian: <SlFormatDate lang="ru" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
139
docs/pages/components/format-number.md
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Format Number
|
||||||
|
description: Formats a number using the specified locale and options.
|
||||||
|
layout: component
|
||||||
|
---
|
||||||
|
|
||||||
|
Localization is handled by the browser's [`Intl.NumberFormat` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat). No language packs are required.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<div class="format-number-overview">
|
||||||
|
<sl-format-number value="1000"></sl-format-number>
|
||||||
|
<br /><br />
|
||||||
|
<sl-input type="number" value="1000" label="Number to Format" style="max-width: 180px;"></sl-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const container = document.querySelector('.format-number-overview');
|
||||||
|
const formatter = container.querySelector('sl-format-number');
|
||||||
|
const input = container.querySelector('sl-input');
|
||||||
|
|
||||||
|
input.addEventListener('sl-input', () => (formatter.value = input.value || 0));
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import { useState } from 'react';
|
||||||
|
import SlFormatNumber from '@shoelace-style/shoelace/dist/react/format-number';
|
||||||
|
import SlInput from '@shoelace-style/shoelace/dist/react/input';
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const [value, setValue] = useState(1000);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SlFormatNumber value={value} />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<SlInput
|
||||||
|
type="number"
|
||||||
|
value={value}
|
||||||
|
label="Number to Format"
|
||||||
|
style={{ maxWidth: '180px' }}
|
||||||
|
onSlInput={event => setValue(event.target.value)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Percentages
|
||||||
|
|
||||||
|
To get the value as a percent, set the `type` attribute to `percent`.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-format-number type="percent" value="0"></sl-format-number><br />
|
||||||
|
<sl-format-number type="percent" value="0.25"></sl-format-number><br />
|
||||||
|
<sl-format-number type="percent" value="0.50"></sl-format-number><br />
|
||||||
|
<sl-format-number type="percent" value="0.75"></sl-format-number><br />
|
||||||
|
<sl-format-number type="percent" value="1"></sl-format-number>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlFormatNumber from '@shoelace-style/shoelace/dist/react/format-number';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlFormatNumber type="percent" value={0} />
|
||||||
|
<br />
|
||||||
|
<SlFormatNumber type="percent" value={0.25} />
|
||||||
|
<br />
|
||||||
|
<SlFormatNumber type="percent" value={0.5} />
|
||||||
|
<br />
|
||||||
|
<SlFormatNumber type="percent" value={0.75} />
|
||||||
|
<br />
|
||||||
|
<SlFormatNumber type="percent" value={1} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Localization
|
||||||
|
|
||||||
|
Use the `lang` attribute to set the number formatting locale.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
English: <sl-format-number value="2000" lang="en" minimum-fraction-digits="2"></sl-format-number><br />
|
||||||
|
German: <sl-format-number value="2000" lang="de" minimum-fraction-digits="2"></sl-format-number><br />
|
||||||
|
Russian: <sl-format-number value="2000" lang="ru" minimum-fraction-digits="2"></sl-format-number>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlFormatNumber from '@shoelace-style/shoelace/dist/react/format-number';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
English: <SlFormatNumber value="2000" lang="en" minimum-fraction-digits="2" />
|
||||||
|
<br />
|
||||||
|
German: <SlFormatNumber value="2000" lang="de" minimum-fraction-digits="2" />
|
||||||
|
<br />
|
||||||
|
Russian: <SlFormatNumber value="2000" lang="ru" minimum-fraction-digits="2" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Currency
|
||||||
|
|
||||||
|
To format a number as a monetary value, set the `type` attribute to `currency` and set the `currency` attribute to the desired ISO 4217 currency code. You should also specify `lang` to ensure the the number is formatted correctly for the target locale.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-format-number type="currency" currency="USD" value="2000" lang="en-US"></sl-format-number><br />
|
||||||
|
<sl-format-number type="currency" currency="GBP" value="2000" lang="en-GB"></sl-format-number><br />
|
||||||
|
<sl-format-number type="currency" currency="EUR" value="2000" lang="de"></sl-format-number><br />
|
||||||
|
<sl-format-number type="currency" currency="RUB" value="2000" lang="ru"></sl-format-number><br />
|
||||||
|
<sl-format-number type="currency" currency="CNY" value="2000" lang="zh-cn"></sl-format-number>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlFormatNumber from '@shoelace-style/shoelace/dist/react/format-number';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlFormatNumber type="currency" currency="USD" value="2000" lang="en-US" />
|
||||||
|
<br />
|
||||||
|
<SlFormatNumber type="currency" currency="GBP" value="2000" lang="en-GB" />
|
||||||
|
<br />
|
||||||
|
<SlFormatNumber type="currency" currency="EUR" value="2000" lang="de" />
|
||||||
|
<br />
|
||||||
|
<SlFormatNumber type="currency" currency="RUB" value="2000" lang="ru" />
|
||||||
|
<br />
|
||||||
|
<SlFormatNumber type="currency" currency="CNY" value="2000" lang="zh-cn" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
153
docs/pages/components/icon-button.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Icon Button
|
||||||
|
description: Icons buttons are simple, icon-only buttons that can be used for actions and in toolbars.
|
||||||
|
layout: component
|
||||||
|
---
|
||||||
|
|
||||||
|
For a full list of icons that come bundled with Shoelace, refer to the [icon component](/components/icon).
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-icon-button name="gear" label="Settings"></sl-icon-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
|
||||||
|
|
||||||
|
const App = () => <SlIconButton name="gear" label="Settings" />;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Sizes
|
||||||
|
|
||||||
|
Icon buttons inherit their parent element's `font-size`.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-icon-button name="pencil" label="Edit" style="font-size: 1.5rem;"></sl-icon-button>
|
||||||
|
<sl-icon-button name="pencil" label="Edit" style="font-size: 2rem;"></sl-icon-button>
|
||||||
|
<sl-icon-button name="pencil" label="Edit" style="font-size: 2.5rem;"></sl-icon-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlIconButton name="pencil" label="Edit" style={{ fontSize: '1.5rem' }} />
|
||||||
|
<SlIconButton name="pencil" label="Edit" style={{ fontSize: '2rem' }} />
|
||||||
|
<SlIconButton name="pencil" 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">
|
||||||
|
<sl-icon-button name="type-bold" label="Bold"></sl-icon-button>
|
||||||
|
<sl-icon-button name="type-italic" label="Italic"></sl-icon-button>
|
||||||
|
<sl-icon-button name="type-underline" label="Underline"></sl-icon-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.icon-button-color sl-icon-button::part(base) {
|
||||||
|
color: #b00091;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button-color sl-icon-button::part(base):hover,
|
||||||
|
.icon-button-color sl-icon-button::part(base):focus {
|
||||||
|
color: #c913aa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button-color sl-icon-button::part(base):active {
|
||||||
|
color: #960077;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
|
||||||
|
|
||||||
|
const css = `
|
||||||
|
.icon-button-color sl-icon-button::part(base) {
|
||||||
|
color: #b00091;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button-color sl-icon-button::part(base):hover,
|
||||||
|
.icon-button-color sl-icon-button::part(base):focus {
|
||||||
|
color: #c913aa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button-color sl-icon-button::part(base):active {
|
||||||
|
color: #960077;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<div className="icon-button-color">
|
||||||
|
<SlIconButton name="type-bold" label="Bold" />
|
||||||
|
<SlIconButton name="type-italic" label="Italic" />
|
||||||
|
<SlIconButton name="type-underline" label="Underline" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>{css}</style>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Link Buttons
|
||||||
|
|
||||||
|
Use the `href` attribute to convert the button to a link.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-icon-button name="gear" label="Settings" href="https://example.com" target="_blank"></sl-icon-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
|
||||||
|
|
||||||
|
const App = () => <SlIconButton name="gear" label="Settings" href="https://example.com" target="_blank" />;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Icon Button with Tooltip
|
||||||
|
|
||||||
|
Wrap a tooltip around an icon button to provide contextual information to the user.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-tooltip content="Settings">
|
||||||
|
<sl-icon-button name="gear" label="Settings"></sl-icon-button>
|
||||||
|
</sl-tooltip>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
|
||||||
|
import SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlTooltip content="Settings">
|
||||||
|
<SlIconButton name="gear" label="Settings" />
|
||||||
|
</SlTooltip>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disabled
|
||||||
|
|
||||||
|
Use the `disabled` attribute to disable the icon button.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-icon-button name="gear" label="Settings" disabled></sl-icon-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
|
||||||
|
|
||||||
|
const App = () => <SlIconButton name="gear" label="Settings" disabled />;
|
||||||
|
```
|
||||||
869
docs/pages/components/icon.md
Normal file
@@ -0,0 +1,869 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Icon
|
||||||
|
description: Icons are symbols that can be used to represent various options within an application.
|
||||||
|
layout: component
|
||||||
|
---
|
||||||
|
|
||||||
|
Shoelace comes bundled with over 1,500 icons courtesy of the [Bootstrap Icons](https://icons.getbootstrap.com/) project. These icons are part of the `default` icon library. If you prefer, you can register [custom icon libraries](#icon-libraries) as well.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Depending on how you're loading Shoelace, you may need to copy icon assets and/or [set the base path](/getting-started/installation/#setting-the-base-path) so Shoelace knows where to load them from. Otherwise, icons may not appear and you'll see 404 Not Found errors in the dev console.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Default Icons
|
||||||
|
|
||||||
|
All available icons in the `default` icon library are shown below. Click or tap on any icon to copy its name, then you can use it in your HTML like this.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<sl-icon name="icon-name-here"></sl-icon>
|
||||||
|
```
|
||||||
|
|
||||||
|
<div class="icon-search">
|
||||||
|
<div class="icon-search-controls">
|
||||||
|
<sl-input placeholder="Search Icons" clearable>
|
||||||
|
<sl-icon slot="prefix" name="search"></sl-icon>
|
||||||
|
</sl-input>
|
||||||
|
<sl-select value="outline">
|
||||||
|
<sl-option value="outline">Outlined</sl-option>
|
||||||
|
<sl-option value="fill">Filled</sl-option>
|
||||||
|
<sl-option value="all">All icons</sl-option>
|
||||||
|
</sl-select>
|
||||||
|
</div>
|
||||||
|
<div class="icon-list"></div>
|
||||||
|
<input type="text" class="icon-copy-input" aria-hidden="true" tabindex="-1">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Colors
|
||||||
|
|
||||||
|
Icons inherit their color from the current text color. Thus, you can set the `color` property on the `<sl-icon>` element or an ancestor to change the color.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<div style="color: #4a90e2;">
|
||||||
|
<sl-icon name="exclamation-triangle"></sl-icon>
|
||||||
|
<sl-icon name="archive"></sl-icon>
|
||||||
|
<sl-icon name="battery-charging"></sl-icon>
|
||||||
|
<sl-icon name="bell"></sl-icon>
|
||||||
|
</div>
|
||||||
|
<div style="color: #9013fe;">
|
||||||
|
<sl-icon name="clock"></sl-icon>
|
||||||
|
<sl-icon name="cloud"></sl-icon>
|
||||||
|
<sl-icon name="download"></sl-icon>
|
||||||
|
<sl-icon name="file-earmark"></sl-icon>
|
||||||
|
</div>
|
||||||
|
<div style="color: #417505;">
|
||||||
|
<sl-icon name="flag"></sl-icon>
|
||||||
|
<sl-icon name="heart"></sl-icon>
|
||||||
|
<sl-icon name="image"></sl-icon>
|
||||||
|
<sl-icon name="lightning"></sl-icon>
|
||||||
|
</div>
|
||||||
|
<div style="color: #f5a623;">
|
||||||
|
<sl-icon name="mic"></sl-icon>
|
||||||
|
<sl-icon name="search"></sl-icon>
|
||||||
|
<sl-icon name="star"></sl-icon>
|
||||||
|
<sl-icon name="trash"></sl-icon>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<div style={{ color: '#4a90e2' }}>
|
||||||
|
<SlIcon name="exclamation-triangle"></SlIcon>
|
||||||
|
<SlIcon name="archive"></SlIcon>
|
||||||
|
<SlIcon name="battery-charging"></SlIcon>
|
||||||
|
<SlIcon name="bell"></SlIcon>
|
||||||
|
</div>
|
||||||
|
<div style={{ color: '#9013fe' }}>
|
||||||
|
<SlIcon name="clock"></SlIcon>
|
||||||
|
<SlIcon name="cloud"></SlIcon>
|
||||||
|
<SlIcon name="download"></SlIcon>
|
||||||
|
<SlIcon name="file-earmark"></SlIcon>
|
||||||
|
</div>
|
||||||
|
<div style={{ color: '#417505' }}>
|
||||||
|
<SlIcon name="flag"></SlIcon>
|
||||||
|
<SlIcon name="heart"></SlIcon>
|
||||||
|
<SlIcon name="image"></SlIcon>
|
||||||
|
<SlIcon name="lightning"></SlIcon>
|
||||||
|
</div>
|
||||||
|
<div style={{ color: '#f5a623' }}>
|
||||||
|
<SlIcon name="mic"></SlIcon>
|
||||||
|
<SlIcon name="search"></SlIcon>
|
||||||
|
<SlIcon name="star"></SlIcon>
|
||||||
|
<SlIcon name="trash"></SlIcon>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
### Sizing
|
||||||
|
|
||||||
|
Icons are sized relative to the current font size. To change their size, set the `font-size` property on the icon itself or on a parent element as shown below.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<div style="font-size: 32px;">
|
||||||
|
<sl-icon name="exclamation-triangle"></sl-icon>
|
||||||
|
<sl-icon name="archive"></sl-icon>
|
||||||
|
<sl-icon name="battery-charging"></sl-icon>
|
||||||
|
<sl-icon name="bell"></sl-icon>
|
||||||
|
<sl-icon name="clock"></sl-icon>
|
||||||
|
<sl-icon name="cloud"></sl-icon>
|
||||||
|
<sl-icon name="download"></sl-icon>
|
||||||
|
<sl-icon name="file-earmark"></sl-icon>
|
||||||
|
<sl-icon name="flag"></sl-icon>
|
||||||
|
<sl-icon name="heart"></sl-icon>
|
||||||
|
<sl-icon name="image"></sl-icon>
|
||||||
|
<sl-icon name="lightning"></sl-icon>
|
||||||
|
<sl-icon name="mic"></sl-icon>
|
||||||
|
<sl-icon name="search"></sl-icon>
|
||||||
|
<sl-icon name="star"></sl-icon>
|
||||||
|
<sl-icon name="trash"></sl-icon>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<div style={{ fontSize: '32px' }}>
|
||||||
|
<SlIcon name="exclamation-triangle" />
|
||||||
|
<SlIcon name="archive" />
|
||||||
|
<SlIcon name="battery-charging" />
|
||||||
|
<SlIcon name="bell" />
|
||||||
|
<SlIcon name="clock" />
|
||||||
|
<SlIcon name="cloud" />
|
||||||
|
<SlIcon name="download" />
|
||||||
|
<SlIcon name="file-earmark" />
|
||||||
|
<SlIcon name="flag" />
|
||||||
|
<SlIcon name="heart" />
|
||||||
|
<SlIcon name="image" />
|
||||||
|
<SlIcon name="lightning" />
|
||||||
|
<SlIcon name="mic" />
|
||||||
|
<SlIcon name="search" />
|
||||||
|
<SlIcon name="star" />
|
||||||
|
<SlIcon name="trash" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
### Labels
|
||||||
|
|
||||||
|
For non-decorative icons, use the `label` attribute to announce it to assistive devices.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-icon name="star-fill" label="Add to favorites"></sl-icon>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
|
const App = () => <SlIcon name="star-fill" label="Add to favorites" />;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Icons
|
||||||
|
|
||||||
|
Custom icons can be loaded individually with the `src` attribute. Only SVGs on a local or CORS-enabled endpoint are supported. If you're using more than one custom icon, it might make sense to register a [custom icon library](#icon-libraries).
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-icon src="https://shoelace.style/assets/images/shoe.svg" style="font-size: 8rem;"></sl-icon>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
|
||||||
|
const App = () => <SlIcon src="https://shoelace.style/assets/images/shoe.svg" style={{ fontSize: '8rem' }}></SlIcon>;
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
## Icon Libraries
|
||||||
|
|
||||||
|
You can register additional icons to use with the `<sl-icon>` component through icon libraries. Icon files can exist locally or on a CORS-enabled endpoint (e.g. a CDN). There is no limit to how many icon libraries you can register and there is no cost associated with registering them, as individual icons are only requested when they're used.
|
||||||
|
|
||||||
|
Shoelace ships with two built-in icon libraries, `default` and `system`. The [default icon library](#customizing-the-default-library) contains all of the icons in the Bootstrap Icons project. The [system icon library](#customizing-the-system-library) contains only a small subset of icons that are used internally by Shoelace components.
|
||||||
|
|
||||||
|
To register an additional icon library, use the `registerIconLibrary()` function that's exported from `utilities/icon-library.js`. At a minimum, you must provide a name and a resolver function. The resolver function translates an icon name to a URL where the corresponding SVG file exists. Refer to the examples below to better understand how it works.
|
||||||
|
|
||||||
|
If necessary, a mutator function can be used to mutate the SVG element before rendering. This is necessary for some libraries due to the many possible ways SVGs are crafted. For example, icons should ideally inherit the current text color via `currentColor`, so you may need to apply `fill="currentColor` or `stroke="currentColor"` to the SVG element using this function.
|
||||||
|
|
||||||
|
Here's an example that registers an icon library located in the `/assets/icons` directory.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script type="module">
|
||||||
|
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||||
|
|
||||||
|
registerIconLibrary('my-icons', {
|
||||||
|
resolver: name => `/assets/icons/${name}.svg`,
|
||||||
|
mutator: svg => svg.setAttribute('fill', 'currentColor')
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
To display an icon, set the `library` and `name` attributes of an `<sl-icon>` element.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- This will show the icon located at /assets/icons/smile.svg -->
|
||||||
|
<sl-icon library="my-icons" name="smile"></sl-icon>
|
||||||
|
```
|
||||||
|
|
||||||
|
If an icon is used before registration occurs, it will be empty initially but shown when registered.
|
||||||
|
|
||||||
|
The following examples demonstrate how to register a number of popular, open source icon libraries via CDN. Feel free to adapt the code as you see fit to use your own origin or naming conventions.
|
||||||
|
|
||||||
|
### Boxicons
|
||||||
|
|
||||||
|
This will register the [Boxicons](https://boxicons.com/) library using the jsDelivr CDN. This library has three variations: regular (`bx-*`), solid (`bxs-*`), and logos (`bxl-*`). A mutator function is required to set the SVG's `fill` to `currentColor`.
|
||||||
|
|
||||||
|
Icons in this library are licensed under the [Creative Commons 4.0 License](https://github.com/atisawd/boxicons#license).
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<script type="module">
|
||||||
|
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||||
|
|
||||||
|
registerIconLibrary('boxicons', {
|
||||||
|
resolver: name => {
|
||||||
|
let folder = 'regular';
|
||||||
|
if (name.substring(0, 4) === 'bxs-') folder = 'solid';
|
||||||
|
if (name.substring(0, 4) === 'bxl-') folder = 'logos';
|
||||||
|
return `https://cdn.jsdelivr.net/npm/boxicons@2.0.5/svg/${folder}/${name}.svg`;
|
||||||
|
},
|
||||||
|
mutator: svg => svg.setAttribute('fill', 'currentColor')
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="font-size: 24px;">
|
||||||
|
<sl-icon library="boxicons" name="bx-bot"></sl-icon>
|
||||||
|
<sl-icon library="boxicons" name="bx-cookie"></sl-icon>
|
||||||
|
<sl-icon library="boxicons" name="bx-joystick"></sl-icon>
|
||||||
|
<sl-icon library="boxicons" name="bx-save"></sl-icon>
|
||||||
|
<sl-icon library="boxicons" name="bx-server"></sl-icon>
|
||||||
|
<sl-icon library="boxicons" name="bx-wine"></sl-icon>
|
||||||
|
<br />
|
||||||
|
<sl-icon library="boxicons" name="bxs-bot"></sl-icon>
|
||||||
|
<sl-icon library="boxicons" name="bxs-cookie"></sl-icon>
|
||||||
|
<sl-icon library="boxicons" name="bxs-joystick"></sl-icon>
|
||||||
|
<sl-icon library="boxicons" name="bxs-save"></sl-icon>
|
||||||
|
<sl-icon library="boxicons" name="bxs-server"></sl-icon>
|
||||||
|
<sl-icon library="boxicons" name="bxs-wine"></sl-icon>
|
||||||
|
<br />
|
||||||
|
<sl-icon library="boxicons" name="bxl-apple"></sl-icon>
|
||||||
|
<sl-icon library="boxicons" name="bxl-chrome"></sl-icon>
|
||||||
|
<sl-icon library="boxicons" name="bxl-edge"></sl-icon>
|
||||||
|
<sl-icon library="boxicons" name="bxl-firefox"></sl-icon>
|
||||||
|
<sl-icon library="boxicons" name="bxl-opera"></sl-icon>
|
||||||
|
<sl-icon library="boxicons" name="bxl-microsoft"></sl-icon>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lucide
|
||||||
|
|
||||||
|
This will register the [Lucide](https://lucide.dev/) icon library using the jsDelivr CDN. This project is a community-maintained fork of the popular [Feather](https://feathericons.com/) icon library.
|
||||||
|
|
||||||
|
Icons in this library are licensed under the [MIT License](https://github.com/lucide-icons/lucide/blob/master/LICENSE).
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<div style="font-size: 24px;">
|
||||||
|
<sl-icon library="lucide" name="feather"></sl-icon>
|
||||||
|
<sl-icon library="lucide" name="pie-chart"></sl-icon>
|
||||||
|
<sl-icon library="lucide" name="settings"></sl-icon>
|
||||||
|
<sl-icon library="lucide" name="map-pin"></sl-icon>
|
||||||
|
<sl-icon library="lucide" name="printer"></sl-icon>
|
||||||
|
<sl-icon library="lucide" name="shopping-cart"></sl-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||||
|
|
||||||
|
registerIconLibrary('lucide', {
|
||||||
|
resolver: name => `https://cdn.jsdelivr.net/npm/lucide-static@0.16.29/icons/${name}.svg`
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Font Awesome
|
||||||
|
|
||||||
|
This will register the [Font Awesome Free](https://fontawesome.com/) library using the jsDelivr CDN. This library has three variations: regular (`far-*`), solid (`fas-*`), and brands (`fab-*`). A mutator function is required to set the SVG's `fill` to `currentColor`.
|
||||||
|
|
||||||
|
Icons in this library are licensed under the [Font Awesome Free License](https://github.com/FortAwesome/Font-Awesome/blob/master/LICENSE.txt). Some of the icons that appear on the Font Awesome website require a license and are therefore not available in the CDN.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<script type="module">
|
||||||
|
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||||
|
|
||||||
|
registerIconLibrary('fa', {
|
||||||
|
resolver: name => {
|
||||||
|
const filename = name.replace(/^fa[rbs]-/, '');
|
||||||
|
let folder = 'regular';
|
||||||
|
if (name.substring(0, 4) === 'fas-') folder = 'solid';
|
||||||
|
if (name.substring(0, 4) === 'fab-') folder = 'brands';
|
||||||
|
return `https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.1/svgs/${folder}/${filename}.svg`;
|
||||||
|
},
|
||||||
|
mutator: svg => svg.setAttribute('fill', 'currentColor')
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="font-size: 24px;">
|
||||||
|
<sl-icon library="fa" name="far-bell"></sl-icon>
|
||||||
|
<sl-icon library="fa" name="far-comment"></sl-icon>
|
||||||
|
<sl-icon library="fa" name="far-hand-point-right"></sl-icon>
|
||||||
|
<sl-icon library="fa" name="far-hdd"></sl-icon>
|
||||||
|
<sl-icon library="fa" name="far-heart"></sl-icon>
|
||||||
|
<sl-icon library="fa" name="far-star"></sl-icon>
|
||||||
|
<br />
|
||||||
|
<sl-icon library="fa" name="fas-archive"></sl-icon>
|
||||||
|
<sl-icon library="fa" name="fas-book"></sl-icon>
|
||||||
|
<sl-icon library="fa" name="fas-chess-knight"></sl-icon>
|
||||||
|
<sl-icon library="fa" name="fas-dice"></sl-icon>
|
||||||
|
<sl-icon library="fa" name="fas-pizza-slice"></sl-icon>
|
||||||
|
<sl-icon library="fa" name="fas-scroll"></sl-icon>
|
||||||
|
<br />
|
||||||
|
<sl-icon library="fa" name="fab-apple"></sl-icon>
|
||||||
|
<sl-icon library="fa" name="fab-chrome"></sl-icon>
|
||||||
|
<sl-icon library="fa" name="fab-edge"></sl-icon>
|
||||||
|
<sl-icon library="fa" name="fab-firefox"></sl-icon>
|
||||||
|
<sl-icon library="fa" name="fab-opera"></sl-icon>
|
||||||
|
<sl-icon library="fa" name="fab-microsoft"></sl-icon>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Heroicons
|
||||||
|
|
||||||
|
This will register the [Heroicons](https://heroicons.com/) library using the jsDelivr CDN.
|
||||||
|
|
||||||
|
Icons in this library are licensed under the [MIT License](https://github.com/tailwindlabs/heroicons/blob/master/LICENSE).
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<script type="module">
|
||||||
|
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||||
|
|
||||||
|
registerIconLibrary('heroicons', {
|
||||||
|
resolver: name => `https://cdn.jsdelivr.net/npm/heroicons@2.0.1/24/outline/${name}.svg`
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="font-size: 24px;">
|
||||||
|
<sl-icon library="heroicons" name="chat-bubble-left"></sl-icon>
|
||||||
|
<sl-icon library="heroicons" name="cloud"></sl-icon>
|
||||||
|
<sl-icon library="heroicons" name="cog"></sl-icon>
|
||||||
|
<sl-icon library="heroicons" name="document-text"></sl-icon>
|
||||||
|
<sl-icon library="heroicons" name="gift"></sl-icon>
|
||||||
|
<sl-icon library="heroicons" name="speaker-wave"></sl-icon>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Iconoir
|
||||||
|
|
||||||
|
This will register the [Iconoir](https://iconoir.com/) library using the jsDelivr CDN.
|
||||||
|
|
||||||
|
Icons in this library are licensed under the [MIT License](https://github.com/lucaburgio/iconoir/blob/master/LICENSE).
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<script type="module">
|
||||||
|
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||||
|
|
||||||
|
registerIconLibrary('iconoir', {
|
||||||
|
resolver: name => `https://cdn.jsdelivr.net/gh/lucaburgio/iconoir@latest/icons/${name}.svg`
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="font-size: 24px;">
|
||||||
|
<sl-icon library="iconoir" name="check-circled-outline"></sl-icon>
|
||||||
|
<sl-icon library="iconoir" name="drawer"></sl-icon>
|
||||||
|
<sl-icon library="iconoir" name="keyframes"></sl-icon>
|
||||||
|
<sl-icon library="iconoir" name="headset-help"></sl-icon>
|
||||||
|
<sl-icon library="iconoir" name="color-picker"></sl-icon>
|
||||||
|
<sl-icon library="iconoir" name="wifi"></sl-icon>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ionicons
|
||||||
|
|
||||||
|
This will register the [Ionicons](https://ionicons.com/) library using the jsDelivr CDN. This library has three variations: outline (default), filled (`*-filled`), and sharp (`*-sharp`). A mutator function is required to polyfill a handful of styles we're not including.
|
||||||
|
|
||||||
|
Icons in this library are licensed under the [MIT License](https://github.com/ionic-team/ionicons/blob/master/LICENSE).
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<script type="module">
|
||||||
|
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||||
|
|
||||||
|
registerIconLibrary('ionicons', {
|
||||||
|
resolver: name => `https://cdn.jsdelivr.net/npm/ionicons@5.1.2/dist/ionicons/svg/${name}.svg`,
|
||||||
|
mutator: svg => {
|
||||||
|
svg.setAttribute('fill', 'currentColor');
|
||||||
|
svg.setAttribute('stroke', 'currentColor');
|
||||||
|
[...svg.querySelectorAll('.ionicon-fill-none')].map(el => el.setAttribute('fill', 'none'));
|
||||||
|
[...svg.querySelectorAll('.ionicon-stroke-width')].map(el => el.setAttribute('stroke-width', '32px'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="font-size: 24px;">
|
||||||
|
<sl-icon library="ionicons" name="alarm"></sl-icon>
|
||||||
|
<sl-icon library="ionicons" name="american-football"></sl-icon>
|
||||||
|
<sl-icon library="ionicons" name="bug"></sl-icon>
|
||||||
|
<sl-icon library="ionicons" name="chatbubble"></sl-icon>
|
||||||
|
<sl-icon library="ionicons" name="settings"></sl-icon>
|
||||||
|
<sl-icon library="ionicons" name="warning"></sl-icon>
|
||||||
|
<br />
|
||||||
|
<sl-icon library="ionicons" name="alarm-outline"></sl-icon>
|
||||||
|
<sl-icon library="ionicons" name="american-football-outline"></sl-icon>
|
||||||
|
<sl-icon library="ionicons" name="bug-outline"></sl-icon>
|
||||||
|
<sl-icon library="ionicons" name="chatbubble-outline"></sl-icon>
|
||||||
|
<sl-icon library="ionicons" name="settings-outline"></sl-icon>
|
||||||
|
<sl-icon library="ionicons" name="warning-outline"></sl-icon>
|
||||||
|
<br />
|
||||||
|
<sl-icon library="ionicons" name="alarm-sharp"></sl-icon>
|
||||||
|
<sl-icon library="ionicons" name="american-football-sharp"></sl-icon>
|
||||||
|
<sl-icon library="ionicons" name="bug-sharp"></sl-icon>
|
||||||
|
<sl-icon library="ionicons" name="chatbubble-sharp"></sl-icon>
|
||||||
|
<sl-icon library="ionicons" name="settings-sharp"></sl-icon>
|
||||||
|
<sl-icon library="ionicons" name="warning-sharp"></sl-icon>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Jam Icons
|
||||||
|
|
||||||
|
This will register the [Jam Icons](https://jam-icons.com/) library using the jsDelivr CDN. This library has two variations: regular (default) and filled (`*-f`). A mutator function is required to set the SVG's `fill` to `currentColor`.
|
||||||
|
|
||||||
|
Icons in this library are licensed under the [MIT License](https://github.com/michaelampr/jam/blob/master/LICENSE).
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<script type="module">
|
||||||
|
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||||
|
|
||||||
|
registerIconLibrary('jam', {
|
||||||
|
resolver: name => `https://cdn.jsdelivr.net/npm/jam-icons@2.0.0/svg/${name}.svg`,
|
||||||
|
mutator: svg => svg.setAttribute('fill', 'currentColor')
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="font-size: 24px;">
|
||||||
|
<sl-icon library="jam" name="calendar"></sl-icon>
|
||||||
|
<sl-icon library="jam" name="camera"></sl-icon>
|
||||||
|
<sl-icon library="jam" name="filter"></sl-icon>
|
||||||
|
<sl-icon library="jam" name="leaf"></sl-icon>
|
||||||
|
<sl-icon library="jam" name="picture"></sl-icon>
|
||||||
|
<sl-icon library="jam" name="set-square"></sl-icon>
|
||||||
|
<br />
|
||||||
|
<sl-icon library="jam" name="calendar-f"></sl-icon>
|
||||||
|
<sl-icon library="jam" name="camera-f"></sl-icon>
|
||||||
|
<sl-icon library="jam" name="filter-f"></sl-icon>
|
||||||
|
<sl-icon library="jam" name="leaf-f"></sl-icon>
|
||||||
|
<sl-icon library="jam" name="picture-f"></sl-icon>
|
||||||
|
<sl-icon library="jam" name="set-square-f"></sl-icon>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Material Icons
|
||||||
|
|
||||||
|
This will register the [Material Icons](https://material.io/resources/icons/?style=baseline) library using the jsDelivr CDN. This library has three variations: outline (default), round (`*_round`), and sharp (`*_sharp`). A mutator function is required to set the SVG's `fill` to `currentColor`.
|
||||||
|
|
||||||
|
Icons in this library are licensed under the [Apache 2.0 License](https://github.com/google/material-design-icons/blob/master/LICENSE).
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<script type="module">
|
||||||
|
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||||
|
|
||||||
|
registerIconLibrary('material', {
|
||||||
|
resolver: name => {
|
||||||
|
const match = name.match(/^(.*?)(_(round|sharp))?$/);
|
||||||
|
return `https://cdn.jsdelivr.net/npm/@material-icons/svg@1.0.5/svg/${match[1]}/${match[3] || 'outline'}.svg`;
|
||||||
|
},
|
||||||
|
mutator: svg => svg.setAttribute('fill', 'currentColor')
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="font-size: 24px;">
|
||||||
|
<sl-icon library="material" name="notifications"></sl-icon>
|
||||||
|
<sl-icon library="material" name="email"></sl-icon>
|
||||||
|
<sl-icon library="material" name="delete"></sl-icon>
|
||||||
|
<sl-icon library="material" name="volume_up"></sl-icon>
|
||||||
|
<sl-icon library="material" name="settings"></sl-icon>
|
||||||
|
<sl-icon library="material" name="shopping_basket"></sl-icon>
|
||||||
|
<br />
|
||||||
|
<sl-icon library="material" name="notifications_round"></sl-icon>
|
||||||
|
<sl-icon library="material" name="email_round"></sl-icon>
|
||||||
|
<sl-icon library="material" name="delete_round"></sl-icon>
|
||||||
|
<sl-icon library="material" name="volume_up_round"></sl-icon>
|
||||||
|
<sl-icon library="material" name="settings_round"></sl-icon>
|
||||||
|
<sl-icon library="material" name="shopping_basket_round"></sl-icon>
|
||||||
|
<br />
|
||||||
|
<sl-icon library="material" name="notifications_sharp"></sl-icon>
|
||||||
|
<sl-icon library="material" name="email_sharp"></sl-icon>
|
||||||
|
<sl-icon library="material" name="delete_sharp"></sl-icon>
|
||||||
|
<sl-icon library="material" name="volume_up_sharp"></sl-icon>
|
||||||
|
<sl-icon library="material" name="settings_sharp"></sl-icon>
|
||||||
|
<sl-icon library="material" name="shopping_basket_sharp"></sl-icon>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remix Icon
|
||||||
|
|
||||||
|
This will register the [Remix Icon](https://remixicon.com/) library using the jsDelivr CDN. This library groups icons by categories, so the name must include the category and icon separated by a slash, as well as the `-line` or `-fill` suffix as needed. A mutator function is required to set the SVG's `fill` to `currentColor`.
|
||||||
|
|
||||||
|
Icons in this library are licensed under the [Apache 2.0 License](https://github.com/Remix-Design/RemixIcon/blob/master/License).
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<script type="module">
|
||||||
|
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||||
|
|
||||||
|
registerIconLibrary('remixicon', {
|
||||||
|
resolver: name => {
|
||||||
|
const match = name.match(/^(.*?)\/(.*?)?$/);
|
||||||
|
match[1] = match[1].charAt(0).toUpperCase() + match[1].slice(1);
|
||||||
|
return `https://cdn.jsdelivr.net/npm/remixicon@2.5.0/icons/${match[1]}/${match[2]}.svg`;
|
||||||
|
},
|
||||||
|
mutator: svg => svg.setAttribute('fill', 'currentColor')
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="font-size: 24px;">
|
||||||
|
<sl-icon library="remixicon" name="business/cloud-line"></sl-icon>
|
||||||
|
<sl-icon library="remixicon" name="design/brush-line"></sl-icon>
|
||||||
|
<sl-icon library="remixicon" name="business/pie-chart-line"></sl-icon>
|
||||||
|
<sl-icon library="remixicon" name="development/bug-line"></sl-icon>
|
||||||
|
<sl-icon library="remixicon" name="media/image-line"></sl-icon>
|
||||||
|
<sl-icon library="remixicon" name="system/alert-line"></sl-icon>
|
||||||
|
<br />
|
||||||
|
<sl-icon library="remixicon" name="business/cloud-fill"></sl-icon>
|
||||||
|
<sl-icon library="remixicon" name="design/brush-fill"></sl-icon>
|
||||||
|
<sl-icon library="remixicon" name="business/pie-chart-fill"></sl-icon>
|
||||||
|
<sl-icon library="remixicon" name="development/bug-fill"></sl-icon>
|
||||||
|
<sl-icon library="remixicon" name="media/image-fill"></sl-icon>
|
||||||
|
<sl-icon library="remixicon" name="system/alert-fill"></sl-icon>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tabler Icons
|
||||||
|
|
||||||
|
This will register the [Tabler Icons](https://tabler-icons.io/) library using the jsDelivr CDN. This library features over 1,950 open source icons.
|
||||||
|
|
||||||
|
Icons in this library are licensed under the [MIT License](https://github.com/tabler/tabler-icons/blob/master/LICENSE).
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<script type="module">
|
||||||
|
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||||
|
|
||||||
|
registerIconLibrary('tabler', {
|
||||||
|
resolver: name => `https://cdn.jsdelivr.net/npm/@tabler/icons@1.68.0/icons/${name}.svg`
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="font-size: 24px;">
|
||||||
|
<sl-icon library="tabler" name="alert-triangle"></sl-icon>
|
||||||
|
<sl-icon library="tabler" name="arrow-back"></sl-icon>
|
||||||
|
<sl-icon library="tabler" name="at"></sl-icon>
|
||||||
|
<sl-icon library="tabler" name="ball-baseball"></sl-icon>
|
||||||
|
<sl-icon library="tabler" name="cake"></sl-icon>
|
||||||
|
<sl-icon library="tabler" name="files"></sl-icon>
|
||||||
|
<br />
|
||||||
|
<sl-icon library="tabler" name="keyboard"></sl-icon>
|
||||||
|
<sl-icon library="tabler" name="moon"></sl-icon>
|
||||||
|
<sl-icon library="tabler" name="pig"></sl-icon>
|
||||||
|
<sl-icon library="tabler" name="printer"></sl-icon>
|
||||||
|
<sl-icon library="tabler" name="ship"></sl-icon>
|
||||||
|
<sl-icon library="tabler" name="toilet-paper"></sl-icon>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Unicons
|
||||||
|
|
||||||
|
This will register the [Unicons](https://iconscout.com/unicons) library using the jsDelivr CDN. This library has two variations: line (default) and solid (`*-s`). A mutator function is required to set the SVG's `fill` to `currentColor`.
|
||||||
|
|
||||||
|
Icons in this library are licensed under the [Apache 2.0 License](https://github.com/Iconscout/unicons/blob/master/LICENSE). Some of the icons that appear on the Unicons website, particularly many of the solid variations, require a license and are therefore not available in the CDN.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<script type="module">
|
||||||
|
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||||
|
|
||||||
|
registerIconLibrary('unicons', {
|
||||||
|
resolver: name => {
|
||||||
|
const match = name.match(/^(.*?)(-s)?$/);
|
||||||
|
return `https://cdn.jsdelivr.net/npm/@iconscout/unicons@3.0.3/svg/${match[2] === '-s' ? 'solid' : 'line'}/${
|
||||||
|
match[1]
|
||||||
|
}.svg`;
|
||||||
|
},
|
||||||
|
mutator: svg => svg.setAttribute('fill', 'currentColor')
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="font-size: 24px;">
|
||||||
|
<sl-icon library="unicons" name="clock"></sl-icon>
|
||||||
|
<sl-icon library="unicons" name="graph-bar"></sl-icon>
|
||||||
|
<sl-icon library="unicons" name="padlock"></sl-icon>
|
||||||
|
<sl-icon library="unicons" name="polygon"></sl-icon>
|
||||||
|
<sl-icon library="unicons" name="rocket"></sl-icon>
|
||||||
|
<sl-icon library="unicons" name="star"></sl-icon>
|
||||||
|
<br />
|
||||||
|
<sl-icon library="unicons" name="clock-s"></sl-icon>
|
||||||
|
<sl-icon library="unicons" name="graph-bar-s"></sl-icon>
|
||||||
|
<sl-icon library="unicons" name="padlock-s"></sl-icon>
|
||||||
|
<sl-icon library="unicons" name="polygon-s"></sl-icon>
|
||||||
|
<sl-icon library="unicons" name="rocket-s"></sl-icon>
|
||||||
|
<sl-icon library="unicons" name="star-s"></sl-icon>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customizing the Default Library
|
||||||
|
|
||||||
|
The default icon library contains over 1,300 icons courtesy of the [Bootstrap Icons](https://icons.getbootstrap.com/) project. These are the icons that display when you use `<sl-icon>` without the `library` attribute. If you prefer to have these icons resolve elsewhere or to a different icon library, register an icon library using the `default` name and a custom resolver.
|
||||||
|
|
||||||
|
This example will load the same set of icons from the jsDelivr CDN instead of your local assets folder.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script type="module">
|
||||||
|
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||||
|
|
||||||
|
registerIconLibrary('default', {
|
||||||
|
resolver: name => `https://cdn.jsdelivr.net/npm/bootstrap-icons@1.0.0/icons/${name}.svg`
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Customize the default library to use SVG sprites
|
||||||
|
|
||||||
|
To improve performance you can use a SVG sprites to avoid multiple trips for each SVG. The browser will load the sprite sheet once and then you reference the particular SVG within the sprite sheet using hash selector.
|
||||||
|
|
||||||
|
As always, make sure to benchmark these changes. When using HTTP/2, it may in fact be more bandwidth-friendly to use multiple small requests instead of 1 large sprite sheet.
|
||||||
|
|
||||||
|
:::danger
|
||||||
|
When using sprite sheets, the `sl-load` and `sl-error` events will not fire.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::danger
|
||||||
|
For security reasons, browsers may apply the same-origin policy on `<use>` elements located in the `<sl-icon>` shadow DOM and may refuse to load a cross-origin URL. There is currently no defined way to set a cross-origin policy for `<use>` elements. For this reason, sprite sheets should only be used if you're self-hosting them.
|
||||||
|
:::
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<script type="module">
|
||||||
|
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||||
|
|
||||||
|
registerIconLibrary('sprite', {
|
||||||
|
resolver: name => `/assets/images/sprite.svg#${name}`,
|
||||||
|
mutator: svg => svg.setAttribute('fill', 'currentColor'),
|
||||||
|
spriteSheet: true
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="font-size: 24px;">
|
||||||
|
<sl-icon library="sprite" name="clock"></sl-icon>
|
||||||
|
<sl-icon library="sprite" name="speedometer"></sl-icon>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customizing the System Library
|
||||||
|
|
||||||
|
The system library contains only the icons used internally by Shoelace components. Unlike the default icon library, the system library does not rely on physical assets. Instead, its icons are hard-coded as data URIs into the resolver to ensure their availability.
|
||||||
|
|
||||||
|
If you want to change the icons Shoelace uses internally, you can register an icon library using the `system` name and a custom resolver. If you choose to do this, it's your responsibility to provide all of the icons that are required by components. You can reference `src/components/library.system.ts` for a complete list of system icons used by Shoelace.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script type="module">
|
||||||
|
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||||
|
|
||||||
|
registerIconLibrary('system', {
|
||||||
|
resolver: name => `/path/to/custom/icons/${name}.svg`
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- Supporting scripts and styles for the search utility -->
|
||||||
|
<script>
|
||||||
|
function wrapWithTooltip(item) {
|
||||||
|
const tooltip = document.createElement('sl-tooltip');
|
||||||
|
tooltip.content = item.getAttribute('data-name');
|
||||||
|
|
||||||
|
// Close open tooltips
|
||||||
|
document.querySelectorAll('.icon-list sl-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('sl-input');
|
||||||
|
const select = container.querySelector('sl-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('sl-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('sl-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('sl-icon:type') || 'outline';
|
||||||
|
select.value = iconType;
|
||||||
|
list.setAttribute('data-type', select.value);
|
||||||
|
select.addEventListener('sl-change', () => {
|
||||||
|
list.setAttribute('data-type', select.value);
|
||||||
|
sessionStorage.setItem('sl-icon:type', select.value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.icon-search {
|
||||||
|
border: solid 1px var(--sl-panel-border-color);
|
||||||
|
border-radius: var(--sl-border-radius-medium);
|
||||||
|
padding: var(--sl-spacing-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-search [hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-search-controls {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-search-controls sl-input {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-search-controls sl-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(--sl-border-radius-medium);
|
||||||
|
font-size: 24px;
|
||||||
|
width: 2em;
|
||||||
|
height: 2em;
|
||||||
|
margin: 0 auto;
|
||||||
|
cursor: copy;
|
||||||
|
transition: var(--sl-transition-medium) all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-list-item:hover {
|
||||||
|
background-color: var(--sl-color-primary-50);
|
||||||
|
color: var(--sl-color-primary-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 sl-select {
|
||||||
|
width: auto;
|
||||||
|
margin: 1rem 0 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 500px) {
|
||||||
|
.icon-list {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
---
|
---
|
||||||
title: Image Comparer
|
meta:
|
||||||
description: Compare visual differences between similar photos with a sliding panel.
|
title: Image Comparer
|
||||||
layout: ../../../layouts/ComponentLayout.astro
|
description: Compare visual differences between similar photos with a sliding panel.
|
||||||
|
layout: component
|
||||||
---
|
---
|
||||||
|
|
||||||
For best results, use images that share the same dimensions. The slider can be controlled by dragging or pressing the left and right arrow keys. (Tip: press shift + arrows to move the slider in larger intervals, or home + end to jump to the beginning or end.)
|
For best results, use images that share the same dimensions. The slider can be controlled by dragging or pressing the left and right arrow keys. (Tip: press shift + arrows to move the slider in larger intervals, or home + end to jump to the beginning or end.)
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-image-comparer>
|
<sl-image-comparer>
|
||||||
<img
|
<img
|
||||||
slot="before"
|
slot="before"
|
||||||
src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80&sat=-100&bri=-5"
|
src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80&sat=-100&bri=-5"
|
||||||
@@ -18,14 +19,14 @@ For best results, use images that share the same dimensions. The slider can be c
|
|||||||
src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80"
|
src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80"
|
||||||
alt="Color version of kittens in a basket looking around."
|
alt="Color version of kittens in a basket looking around."
|
||||||
/>
|
/>
|
||||||
</wa-image-comparer>
|
</sl-image-comparer>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaImageComparer from '@shoelace-style/shoelace/dist/react/image-comparer';
|
import SlImageComparer from '@shoelace-style/shoelace/dist/react/image-comparer';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<WaImageComparer>
|
<SlImageComparer>
|
||||||
<img
|
<img
|
||||||
slot="before"
|
slot="before"
|
||||||
src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80&sat=-100&bri=-5"
|
src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80&sat=-100&bri=-5"
|
||||||
@@ -36,7 +37,7 @@ const App = () => (
|
|||||||
src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80"
|
src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80"
|
||||||
alt="Color version of kittens in a basket looking around."
|
alt="Color version of kittens in a basket looking around."
|
||||||
/>
|
/>
|
||||||
</WaImageComparer>
|
</SlImageComparer>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ const App = () => (
|
|||||||
Use the `position` attribute to set the initial position of the slider. This is a percentage from `0` to `100`.
|
Use the `position` attribute to set the initial position of the slider. This is a percentage from `0` to `100`.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-image-comparer position="25">
|
<sl-image-comparer position="25">
|
||||||
<img
|
<img
|
||||||
slot="before"
|
slot="before"
|
||||||
src="https://images.unsplash.com/photo-1520903074185-8eca362b3dce?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1200&q=80"
|
src="https://images.unsplash.com/photo-1520903074185-8eca362b3dce?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1200&q=80"
|
||||||
@@ -58,14 +59,14 @@ Use the `position` attribute to set the initial position of the slider. This is
|
|||||||
src="https://images.unsplash.com/photo-1520640023173-50a135e35804?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80"
|
src="https://images.unsplash.com/photo-1520640023173-50a135e35804?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80"
|
||||||
alt="A person sitting on a yellow curb tying shoelaces on a boot."
|
alt="A person sitting on a yellow curb tying shoelaces on a boot."
|
||||||
/>
|
/>
|
||||||
</wa-image-comparer>
|
</sl-image-comparer>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaImageComparer from '@shoelace-style/shoelace/dist/react/image-comparer';
|
import SlImageComparer from '@shoelace-style/shoelace/dist/react/image-comparer';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<WaImageComparer position={25}>
|
<SlImageComparer position={25}>
|
||||||
<img
|
<img
|
||||||
slot="before"
|
slot="before"
|
||||||
src="https://images.unsplash.com/photo-1520903074185-8eca362b3dce?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1200&q=80"
|
src="https://images.unsplash.com/photo-1520903074185-8eca362b3dce?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1200&q=80"
|
||||||
@@ -76,6 +77,6 @@ const App = () => (
|
|||||||
src="https://images.unsplash.com/photo-1520640023173-50a135e35804?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80"
|
src="https://images.unsplash.com/photo-1520640023173-50a135e35804?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80"
|
||||||
alt="A person sitting on a yellow curb tying shoelaces on a boot."
|
alt="A person sitting on a yellow curb tying shoelaces on a boot."
|
||||||
/>
|
/>
|
||||||
</WaImageComparer>
|
</SlImageComparer>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
@@ -1,44 +1,45 @@
|
|||||||
---
|
---
|
||||||
title: Include
|
meta:
|
||||||
description: Includes give you the power to embed external HTML files into the page.
|
title: Include
|
||||||
layout: ../../../layouts/ComponentLayout.astro
|
description: Includes give you the power to embed external HTML files into the page.
|
||||||
|
layout: component
|
||||||
---
|
---
|
||||||
|
|
||||||
Included files are asynchronously requested using `window.fetch()`. Requests are cached, so the same file can be included multiple times, but only one request will be made.
|
Included files are asynchronously requested using `window.fetch()`. Requests are cached, so the same file can be included multiple times, but only one request will be made.
|
||||||
|
|
||||||
The included content will be inserted into the `<wa-include>` element's default slot so it can be easily accessed and styled through the light DOM.
|
The included content will be inserted into the `<sl-include>` element's default slot so it can be easily accessed and styled through the light DOM.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<wa-include src="https://shoelace.style/assets/examples/include.html"></wa-include>
|
<sl-include src="https://shoelace.style/assets/examples/include.html"></sl-include>
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import WaInclude from '@shoelace-style/shoelace/dist/react/include';
|
import SlInclude from '@shoelace-style/shoelace/dist/react/include';
|
||||||
|
|
||||||
const App = () => <WaInclude src="https://shoelace.style/assets/examples/include.html" />;
|
const App = () => <SlInclude src="https://shoelace.style/assets/examples/include.html" />;
|
||||||
```
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Listening for Events
|
### Listening for Events
|
||||||
|
|
||||||
When an include file loads successfully, the `wa-load` event will be emitted. You can listen for this event to add custom loading logic to your includes.
|
When an include file loads successfully, the `sl-load` event will be emitted. You can listen for this event to add custom loading logic to your includes.
|
||||||
|
|
||||||
If the request fails, the `wa-error` event will be emitted. In this case, `event.detail.status` will contain the resulting HTTP status code of the request, e.g. 404 (not found).
|
If the request fails, the `sl-error` event will be emitted. In this case, `event.detail.status` will contain the resulting HTTP status code of the request, e.g. 404 (not found).
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<wa-include src="https://shoelace.style/assets/examples/include.html"></wa-include>
|
<sl-include src="https://shoelace.style/assets/examples/include.html"></sl-include>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const include = document.querySelector('wa-include');
|
const include = document.querySelector('sl-include');
|
||||||
|
|
||||||
include.addEventListener('wa-load', event => {
|
include.addEventListener('sl-load', event => {
|
||||||
if (event.eventPhase === Event.AT_TARGET) {
|
if (event.eventPhase === Event.AT_TARGET) {
|
||||||
console.log('Success');
|
console.log('Success');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
include.addEventListener('wa-error', event => {
|
include.addEventListener('sl-error', event => {
|
||||||
if (event.eventPhase === Event.AT_TARGET) {
|
if (event.eventPhase === Event.AT_TARGET) {
|
||||||
console.log('Error', event.detail.status);
|
console.log('Error', event.detail.status);
|
||||||
}
|
}
|
||||||
281
docs/pages/components/input.md
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Input
|
||||||
|
description: Inputs collect data from the user.
|
||||||
|
layout: component
|
||||||
|
---
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-input></sl-input>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlInput from '@shoelace-style/shoelace/dist/react/input';
|
||||||
|
|
||||||
|
const App = () => <SlInput />;
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Labels
|
||||||
|
|
||||||
|
Use the `label` attribute to give the input an accessible label. For labels that contain HTML, use the `label` slot instead.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-input label="What is your name?"></sl-input>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
import SlInput from '@shoelace-style/shoelace/dist/react/input';
|
||||||
|
|
||||||
|
const App = () => <SlInput label="What is your name?" />;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Help Text
|
||||||
|
|
||||||
|
Add descriptive help text to an input with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-input label="Nickname" help-text="What would you like people to call you?"></sl-input>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
import SlInput from '@shoelace-style/shoelace/dist/react/input';
|
||||||
|
|
||||||
|
const App = () => <SlInput label="Nickname" help-text="What would you like people to call you?" />;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Placeholders
|
||||||
|
|
||||||
|
Use the `placeholder` attribute to add a placeholder.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-input placeholder="Type something"></sl-input>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlInput from '@shoelace-style/shoelace/dist/react/input';
|
||||||
|
|
||||||
|
const App = () => <SlInput placeholder="Type something" />;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clearable
|
||||||
|
|
||||||
|
Add the `clearable` attribute to add a clear button when the input has content.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-input placeholder="Clearable" clearable></sl-input>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlInput from '@shoelace-style/shoelace/dist/react/input';
|
||||||
|
|
||||||
|
const App = () => <SlInput placeholder="Clearable" clearable />;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Toggle Password
|
||||||
|
|
||||||
|
Add the `password-toggle` attribute to add a toggle button that will show the password when activated.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-input type="password" placeholder="Password Toggle" password-toggle></sl-input>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlInput from '@shoelace-style/shoelace/dist/react/input';
|
||||||
|
|
||||||
|
const App = () => <SlInput type="password" placeholder="Password Toggle" size="medium" password-toggle />;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filled Inputs
|
||||||
|
|
||||||
|
Add the `filled` attribute to draw a filled input.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-input placeholder="Type something" filled></sl-input>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlInput from '@shoelace-style/shoelace/dist/react/input';
|
||||||
|
|
||||||
|
const App = () => <SlInput placeholder="Type something" filled />;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disabled
|
||||||
|
|
||||||
|
Use the `disabled` attribute to disable an input.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-input placeholder="Disabled" disabled></sl-input>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlInput from '@shoelace-style/shoelace/dist/react/input';
|
||||||
|
|
||||||
|
const App = () => <SlInput placeholder="Disabled" disabled />;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sizes
|
||||||
|
|
||||||
|
Use the `size` attribute to change an input's size.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-input placeholder="Small" size="small"></sl-input>
|
||||||
|
<br />
|
||||||
|
<sl-input placeholder="Medium" size="medium"></sl-input>
|
||||||
|
<br />
|
||||||
|
<sl-input placeholder="Large" size="large"></sl-input>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlInput from '@shoelace-style/shoelace/dist/react/input';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlInput placeholder="Small" size="small" />
|
||||||
|
<br />
|
||||||
|
<SlInput placeholder="Medium" size="medium" />
|
||||||
|
<br />
|
||||||
|
<SlInput placeholder="Large" size="large" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pill
|
||||||
|
|
||||||
|
Use the `pill` attribute to give inputs rounded edges.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-input placeholder="Small" size="small" pill></sl-input>
|
||||||
|
<br />
|
||||||
|
<sl-input placeholder="Medium" size="medium" pill></sl-input>
|
||||||
|
<br />
|
||||||
|
<sl-input placeholder="Large" size="large" pill></sl-input>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlInput from '@shoelace-style/shoelace/dist/react/input';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlInput placeholder="Small" size="small" pill />
|
||||||
|
<br />
|
||||||
|
<SlInput placeholder="Medium" size="medium" pill />
|
||||||
|
<br />
|
||||||
|
<SlInput placeholder="Large" size="large" pill />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Input Types
|
||||||
|
|
||||||
|
The `type` attribute controls the type of input the browser renders.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-input type="email" placeholder="Email"></sl-input>
|
||||||
|
<br />
|
||||||
|
<sl-input type="number" placeholder="Number"></sl-input>
|
||||||
|
<br />
|
||||||
|
<sl-input type="date" placeholder="Date"></sl-input>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlInput from '@shoelace-style/shoelace/dist/react/input';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlInput type="email" placeholder="Email" />
|
||||||
|
<br />
|
||||||
|
<SlInput type="number" placeholder="Number" />
|
||||||
|
<br />
|
||||||
|
<SlInput type="date" placeholder="Date" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prefix & Suffix Icons
|
||||||
|
|
||||||
|
Use the `prefix` and `suffix` slots to add icons.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-input placeholder="Small" size="small">
|
||||||
|
<sl-icon name="house" slot="prefix"></sl-icon>
|
||||||
|
<sl-icon name="chat" slot="suffix"></sl-icon>
|
||||||
|
</sl-input>
|
||||||
|
<br />
|
||||||
|
<sl-input placeholder="Medium" size="medium">
|
||||||
|
<sl-icon name="house" slot="prefix"></sl-icon>
|
||||||
|
<sl-icon name="chat" slot="suffix"></sl-icon>
|
||||||
|
</sl-input>
|
||||||
|
<br />
|
||||||
|
<sl-input placeholder="Large" size="large">
|
||||||
|
<sl-icon name="house" slot="prefix"></sl-icon>
|
||||||
|
<sl-icon name="chat" slot="suffix"></sl-icon>
|
||||||
|
</sl-input>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
import SlInput from '@shoelace-style/shoelace/dist/react/input';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<>
|
||||||
|
<SlInput placeholder="Small" size="small">
|
||||||
|
<SlIcon name="house" slot="prefix"></SlIcon>
|
||||||
|
<SlIcon name="chat" slot="suffix"></SlIcon>
|
||||||
|
</SlInput>
|
||||||
|
<br />
|
||||||
|
<SlInput placeholder="Medium" size="medium">
|
||||||
|
<SlIcon name="house" slot="prefix"></SlIcon>
|
||||||
|
<SlIcon name="chat" slot="suffix"></SlIcon>
|
||||||
|
</SlInput>
|
||||||
|
<br />
|
||||||
|
<SlInput placeholder="Large" size="large">
|
||||||
|
<SlIcon name="house" slot="prefix"></SlIcon>
|
||||||
|
<SlIcon name="chat" slot="suffix"></SlIcon>
|
||||||
|
</SlInput>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customizing Label Position
|
||||||
|
|
||||||
|
Use [CSS parts](#css-parts) to customize the way form controls are drawn. This example uses CSS grid to position the label to the left of the control, but the possible orientations are nearly endless. The same technique works for inputs, textareas, radio groups, and similar form controls.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-input class="label-on-left" label="Name" help-text="Enter your name"></sl-input>
|
||||||
|
<sl-input class="label-on-left" label="Email" type="email" help-text="Enter your email"></sl-input>
|
||||||
|
<sl-textarea class="label-on-left" label="Bio" help-text="Tell us something about yourself"></sl-textarea>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.label-on-left {
|
||||||
|
--label-width: 3.75rem;
|
||||||
|
--gap-width: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-on-left + .label-on-left {
|
||||||
|
margin-top: var(--sl-spacing-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-on-left::part(form-control) {
|
||||||
|
display: grid;
|
||||||
|
grid: auto / var(--label-width) 1fr;
|
||||||
|
gap: var(--sl-spacing-3x-small) var(--gap-width);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-on-left::part(form-control-label) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-on-left::part(form-control-help-text) {
|
||||||
|
grid-column-start: 2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
245
docs/pages/components/menu-item.md
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Menu Item
|
||||||
|
description: Menu items provide options for the user to pick from in a menu.
|
||||||
|
layout: component
|
||||||
|
---
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-menu style="max-width: 200px;">
|
||||||
|
<sl-menu-item>Option 1</sl-menu-item>
|
||||||
|
<sl-menu-item>Option 2</sl-menu-item>
|
||||||
|
<sl-menu-item>Option 3</sl-menu-item>
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
<sl-menu-item type="checkbox" checked>Checkbox</sl-menu-item>
|
||||||
|
<sl-menu-item disabled>Disabled</sl-menu-item>
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
<sl-menu-item>
|
||||||
|
Prefix Icon
|
||||||
|
<sl-icon slot="prefix" name="gift"></sl-icon>
|
||||||
|
</sl-menu-item>
|
||||||
|
<sl-menu-item>
|
||||||
|
Suffix Icon
|
||||||
|
<sl-icon slot="suffix" name="heart"></sl-icon>
|
||||||
|
</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||||
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlMenu style={{ maxWidth: '200px' }}>
|
||||||
|
<SlMenuItem>Option 1</SlMenuItem>
|
||||||
|
<SlMenuItem>Option 2</SlMenuItem>
|
||||||
|
<SlMenuItem>Option 3</SlMenuItem>
|
||||||
|
<SlDivider />
|
||||||
|
<SlMenuItem type="checkbox" checked>
|
||||||
|
Checkbox
|
||||||
|
</SlMenuItem>
|
||||||
|
<SlMenuItem disabled>Disabled</SlMenuItem>
|
||||||
|
<SlDivider />
|
||||||
|
<SlMenuItem>
|
||||||
|
Prefix Icon
|
||||||
|
<SlIcon slot="prefix" name="gift" />
|
||||||
|
</SlMenuItem>
|
||||||
|
<SlMenuItem>
|
||||||
|
Suffix Icon
|
||||||
|
<SlIcon slot="suffix" name="heart" />
|
||||||
|
</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Disabled
|
||||||
|
|
||||||
|
Add the `disabled` attribute to disable the menu item so it cannot be selected.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-menu style="max-width: 200px;">
|
||||||
|
<sl-menu-item>Option 1</sl-menu-item>
|
||||||
|
<sl-menu-item disabled>Option 2</sl-menu-item>
|
||||||
|
<sl-menu-item>Option 3</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlMenu style={{ maxWidth: '200px' }}>
|
||||||
|
<SlMenuItem>Option 1</SlMenuItem>
|
||||||
|
<SlMenuItem disabled>Option 2</SlMenuItem>
|
||||||
|
<SlMenuItem>Option 3</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
### Prefix & Suffix
|
||||||
|
|
||||||
|
Add content to the start and end of menu items using the `prefix` and `suffix` slots.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-menu style="max-width: 200px;">
|
||||||
|
<sl-menu-item>
|
||||||
|
<sl-icon slot="prefix" name="house"></sl-icon>
|
||||||
|
Home
|
||||||
|
</sl-menu-item>
|
||||||
|
|
||||||
|
<sl-menu-item>
|
||||||
|
<sl-icon slot="prefix" name="envelope"></sl-icon>
|
||||||
|
Messages
|
||||||
|
<sl-badge slot="suffix" variant="primary" pill>12</sl-badge>
|
||||||
|
</sl-menu-item>
|
||||||
|
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
|
||||||
|
<sl-menu-item>
|
||||||
|
<sl-icon slot="prefix" name="gear"></sl-icon>
|
||||||
|
Settings
|
||||||
|
</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
|
||||||
|
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||||
|
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlMenu style={{ maxWidth: '200px' }}>
|
||||||
|
<SlMenuItem>
|
||||||
|
<SlIcon slot="prefix" name="house" />
|
||||||
|
Home
|
||||||
|
</SlMenuItem>
|
||||||
|
|
||||||
|
<SlMenuItem>
|
||||||
|
<SlIcon slot="prefix" name="envelope" />
|
||||||
|
Messages
|
||||||
|
<SlBadge slot="suffix" variant="primary" pill>
|
||||||
|
12
|
||||||
|
</SlBadge>
|
||||||
|
</SlMenuItem>
|
||||||
|
|
||||||
|
<SlDivider />
|
||||||
|
|
||||||
|
<SlMenuItem>
|
||||||
|
<SlIcon slot="prefix" name="gear" />
|
||||||
|
Settings
|
||||||
|
</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
### Checkbox Menu Items
|
||||||
|
|
||||||
|
Set the `type` attribute to `checkbox` to create a menu item that will toggle on and off when selected. You can use the `checked` attribute to set the initial state.
|
||||||
|
|
||||||
|
Checkbox menu items are visually indistinguishable from regular menu items. Their ability to be toggled is primarily inferred from context, much like you'd find in the menu of a native app.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-menu style="max-width: 200px;">
|
||||||
|
<sl-menu-item type="checkbox">Autosave</sl-menu-item>
|
||||||
|
<sl-menu-item type="checkbox" checked>Check Spelling</sl-menu-item>
|
||||||
|
<sl-menu-item type="checkbox">Word Wrap</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlMenu style={{ maxWidth: '200px' }}>
|
||||||
|
<SlMenuItem type="checkbox">Autosave</SlMenuItem>
|
||||||
|
<SlMenuItem type="checkbox" checked>
|
||||||
|
Check Spelling
|
||||||
|
</SlMenuItem>
|
||||||
|
<SlMenuItem type="checkbox">Word Wrap</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
### Value & Selection
|
||||||
|
|
||||||
|
The `value` attribute can be used to assign a hidden value, such as a unique identifier, to a menu item. When an item is selected, the `sl-select` event will be emitted and a reference to the item will be available at `event.detail.item`. You can use this reference to access the selected item's value, its checked state, and more.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-menu class="menu-value" style="max-width: 200px;">
|
||||||
|
<sl-menu-item value="opt-1">Option 1</sl-menu-item>
|
||||||
|
<sl-menu-item value="opt-2">Option 2</sl-menu-item>
|
||||||
|
<sl-menu-item value="opt-3">Option 3</sl-menu-item>
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
<sl-menu-item type="checkbox" value="opt-4">Checkbox 4</sl-menu-item>
|
||||||
|
<sl-menu-item type="checkbox" value="opt-5">Checkbox 5</sl-menu-item>
|
||||||
|
<sl-menu-item type="checkbox" value="opt-6">Checkbox 6</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const menu = document.querySelector('.menu-value');
|
||||||
|
|
||||||
|
menu.addEventListener('sl-select', event => {
|
||||||
|
const item = event.detail.item;
|
||||||
|
|
||||||
|
// Log value
|
||||||
|
if (item.type === 'checkbox') {
|
||||||
|
console.log(`Selected value: ${item.value} (${item.checked ? 'checked' : 'unchecked'})`);
|
||||||
|
} else {
|
||||||
|
console.log(`Selected value: ${item.value}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
function handleSelect(event) {
|
||||||
|
const item = event.detail.item;
|
||||||
|
|
||||||
|
// Toggle checked state
|
||||||
|
item.checked = !item.checked;
|
||||||
|
|
||||||
|
// Log value
|
||||||
|
console.log(`Selected value: ${item.value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SlMenu style={{ maxWidth: '200px' }} onSlSelect={handleSelect}>
|
||||||
|
<SlMenuItem value="opt-1">Option 1</SlMenuItem>
|
||||||
|
<SlMenuItem value="opt-2">Option 2</SlMenuItem>
|
||||||
|
<SlMenuItem value="opt-3">Option 3</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
45
docs/pages/components/menu-label.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Menu Label
|
||||||
|
description: Menu labels are used to describe a group of menu items.
|
||||||
|
layout: component
|
||||||
|
---
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-menu style="max-width: 200px;">
|
||||||
|
<sl-menu-label>Fruits</sl-menu-label>
|
||||||
|
<sl-menu-item value="apple">Apple</sl-menu-item>
|
||||||
|
<sl-menu-item value="banana">Banana</sl-menu-item>
|
||||||
|
<sl-menu-item value="orange">Orange</sl-menu-item>
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
<sl-menu-label>Vegetables</sl-menu-label>
|
||||||
|
<sl-menu-item value="broccoli">Broccoli</sl-menu-item>
|
||||||
|
<sl-menu-item value="carrot">Carrot</sl-menu-item>
|
||||||
|
<sl-menu-item value="zucchini">Zucchini</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuLabel from '@shoelace-style/shoelace/dist/react/menu-label';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlMenu style={{ maxWidth: '200px' }}>
|
||||||
|
<SlMenuLabel>Fruits</SlMenuLabel>
|
||||||
|
<SlMenuItem value="apple">Apple</SlMenuItem>
|
||||||
|
<SlMenuItem value="banana">Banana</SlMenuItem>
|
||||||
|
<SlMenuItem value="orange">Orange</SlMenuItem>
|
||||||
|
<SlDivider />
|
||||||
|
<SlMenuLabel>Vegetables</SlMenuLabel>
|
||||||
|
<SlMenuItem value="broccoli">Broccoli</SlMenuItem>
|
||||||
|
<SlMenuItem value="carrot">Carrot</SlMenuItem>
|
||||||
|
<SlMenuItem value="zucchini">Zucchini</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
46
docs/pages/components/menu.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Menu
|
||||||
|
description: Menus provide a list of options for the user to choose from.
|
||||||
|
layout: component
|
||||||
|
---
|
||||||
|
|
||||||
|
You can use [menu items](/components/menu-item), [menu labels](/components/menu-label), and [dividers](/components/divider) to compose a menu. Menus support keyboard interactions, including type-to-select an option.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-menu style="max-width: 200px;">
|
||||||
|
<sl-menu-item value="undo">Undo</sl-menu-item>
|
||||||
|
<sl-menu-item value="redo">Redo</sl-menu-item>
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
<sl-menu-item value="cut">Cut</sl-menu-item>
|
||||||
|
<sl-menu-item value="copy">Copy</sl-menu-item>
|
||||||
|
<sl-menu-item value="paste">Paste</sl-menu-item>
|
||||||
|
<sl-menu-item value="delete">Delete</sl-menu-item>
|
||||||
|
</sl-menu>
|
||||||
|
```
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||||
|
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||||
|
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlMenu style={{ maxWidth: '200px' }}>
|
||||||
|
<SlMenuItem value="undo">Undo</SlMenuItem>
|
||||||
|
<SlMenuItem value="redo">Redo</SlMenuItem>
|
||||||
|
<SlDivider />
|
||||||
|
<SlMenuItem value="cut">Cut</SlMenuItem>
|
||||||
|
<SlMenuItem value="copy">Copy</SlMenuItem>
|
||||||
|
<SlMenuItem value="paste">Paste</SlMenuItem>
|
||||||
|
<SlMenuItem value="delete">Delete</SlMenuItem>
|
||||||
|
</SlMenu>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Menus are intended for system menus (dropdown menus, select menus, context menus, etc.). They should not be mistaken for navigation menus which serve a different purpose and have a different semantic meaning. If you're building navigation, use `<nav>` and `<a>` elements instead.
|
||||||
|
:::
|
||||||
@@ -1,25 +1,26 @@
|
|||||||
---
|
---
|
||||||
title: Mutation Observer
|
meta:
|
||||||
description: The Mutation Observer component offers a thin, declarative interface to the MutationObserver API.
|
title: Mutation Observer
|
||||||
layout: ../../../layouts/ComponentLayout.astro
|
description: The Mutation Observer component offers a thin, declarative interface to the MutationObserver API.
|
||||||
|
layout: component
|
||||||
---
|
---
|
||||||
|
|
||||||
The mutation observer will report changes to the content it wraps through the `wa-mutation` event. When emitted, a collection of [MutationRecord](https://developer.mozilla.org/en-US/docs/Web/API/MutationRecord) objects will be attached to `event.detail` that contains information about how it changed.
|
The mutation observer will report changes to the content it wraps through the `sl-mutation` event. When emitted, a collection of [MutationRecord](https://developer.mozilla.org/en-US/docs/Web/API/MutationRecord) objects will be attached to `event.detail` that contains information about how it changed.
|
||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<div class="mutation-overview">
|
<div class="mutation-overview">
|
||||||
<wa-mutation-observer attr="variant">
|
<sl-mutation-observer attr="variant">
|
||||||
<wa-button variant="brand">Click to mutate</wa-button>
|
<sl-button variant="primary">Click to mutate</sl-button>
|
||||||
</wa-mutation-observer>
|
</sl-mutation-observer>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
👆 Click the button and watch the console
|
👆 Click the button and watch the console
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const container = document.querySelector('.mutation-overview');
|
const container = document.querySelector('.mutation-overview');
|
||||||
const mutationObserver = container.querySelector('wa-mutation-observer');
|
const mutationObserver = container.querySelector('sl-mutation-observer');
|
||||||
const button = container.querySelector('wa-button');
|
const button = container.querySelector('sl-button');
|
||||||
const variants = ['brand', 'success', 'neutral', 'warning', 'danger'];
|
const variants = ['primary', 'success', 'neutral', 'warning', 'danger'];
|
||||||
let clicks = 0;
|
let clicks = 0;
|
||||||
|
|
||||||
// Change the button's variant attribute
|
// Change the button's variant attribute
|
||||||
@@ -29,13 +30,13 @@ The mutation observer will report changes to the content it wraps through the `w
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Log mutations
|
// Log mutations
|
||||||
mutationObserver.addEventListener('wa-mutation', event => {
|
mutationObserver.addEventListener('sl-mutation', event => {
|
||||||
console.log(event.detail);
|
console.log(event.detail);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.mutation-overview wa-button {
|
.mutation-overview sl-button {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -44,20 +45,25 @@ The mutation observer will report changes to the content it wraps through the `w
|
|||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaMutationObserver from '@shoelace-style/shoelace/dist/react/mutation-observer';
|
import SlMutationObserver from '@shoelace-style/shoelace/dist/react/mutation-observer';
|
||||||
|
|
||||||
const css = `
|
const css = `
|
||||||
.mutation-overview wa-button {
|
.resize-observer-overview div {
|
||||||
margin-bottom: 1rem;
|
display: flex;
|
||||||
|
border: solid 2px var(--sl-input-border-color);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: 4rem 2rem;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const variants = ['brand', 'success', 'neutral', 'warning', 'danger'];
|
const variants = ['primary', 'success', 'neutral', 'warning', 'danger'];
|
||||||
let clicks = 0;
|
let clicks = 0;
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [variant, setVariant] = useState('brand');
|
const [variant, setVariant] = useState('primary');
|
||||||
|
|
||||||
function handleClick() {
|
function handleClick() {
|
||||||
clicks++;
|
clicks++;
|
||||||
@@ -66,14 +72,11 @@ const App = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WaMutationObserver attr="*" onWaMutation={event => console.log(event.detail)}>
|
<SlMutationObserver attr="*" onSlMutation={event => console.log(event.detail)}>
|
||||||
<WaButton variant={variant} onClick={handleClick}>
|
<SlButton variant={variant} onClick={handleClick}>
|
||||||
Click to mutate
|
Click to mutate
|
||||||
</WaButton>
|
</SlButton>
|
||||||
</WaMutationObserver>
|
</SlMutationObserver>
|
||||||
|
|
||||||
<br />
|
|
||||||
👆 Click the button and watch the console
|
|
||||||
|
|
||||||
<style>{css}</style>
|
<style>{css}</style>
|
||||||
</>
|
</>
|
||||||
@@ -93,31 +96,31 @@ Use the `child-list` attribute to watch for new child elements that are added or
|
|||||||
|
|
||||||
```html:preview
|
```html:preview
|
||||||
<div class="mutation-child-list">
|
<div class="mutation-child-list">
|
||||||
<wa-mutation-observer child-list>
|
<sl-mutation-observer child-list>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<wa-button variant="brand">Add button</wa-button>
|
<sl-button variant="primary">Add button</sl-button>
|
||||||
</div>
|
</div>
|
||||||
</wa-mutation-observer>
|
</sl-mutation-observer>
|
||||||
|
|
||||||
👆 Add and remove buttons and watch the console
|
👆 Add and remove buttons and watch the console
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const container = document.querySelector('.mutation-child-list');
|
const container = document.querySelector('.mutation-child-list');
|
||||||
const mutationObserver = container.querySelector('wa-mutation-observer');
|
const mutationObserver = container.querySelector('sl-mutation-observer');
|
||||||
const buttons = container.querySelector('.buttons');
|
const buttons = container.querySelector('.buttons');
|
||||||
const button = container.querySelector('wa-button[variant="brand"]');
|
const button = container.querySelector('sl-button[variant="primary"]');
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
// Add a button
|
// Add a button
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
const button = document.createElement('wa-button');
|
const button = document.createElement('sl-button');
|
||||||
button.textContent = ++i;
|
button.textContent = ++i;
|
||||||
buttons.append(button);
|
buttons.append(button);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove a button
|
// Remove a button
|
||||||
buttons.addEventListener('click', event => {
|
buttons.addEventListener('click', event => {
|
||||||
const target = event.target.closest('wa-button:not([variant="brand"])');
|
const target = event.target.closest('sl-button:not([variant="primary"])');
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
if (target) {
|
if (target) {
|
||||||
@@ -126,7 +129,7 @@ Use the `child-list` attribute to watch for new child elements that are added or
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Log mutations
|
// Log mutations
|
||||||
mutationObserver.addEventListener('wa-mutation', event => {
|
mutationObserver.addEventListener('sl-mutation', event => {
|
||||||
console.log(event.detail);
|
console.log(event.detail);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -144,8 +147,8 @@ Use the `child-list` attribute to watch for new child elements that are added or
|
|||||||
|
|
||||||
```jsx:react
|
```jsx:react
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import WaButton from '@shoelace-style/shoelace/dist/react/button';
|
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||||
import WaMutationObserver from '@shoelace-style/shoelace/dist/react/mutation-observer';
|
import SlMutationObserver from '@shoelace-style/shoelace/dist/react/mutation-observer';
|
||||||
|
|
||||||
const css = `
|
const css = `
|
||||||
.mutation-child-list .buttons {
|
.mutation-child-list .buttons {
|
||||||
@@ -172,18 +175,18 @@ const App = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mutation-child-list">
|
<div className="mutation-child-list">
|
||||||
<WaMutationObserver child-list onWaMutation={event => console.log(event.detail)}>
|
<SlMutationObserver child-list onSlMutation={event => console.log(event.detail)}>
|
||||||
<div className="buttons">
|
<div className="buttons">
|
||||||
<WaButton variant="brand" onClick={addButton}>
|
<SlButton variant="primary" onClick={addButton}>
|
||||||
Add button
|
Add button
|
||||||
</WaButton>
|
</SlButton>
|
||||||
{buttonIds.map(id => (
|
{buttonIds.map(id => (
|
||||||
<WaButton key={id} variant="default" onClick={() => removeButton(id)}>
|
<SlButton key={id} variant="default" onClick={() => removeButton(id)}>
|
||||||
{id}
|
{id}
|
||||||
</WaButton>
|
</SlButton>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</WaMutationObserver>
|
</SlMutationObserver>
|
||||||
</div>
|
</div>
|
||||||
👆 Add and remove buttons and watch the console
|
👆 Add and remove buttons and watch the console
|
||||||
<style>{css}</style>
|
<style>{css}</style>
|
||||||
82
docs/pages/components/option.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
meta:
|
||||||
|
title: Option
|
||||||
|
description: Options define the selectable items within various form controls such as select.
|
||||||
|
layout: component
|
||||||
|
---
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-select label="Select one">
|
||||||
|
<sl-option value="option-1">Option 1</sl-option>
|
||||||
|
<sl-option value="option-2">Option 2</sl-option>
|
||||||
|
<sl-option value="option-3">Option 3</sl-option>
|
||||||
|
</sl-select>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlOption from '@shoelace-style/shoelace/dist/react/option';
|
||||||
|
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlSelect>
|
||||||
|
<SlOption value="option-1">Option 1</SlOption>
|
||||||
|
<SlOption value="option-2">Option 2</SlOption>
|
||||||
|
<SlOption value="option-3">Option 3</SlOption>
|
||||||
|
</SlSelect>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Disabled
|
||||||
|
|
||||||
|
Use the `disabled` attribute to disable an option and prevent it from being selected.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-select label="Select one">
|
||||||
|
<sl-option value="option-1">Option 1</sl-option>
|
||||||
|
<sl-option value="option-2" disabled>Option 2</sl-option>
|
||||||
|
<sl-option value="option-3">Option 3</sl-option>
|
||||||
|
</sl-select>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx:react
|
||||||
|
import SlOption from '@shoelace-style/shoelace/dist/react/option';
|
||||||
|
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<SlSelect>
|
||||||
|
<SlOption value="option-1">Option 1</SlOption>
|
||||||
|
<SlOption value="option-2" disabled>
|
||||||
|
Option 2
|
||||||
|
</SlOption>
|
||||||
|
<SlOption value="option-3">Option 3</SlOption>
|
||||||
|
</SlSelect>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prefix & Suffix
|
||||||
|
|
||||||
|
Add icons to the start and end of menu items using the `prefix` and `suffix` slots.
|
||||||
|
|
||||||
|
```html:preview
|
||||||
|
<sl-select label="Select one">
|
||||||
|
<sl-option value="option-1">
|
||||||
|
<sl-icon slot="prefix" name="envelope"></sl-icon>
|
||||||
|
Email
|
||||||
|
<sl-icon slot="suffix" name="patch-check"></sl-icon>
|
||||||
|
</sl-option>
|
||||||
|
|
||||||
|
<sl-option value="option-2">
|
||||||
|
<sl-icon slot="prefix" name="telephone"></sl-icon>
|
||||||
|
Phone
|
||||||
|
<sl-icon slot="suffix" name="patch-check"></sl-icon>
|
||||||
|
</sl-option>
|
||||||
|
|
||||||
|
<sl-option value="option-3">
|
||||||
|
<sl-icon slot="prefix" name="chat-dots"></sl-icon>
|
||||||
|
Chat
|
||||||
|
<sl-icon slot="suffix" name="patch-check"></sl-icon>
|
||||||
|
</sl-option>
|
||||||
|
</sl-select>
|
||||||
|
```
|
||||||