mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-19 07:29:14 +00:00
Compare commits
379 Commits
v2.0.0-bet
...
v2.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1ec60af62 | ||
|
|
dcbcc4c050 | ||
|
|
0eb3375bb9 | ||
|
|
c26a8810c8 | ||
|
|
872227e345 | ||
|
|
f22c529eab | ||
|
|
3430b33c3e | ||
|
|
1bc2a6ef76 | ||
|
|
5a94f5bf5b | ||
|
|
4277377189 | ||
|
|
f0efb9253c | ||
|
|
cfd28f2608 | ||
|
|
a3844fe074 | ||
|
|
65e90f12f4 | ||
|
|
4335289d6a | ||
|
|
86cc721e03 | ||
|
|
4a28825ea7 | ||
|
|
19cf823da5 | ||
|
|
737b55d78d | ||
|
|
8493131db5 | ||
|
|
0d86c2af37 | ||
|
|
b260a4dc29 | ||
|
|
1f1024f4ca | ||
|
|
9e92d92684 | ||
|
|
527bf79973 | ||
|
|
b281c5bbc1 | ||
|
|
f03de8925b | ||
|
|
776ab2c715 | ||
|
|
df967b7e84 | ||
|
|
a539058253 | ||
|
|
af70d88153 | ||
|
|
8dcffe270f | ||
|
|
c958f2e50a | ||
|
|
cedcd65c72 | ||
|
|
12f62075ad | ||
|
|
b8695b70a9 | ||
|
|
a4e371618a | ||
|
|
039ab175c3 | ||
|
|
7549e50fe4 | ||
|
|
3c2cda699e | ||
|
|
8685ddd049 | ||
|
|
c47ad40802 | ||
|
|
ef1f129b22 | ||
|
|
6bb508ef14 | ||
|
|
3596c8144d | ||
|
|
20903bb638 | ||
|
|
f45fb6848f | ||
|
|
400f9b76d5 | ||
|
|
38a9e98d9b | ||
|
|
e8fe783fb4 | ||
|
|
223ef32b70 | ||
|
|
0793a219a2 | ||
|
|
3bb92c095f | ||
|
|
deec097267 | ||
|
|
873e280700 | ||
|
|
5d047f7a93 | ||
|
|
f24ab23752 | ||
|
|
44ecc8ce56 | ||
|
|
e758b1d9bb | ||
|
|
5cdbaa873d | ||
|
|
e9aca6cedb | ||
|
|
7c3896ed42 | ||
|
|
93158e8e90 | ||
|
|
5f9bbdfa06 | ||
|
|
3a0f486e98 | ||
|
|
29c671c0f4 | ||
|
|
88c4bef5e7 | ||
|
|
6066bc468b | ||
|
|
efd944d822 | ||
|
|
4a3f2caf59 | ||
|
|
511182b41b | ||
|
|
1088a51ed5 | ||
|
|
e3e0842bdd | ||
|
|
e4c908b08b | ||
|
|
f86578a213 | ||
|
|
3c2f5ec48e | ||
|
|
fec7ef17aa | ||
|
|
29ff99dd76 | ||
|
|
6b9b410bdc | ||
|
|
b45a9d55ca | ||
|
|
7ce079b7a1 | ||
|
|
b0ba9ff14f | ||
|
|
f665bf984b | ||
|
|
ac429a62c0 | ||
|
|
dc909d10b6 | ||
|
|
aa65077b12 | ||
|
|
7e37c51856 | ||
|
|
6e26daf804 | ||
|
|
25c2d2d5bf | ||
|
|
edc9e69f30 | ||
|
|
2e7ac38678 | ||
|
|
1a68c825c0 | ||
|
|
2cbdeeade0 | ||
|
|
79624f63ed | ||
|
|
01a8ec36ec | ||
|
|
60324885ed | ||
|
|
0c2f43b837 | ||
|
|
0df27cf730 | ||
|
|
68ed69292c | ||
|
|
62c58b3a8c | ||
|
|
1fbb809057 | ||
|
|
e2d2f5d670 | ||
|
|
acef0da2c1 | ||
|
|
3c66d2ab99 | ||
|
|
31f16c4680 | ||
|
|
8056379fdd | ||
|
|
9a6b9a7841 | ||
|
|
02fc39ebe0 | ||
|
|
a90b22c05d | ||
|
|
f5dd4f2aca | ||
|
|
d5b3489b22 | ||
|
|
8ee5f19184 | ||
|
|
d0a32d48b1 | ||
|
|
bf527437a0 | ||
|
|
ae3070ac45 | ||
|
|
6af68343a7 | ||
|
|
e632b51eb8 | ||
|
|
c8e633c4a1 | ||
|
|
724f4a59db | ||
|
|
041364fb7d | ||
|
|
fbcb4d8dbd | ||
|
|
27a6b4a8c9 | ||
|
|
c814e9e94e | ||
|
|
0e957c0cd4 | ||
|
|
b330657e0a | ||
|
|
a0fce64fd9 | ||
|
|
b183a04fba | ||
|
|
7645b997b2 | ||
|
|
d81e2f1470 | ||
|
|
f50fe72df2 | ||
|
|
dcca64a986 | ||
|
|
c216cfe0fd | ||
|
|
192f15e3b7 | ||
|
|
01be3daf6d | ||
|
|
d36eec5637 | ||
|
|
121464fa2d | ||
|
|
ee0254e822 | ||
|
|
27f634402c | ||
|
|
67fbe3b34e | ||
|
|
164ebce990 | ||
|
|
571ae704e0 | ||
|
|
ad305fb653 | ||
|
|
fc0541ce53 | ||
|
|
c8555f448c | ||
|
|
96e41198ec | ||
|
|
57064aef4d | ||
|
|
cf200aa58a | ||
|
|
388a4f85a4 | ||
|
|
5f8556b1b2 | ||
|
|
e411b57124 | ||
|
|
0120e7429d | ||
|
|
377dbe28eb | ||
|
|
b25b1d5750 | ||
|
|
0e1b792bf7 | ||
|
|
417f0d17c9 | ||
|
|
d9252fe755 | ||
|
|
c5555ab5fe | ||
|
|
eb61dc7d91 | ||
|
|
a4c522f090 | ||
|
|
a8fe8c3e71 | ||
|
|
87000306c0 | ||
|
|
ae07b7d0a8 | ||
|
|
626b76610f | ||
|
|
b4a1e1b0c9 | ||
|
|
913243f8c1 | ||
|
|
563ed81984 | ||
|
|
fcbf339a86 | ||
|
|
70585e1d2a | ||
|
|
479e568296 | ||
|
|
a473e41ab3 | ||
|
|
92f6a2d8e9 | ||
|
|
06dc5740bf | ||
|
|
fe524e0fac | ||
|
|
ea7de2eb70 | ||
|
|
b8d02537a6 | ||
|
|
24744ef8c5 | ||
|
|
69997466be | ||
|
|
f7d7fdf5b1 | ||
|
|
c0013c5639 | ||
|
|
935040204f | ||
|
|
2ffbf9b017 | ||
|
|
0f67b9a9d1 | ||
|
|
c5dee51233 | ||
|
|
b07238d536 | ||
|
|
e2a65c28f4 | ||
|
|
1f457cdde0 | ||
|
|
46fda5f0a6 | ||
|
|
e22c2f839b | ||
|
|
5bff912162 | ||
|
|
f3010cecbe | ||
|
|
2dc275defd | ||
|
|
9f79445292 | ||
|
|
10cb26b81e | ||
|
|
3722e0ad91 | ||
|
|
f28a0ec743 | ||
|
|
a42b393bf1 | ||
|
|
41f50777bd | ||
|
|
6afc3ba12e | ||
|
|
d8b7040a9e | ||
|
|
59cd70ae6f | ||
|
|
6d9c8561fb | ||
|
|
edb8a92838 | ||
|
|
da3ffe9a60 | ||
|
|
e2b791dee8 | ||
|
|
9178be576b | ||
|
|
752f5cff55 | ||
|
|
3675787ddd | ||
|
|
6284ed0347 | ||
|
|
1ff05c4b3d | ||
|
|
066abe4e52 | ||
|
|
e9134ddcf6 | ||
|
|
0237851aa1 | ||
|
|
1841251fdf | ||
|
|
b1e48406f3 | ||
|
|
4ca51cf11e | ||
|
|
41bd61c3a5 | ||
|
|
c6df057e15 | ||
|
|
1428481a5c | ||
|
|
1c359fbea9 | ||
|
|
0329694760 | ||
|
|
936ec5a23a | ||
|
|
d291506284 | ||
|
|
3877351fdc | ||
|
|
7885572ebd | ||
|
|
2ee926023c | ||
|
|
69e557cd8c | ||
|
|
f2efa73e20 | ||
|
|
2dd57956d5 | ||
|
|
ce86a1c9f2 | ||
|
|
a0f83c3b2b | ||
|
|
80a16ee42a | ||
|
|
31b05fedd3 | ||
|
|
d3a7950483 | ||
|
|
f299621490 | ||
|
|
c62ee3e207 | ||
|
|
a313087937 | ||
|
|
3945571b20 | ||
|
|
c6a044416a | ||
|
|
d2f1b50ad0 | ||
|
|
1f1bae77dd | ||
|
|
829e22dc1e | ||
|
|
8b28ee818f | ||
|
|
afb2e3d5b4 | ||
|
|
fb0adb9ccb | ||
|
|
efea514f5a | ||
|
|
fda9bd52a3 | ||
|
|
01f8ce6b03 | ||
|
|
e10651565f | ||
|
|
35aa56d334 | ||
|
|
31e1f2fc59 | ||
|
|
ee30f7a10b | ||
|
|
a50909d474 | ||
|
|
63115d51e5 | ||
|
|
026036a14b | ||
|
|
22e09a778b | ||
|
|
488088d5f0 | ||
|
|
0c18880e5c | ||
|
|
0b0a571d17 | ||
|
|
ea9e596279 | ||
|
|
fe17c8406b | ||
|
|
e7a4a5135d | ||
|
|
ebf12860be | ||
|
|
4b81778e46 | ||
|
|
28a8a90bb1 | ||
|
|
8dab5a8f04 | ||
|
|
4ac9483213 | ||
|
|
ae3c0d72c0 | ||
|
|
83b73e823d | ||
|
|
da6ae608f1 | ||
|
|
1e39647d7f | ||
|
|
4dc92247d5 | ||
|
|
1b6f982d68 | ||
|
|
81c775ea87 | ||
|
|
09f741e930 | ||
|
|
602a863d89 | ||
|
|
3dd19a7f62 | ||
|
|
bdf890ab0d | ||
|
|
0b7eae0c20 | ||
|
|
f3e273744a | ||
|
|
7f9b0bd5a2 | ||
|
|
6579a95999 | ||
|
|
aaa8e4a01b | ||
|
|
200d4385ec | ||
|
|
50d983a1ab | ||
|
|
1e6d295746 | ||
|
|
5d128c154c | ||
|
|
6c7d7f4b7e | ||
|
|
0cc8fdb8f8 | ||
|
|
4f7d915853 | ||
|
|
61738a3f6a | ||
|
|
9061c1987a | ||
|
|
c40b3a86a9 | ||
|
|
8b2c090bac | ||
|
|
8f2a3bd8bd | ||
|
|
ade05a6262 | ||
|
|
a706e69be6 | ||
|
|
bc6a813c46 | ||
|
|
599373c24a | ||
|
|
daebd08475 | ||
|
|
a3f658938d | ||
|
|
23414fe411 | ||
|
|
c8061fdee9 | ||
|
|
857b21a6a3 | ||
|
|
48be3f46b8 | ||
|
|
66012a57c8 | ||
|
|
0e77d8a459 | ||
|
|
b559c2345a | ||
|
|
d79d7da299 | ||
|
|
b296ac08cf | ||
|
|
36e677dd6b | ||
|
|
6167993941 | ||
|
|
f9d3f0be27 | ||
|
|
7f76cacc10 | ||
|
|
96cac8da85 | ||
|
|
a788e976c9 | ||
|
|
68c1319ed5 | ||
|
|
9db6f256e5 | ||
|
|
33e19c003c | ||
|
|
337d688bd3 | ||
|
|
cc305f8957 | ||
|
|
40cb38e0a0 | ||
|
|
a0d3ac047d | ||
|
|
e399bd19a2 | ||
|
|
f64f144b4b | ||
|
|
197a0c3048 | ||
|
|
e5e6b0d74f | ||
|
|
db1c2ee5cc | ||
|
|
c6a43ba4c2 | ||
|
|
ed45f52433 | ||
|
|
2d478c36fa | ||
|
|
f140007758 | ||
|
|
f03b09a410 | ||
|
|
0d9767596a | ||
|
|
c626706e27 | ||
|
|
58b653f1ae | ||
|
|
5eeb98d39d | ||
|
|
e90b64b463 | ||
|
|
5fd682d83a | ||
|
|
49193c972f | ||
|
|
c027c0a527 | ||
|
|
8160c33aa4 | ||
|
|
7d0226e3c4 | ||
|
|
084c1dc5b5 | ||
|
|
bfa320c5b5 | ||
|
|
4e1cf11461 | ||
|
|
5b4197da2c | ||
|
|
e29b2f12fb | ||
|
|
33a41bb2e4 | ||
|
|
ab754e9409 | ||
|
|
433955acf7 | ||
|
|
55445a80a3 | ||
|
|
9a64ea0670 | ||
|
|
0ff144a787 | ||
|
|
8893045bf1 | ||
|
|
a8de02bd53 | ||
|
|
4db0aec1c2 | ||
|
|
d885087c93 | ||
|
|
065aad2e4c | ||
|
|
9027ce055a | ||
|
|
119b923522 | ||
|
|
3b4d0652c4 | ||
|
|
1301934796 | ||
|
|
a8539bae02 | ||
|
|
8121faa1d4 | ||
|
|
70766b4e68 | ||
|
|
361efd43a2 | ||
|
|
a78b9fe5bd | ||
|
|
0cba4695ba | ||
|
|
22fa81433e | ||
|
|
5458a0e8f5 | ||
|
|
0e67f85995 | ||
|
|
808003f3df | ||
|
|
46c225d50f | ||
|
|
0a3656efc5 | ||
|
|
0aca600063 | ||
|
|
38f05ff010 | ||
|
|
3d4cbbf6ba | ||
|
|
c877e16c14 | ||
|
|
acce8eb146 |
@@ -1,7 +1,16 @@
|
||||
/* eslint-env node */
|
||||
|
||||
module.exports = {
|
||||
plugins: ['@typescript-eslint', 'wc', 'lit', 'lit-a11y', 'chai-expect', 'chai-friendly', 'import'],
|
||||
plugins: [
|
||||
'@typescript-eslint',
|
||||
'wc',
|
||||
'lit',
|
||||
'lit-a11y',
|
||||
'chai-expect',
|
||||
'chai-friendly',
|
||||
'import',
|
||||
'sort-imports-es6-autofix'
|
||||
],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:wc/recommended',
|
||||
@@ -13,7 +22,6 @@ module.exports = {
|
||||
es2021: true,
|
||||
browser: true
|
||||
},
|
||||
reportUnusedDisableDirectives: true,
|
||||
parserOptions: {
|
||||
sourceType: 'module'
|
||||
},
|
||||
@@ -105,6 +113,7 @@ module.exports = {
|
||||
curly: 'off',
|
||||
'default-param-last': 'error',
|
||||
eqeqeq: 'error',
|
||||
'lit-a11y/click-events-have-key-events': 'off',
|
||||
'no-constructor-return': 'error',
|
||||
'no-empty-function': 'warn',
|
||||
'no-eval': 'error',
|
||||
@@ -149,8 +158,8 @@ module.exports = {
|
||||
'prefer-object-spread': 'warn',
|
||||
'prefer-rest-params': 'warn',
|
||||
'prefer-spread': 'warn',
|
||||
'prefer-template': 'warn',
|
||||
'no-else-return': 'warn',
|
||||
'prefer-template': 'off',
|
||||
'no-else-return': 'off',
|
||||
'func-names': ['warn', 'never'],
|
||||
'one-var': ['warn', 'never'],
|
||||
'operator-assignment': 'warn',
|
||||
@@ -171,22 +180,12 @@ module.exports = {
|
||||
}
|
||||
],
|
||||
'import/no-duplicates': 'warn',
|
||||
'import/order': [
|
||||
'warn',
|
||||
'sort-imports-es6-autofix/sort-imports-es6': [
|
||||
2,
|
||||
{
|
||||
groups: ['builtin', 'external', 'internal', 'unknown', 'parent', 'sibling', 'index', 'object', 'type'],
|
||||
pathGroups: [
|
||||
{
|
||||
pattern: 'dist/**',
|
||||
group: 'external'
|
||||
}
|
||||
],
|
||||
alphabetize: {
|
||||
order: 'asc',
|
||||
caseInsensitive: true
|
||||
},
|
||||
'newlines-between': 'never',
|
||||
warnOnUnassignedImports: true
|
||||
ignoreCase: true,
|
||||
ignoreMemberSort: false,
|
||||
memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single']
|
||||
}
|
||||
],
|
||||
'wc/guard-super-call': 'off'
|
||||
|
||||
2
.github/workflows/node.js.yml
vendored
2
.github/workflows/node.js.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x, 16.x]
|
||||
node-version: [18.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"apos",
|
||||
"atrule",
|
||||
"autocorrect",
|
||||
"autofix",
|
||||
"autoplay",
|
||||
"bezier",
|
||||
"boxicons",
|
||||
@@ -20,6 +21,7 @@
|
||||
"codebases",
|
||||
"codepen",
|
||||
"colocated",
|
||||
"colour",
|
||||
"combobox",
|
||||
"Composability",
|
||||
"Consolas",
|
||||
@@ -44,6 +46,7 @@
|
||||
"fieldsets",
|
||||
"formaction",
|
||||
"formdata",
|
||||
"formenctype",
|
||||
"formmethod",
|
||||
"formnovalidate",
|
||||
"formtarget",
|
||||
@@ -120,6 +123,7 @@
|
||||
"tera",
|
||||
"textareas",
|
||||
"textfield",
|
||||
"tinycolor",
|
||||
"transitionend",
|
||||
"treeitem",
|
||||
"Triaging",
|
||||
|
||||
@@ -1,11 +1,29 @@
|
||||
import fs from 'fs';
|
||||
import { generateCustomData } from 'cem-plugin-vs-code-custom-data-generator';
|
||||
import { parse } from 'comment-parser';
|
||||
import { pascalCase } from 'pascal-case';
|
||||
import commandLineArgs from 'command-line-args';
|
||||
import fs from 'fs';
|
||||
|
||||
const packageData = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
||||
const { name, description, version, author, homepage, license } = packageData;
|
||||
// eslint-disable-next-line func-style
|
||||
const noDash = string => string.replace(/^\s?-/, '').trim();
|
||||
|
||||
const { outdir } = commandLineArgs([
|
||||
{ name: 'litelement', type: String },
|
||||
{ name: 'analyze', defaultOption: true },
|
||||
{ name: 'outdir', type: String }
|
||||
]);
|
||||
|
||||
function noDash(string) {
|
||||
return string.replace(/^\s?-/, '').trim();
|
||||
}
|
||||
|
||||
function replace(string, terms) {
|
||||
terms.forEach(({ from, to }) => {
|
||||
string = string?.replace(from, to);
|
||||
});
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
export default {
|
||||
globs: ['src/components/**/*.ts'],
|
||||
@@ -27,7 +45,7 @@ export default {
|
||||
case ts.SyntaxKind.ClassDeclaration: {
|
||||
const className = node.name.getText();
|
||||
const classDoc = moduleDoc?.declarations?.find(declaration => declaration.name === className);
|
||||
const customTags = ['animation', 'dependency', 'since', 'status'];
|
||||
const customTags = ['animation', 'dependency', 'documentation', 'since', 'status', 'title'];
|
||||
let customComments = '/**';
|
||||
|
||||
node.jsDoc?.forEach(jsDoc => {
|
||||
@@ -63,8 +81,10 @@ export default {
|
||||
break;
|
||||
|
||||
// Value-only metadata tags
|
||||
case 'documentation':
|
||||
case 'since':
|
||||
case 'status':
|
||||
case 'title':
|
||||
classDoc[t.tag] = t.name;
|
||||
break;
|
||||
|
||||
@@ -85,7 +105,6 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'shoelace-react-event-names',
|
||||
analyzePhase({ ts, node, moduleDoc }) {
|
||||
@@ -102,6 +121,47 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'shoelace-translate-module-paths',
|
||||
packageLinkPhase({ customElementsManifest }) {
|
||||
customElementsManifest?.modules?.forEach(mod => {
|
||||
//
|
||||
// CEM paths look like this:
|
||||
//
|
||||
// src/components/button/button.ts
|
||||
//
|
||||
// But we want them to look like this:
|
||||
//
|
||||
// components/button/button.js
|
||||
//
|
||||
const terms = [
|
||||
{ from: /^src\//, to: '' }, // Strip the src/ prefix
|
||||
{ from: /\.(t|j)sx?$/, to: '.js' } // Convert .ts to .js
|
||||
];
|
||||
|
||||
mod.path = replace(mod.path, terms);
|
||||
|
||||
for (const ex of mod.exports ?? []) {
|
||||
ex.declaration.module = replace(ex.declaration.module, terms);
|
||||
}
|
||||
|
||||
for (const dec of mod.declarations ?? []) {
|
||||
if (dec.kind === 'class') {
|
||||
for (const member of dec.members ?? []) {
|
||||
if (member.inheritedFrom) {
|
||||
member.inheritedFrom.module = replace(member.inheritedFrom.module, terms);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
// Generate custom VS Code data
|
||||
generateCustomData({
|
||||
outdir,
|
||||
cssFileName: null
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
- [Menu](/components/menu)
|
||||
- [Menu Item](/components/menu-item)
|
||||
- [Menu Label](/components/menu-label)
|
||||
- [Option](/components/option)
|
||||
- [Progress Bar](/components/progress-bar)
|
||||
- [Progress Ring](/components/progress-ring)
|
||||
- [QR Code](/components/qr-code)
|
||||
@@ -91,6 +92,7 @@
|
||||
- [Border Radius](/tokens/border-radius)
|
||||
- [Transition](/tokens/transition)
|
||||
- [Z-index](/tokens/z-index)
|
||||
- [More](/tokens/more)
|
||||
|
||||
- Tutorials
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 950 KiB After Width: | Height: | Size: 1.0 MiB |
@@ -146,8 +146,6 @@
|
||||
</div>
|
||||
|
||||
<div class="code-block__buttons">
|
||||
${hasReact ? ` ${htmlButton} ${reactButton} ` : ''}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="code-block__button code-block__toggle"
|
||||
@@ -167,6 +165,8 @@
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
${hasReact ? ` ${htmlButton} ${reactButton} ` : ''}
|
||||
|
||||
${!code.classList.contains('no-codepen') ? codePenButton : ''}
|
||||
</div>
|
||||
</div>
|
||||
@@ -232,36 +232,45 @@
|
||||
|
||||
// Toggle source mode
|
||||
document.addEventListener('click', event => {
|
||||
const button = event.target.closest('button');
|
||||
const button = event.target.closest('.code-block__button');
|
||||
const codeBlock = button?.closest('.code-block');
|
||||
|
||||
if (button?.classList.contains('code-block__button--html')) {
|
||||
// Show HTML
|
||||
setFlavor('html');
|
||||
toggleSource(codeBlock, true);
|
||||
} else if (button?.classList.contains('code-block__button--react')) {
|
||||
// Show React
|
||||
setFlavor('react');
|
||||
toggleSource(codeBlock, true);
|
||||
} else if (button?.classList.contains('code-block__toggle')) {
|
||||
// Toggle source
|
||||
toggleSource(codeBlock);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update flavor buttons
|
||||
[...document.querySelectorAll('.code-block')].forEach(codeBlock => {
|
||||
codeBlock
|
||||
.querySelector('.code-block__button--html')
|
||||
?.classList.toggle('code-block__button--selected', flavor === 'html');
|
||||
codeBlock
|
||||
.querySelector('.code-block__button--react')
|
||||
?.classList.toggle('code-block__button--selected', flavor === 'react');
|
||||
[...document.querySelectorAll('.code-block')].forEach(cb => {
|
||||
cb.querySelector('.code-block__button--html')?.classList.toggle(
|
||||
'code-block__button--selected',
|
||||
flavor === 'html'
|
||||
);
|
||||
cb.querySelector('.code-block__button--react')?.classList.toggle(
|
||||
'code-block__button--selected',
|
||||
flavor === 'react'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Expand and collapse code blocks
|
||||
document.addEventListener('click', event => {
|
||||
const toggle = event.target.closest('.code-block__toggle');
|
||||
function toggleSource(codeBlock, force) {
|
||||
const toggle = codeBlock.querySelector('.code-block__toggle');
|
||||
|
||||
if (toggle) {
|
||||
const codeBlock = event.target.closest('.code-block');
|
||||
codeBlock.classList.toggle('code-block--expanded');
|
||||
codeBlock.classList.toggle('code-block--expanded', force === undefined ? undefined : force);
|
||||
event.target.setAttribute('aria-expanded', codeBlock.classList.contains('code-block--expanded'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show pulse when copying
|
||||
document.addEventListener('click', event => {
|
||||
|
||||
@@ -57,7 +57,18 @@
|
||||
`;
|
||||
})
|
||||
.join('')}
|
||||
</tbody>
|
||||
|
||||
<tr>
|
||||
<td class="nowrap"><code>updateComplete</code></td>
|
||||
<td>
|
||||
A read-only promise that resolves when the component has
|
||||
<a href="/getting-started/usage?id=component-rendering-and-updating">finished updating</a>.
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
`;
|
||||
|
||||
return table.outerHTML;
|
||||
@@ -110,7 +121,7 @@
|
||||
.map(
|
||||
method => `
|
||||
<tr>
|
||||
<td class="nowrap"><code>${escapeHtml(method.name)}</code></td>
|
||||
<td class="nowrap"><code>${escapeHtml(method.name)}()</code></td>
|
||||
<td>${escapeHtml(method.description)}</td>
|
||||
<td>
|
||||
${
|
||||
@@ -283,6 +294,7 @@
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" rel="noopener noreferrer" target="_blank">$1</a>')
|
||||
.replace(/`(.*?)`/g, '<code>$1</code>');
|
||||
}
|
||||
|
||||
@@ -374,7 +386,7 @@
|
||||
result += `
|
||||
<div class="component-header">
|
||||
<div class="component-header__tag">
|
||||
<code><${component.tagName}> | ${component.name}</code>
|
||||
<code><${component.tagName}> | ${component.title ?? component.name}</code>
|
||||
</div>
|
||||
|
||||
<div class="component-header__info">
|
||||
@@ -386,6 +398,10 @@
|
||||
${component.status}
|
||||
</sl-badge>
|
||||
</div>
|
||||
|
||||
<div class="component-header__summary">
|
||||
${component.summary ? `<p>${marked(component.summary)}</p>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -430,7 +446,7 @@
|
||||
To import this component from [the CDN](https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace) using a script tag:
|
||||
|
||||
\`\`\`html
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${metadata.package.version}/${component.path}"></script>
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${metadata.package.version}/dist/${component.path}"></script>
|
||||
\`\`\`
|
||||
</sl-tab-panel>
|
||||
|
||||
@@ -438,14 +454,14 @@
|
||||
To import this component from [the CDN](https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace) using a JavaScript import:
|
||||
|
||||
\`\`\`js
|
||||
import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${metadata.package.version}/${component.path}';
|
||||
import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${metadata.package.version}/dist/${component.path}';
|
||||
\`\`\`
|
||||
</sl-tab-panel>
|
||||
|
||||
<sl-tab-panel name="bundler">\n
|
||||
To import this component using [a bundler](/getting-started/installation#bundling):
|
||||
\`\`\`js
|
||||
import '@shoelace-style/shoelace/${component.path}';
|
||||
import '@shoelace-style/shoelace/dist/${component.path}';
|
||||
\`\`\`
|
||||
</sl-tab-panel>
|
||||
|
||||
@@ -480,12 +496,21 @@
|
||||
`;
|
||||
}
|
||||
|
||||
if (component.slots?.length) {
|
||||
result += `
|
||||
## Slots
|
||||
${createSlotsTable(component.slots)}
|
||||
|
||||
_Learn more about [using slots](/getting-started/usage#slots)._
|
||||
`;
|
||||
}
|
||||
|
||||
if (props?.length) {
|
||||
result += `
|
||||
## Properties
|
||||
## Attributes & Properties
|
||||
${createPropsTable(props)}
|
||||
|
||||
_Learn more about [properties and attributes](/getting-started/usage#properties)._
|
||||
_Learn more about [attributes and properties](/getting-started/usage#properties)._
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -508,15 +533,6 @@
|
||||
`;
|
||||
}
|
||||
|
||||
if (component.slots?.length) {
|
||||
result += `
|
||||
## Slots
|
||||
${createSlotsTable(component.slots)}
|
||||
|
||||
_Learn more about [using slots](/getting-started/usage#slots)._
|
||||
`;
|
||||
}
|
||||
|
||||
if (component.cssProperties?.length) {
|
||||
result += `
|
||||
## CSS Custom Properties
|
||||
@@ -548,7 +564,7 @@
|
||||
result += `
|
||||
## Dependencies
|
||||
|
||||
This component imports the following dependencies.
|
||||
This component automatically imports the following dependencies.
|
||||
|
||||
${createDependenciesList(component.tagName, getAllComponents(metadata))}
|
||||
`;
|
||||
|
||||
@@ -178,3 +178,15 @@ body.site-search-visible {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Forced colors mode */
|
||||
@media (forced-colors: active) {
|
||||
.site-search__panel {
|
||||
border: solid 1px var(--sl-color-neutral-0);
|
||||
}
|
||||
|
||||
.site-search__results li[aria-selected='true'] a {
|
||||
outline: dashed 1px SelectedItem;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,10 +47,10 @@
|
||||
</sl-button>
|
||||
<sl-menu>
|
||||
<sl-menu-label>Toggle <kbd>\\</kbd></sl-menu-label>
|
||||
<sl-menu-item value="light">Light</sl-menu-item>
|
||||
<sl-menu-item value="dark">Dark</sl-menu-item>
|
||||
<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 value="auto">Auto</sl-menu-item>
|
||||
<sl-menu-item type="checkbox" value="auto">Auto</sl-menu-item>
|
||||
</sl-menu>
|
||||
`;
|
||||
document.querySelector('.sidebar-toggle').insertAdjacentElement('afterend', dropdown);
|
||||
|
||||
@@ -490,6 +490,7 @@ kbd,
|
||||
.markdown-section p.tip code,
|
||||
.markdown-section p.warn code {
|
||||
background-color: var(--sl-color-neutral-100);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Sponsorship callouts */
|
||||
@@ -508,10 +509,7 @@ kbd,
|
||||
|
||||
/* Component headers */
|
||||
.component-header {
|
||||
border-bottom: solid 1px var(--sl-color-neutral-200);
|
||||
padding-bottom: 2rem;
|
||||
margin-top: -1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.component-header__tag {
|
||||
@@ -520,6 +518,13 @@ kbd,
|
||||
margin: 0.75rem 0 0.25rem 0;
|
||||
}
|
||||
|
||||
.component-header__summary {
|
||||
font-size: var(--sl-font-size-large);
|
||||
line-height: 1.6;
|
||||
border-top: solid 1px var(--sl-color-neutral-200);
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.markdown-section .component-header__tag code {
|
||||
background: none;
|
||||
color: var(--sl-color-neutral-600);
|
||||
@@ -532,12 +537,6 @@ kbd,
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* Lead sentences that occur immediately after the header */
|
||||
.component-header + p {
|
||||
font-size: var(--sl-font-size-large);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Repo buttons */
|
||||
.repo-button--sponsor sl-icon {
|
||||
color: var(--sl-color-pink-600);
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-alert]
|
||||
|
||||
Alerts are used to display important messages inline or as toast notifications.
|
||||
|
||||
```html preview
|
||||
<sl-alert open>
|
||||
<sl-icon slot="icon" name="info-circle"></sl-icon>
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-animated-image]
|
||||
|
||||
A component for displaying animated GIFs and WEBPs that play and pause on interaction.
|
||||
|
||||
```html preview
|
||||
<sl-animated-image
|
||||
src="https://shoelace.style/assets/images/walk.gif"
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-animation]
|
||||
|
||||
Animate elements declaratively with nearly 100 baked-in presets, or roll your own with custom keyframes. Powered by the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API).
|
||||
|
||||
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
|
||||
@@ -93,19 +91,19 @@ This example demonstrates all of the baked-in animations and easings. Animations
|
||||
const easings = getEasingNames();
|
||||
|
||||
animations.map(name => {
|
||||
const menuItem = Object.assign(document.createElement('sl-menu-item'), {
|
||||
const option = Object.assign(document.createElement('sl-option'), {
|
||||
textContent: name,
|
||||
value: name
|
||||
});
|
||||
animationName.appendChild(menuItem);
|
||||
animationName.appendChild(option);
|
||||
});
|
||||
|
||||
easings.map(name => {
|
||||
const menuItem = Object.assign(document.createElement('sl-menu-item'), {
|
||||
const option = Object.assign(document.createElement('sl-option'), {
|
||||
textContent: name,
|
||||
value: name
|
||||
});
|
||||
easingName.appendChild(menuItem);
|
||||
easingName.appendChild(option);
|
||||
});
|
||||
|
||||
animationName.addEventListener('sl-change', () => (animation.name = animationName.value));
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-avatar]
|
||||
|
||||
Avatars are used to represent a person or object.
|
||||
|
||||
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
|
||||
@@ -21,12 +19,18 @@ const App = () => <SlAvatar label="User avatar" />;
|
||||
### Images
|
||||
|
||||
To use an image for the avatar, set the `image` and `label` attributes. This will take priority and be shown over initials and icons.
|
||||
Avatar images can be lazily loaded by setting the `loading` attribute to `lazy`.
|
||||
|
||||
```html preview
|
||||
<sl-avatar
|
||||
image="https://images.unsplash.com/photo-1529778873920-4da4926a72c2?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=80"
|
||||
label="Avatar of a gray tabby kitten looking down"
|
||||
></sl-avatar>
|
||||
<sl-avatar
|
||||
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"
|
||||
loading="lazy"
|
||||
></sl-avatar>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
@@ -37,6 +41,11 @@ const App = () => (
|
||||
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"
|
||||
/>
|
||||
<SlAvatar
|
||||
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"
|
||||
loading="lazy"
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-badge]
|
||||
|
||||
Badges are used to draw attention and display statuses or counts.
|
||||
|
||||
```html preview
|
||||
<sl-badge>Badge</sl-badge>
|
||||
```
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-breadcrumb-item]
|
||||
|
||||
Breadcrumb Items are used inside [breadcrumbs](/components/breadcrumb) to represent different links.
|
||||
|
||||
```html preview
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item>
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-breadcrumb]
|
||||
|
||||
Breadcrumbs provide a group of links so users can easily navigate a website's hierarchy.
|
||||
|
||||
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
|
||||
@@ -205,9 +203,9 @@ Dropdown menus can be placed in a prefix or suffix slot to provide additional op
|
||||
<sl-icon label="More options" name="three-dots"></sl-icon>
|
||||
</sl-button>
|
||||
<sl-menu>
|
||||
<sl-menu-item checked>Web Design</sl-menu-item>
|
||||
<sl-menu-item>Web Development</sl-menu-item>
|
||||
<sl-menu-item>Marketing</sl-menu-item>
|
||||
<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>
|
||||
@@ -237,9 +235,11 @@ const App = () => (
|
||||
<SlIcon label="More options" name="three-dots"></SlIcon>
|
||||
</SlButton>
|
||||
<SlMenu>
|
||||
<SlMenuItem checked>Web Design</SlMenuItem>
|
||||
<SlMenuItem>Web Development</SlMenuItem>
|
||||
<SlMenuItem>Marketing</SlMenuItem>
|
||||
<SlMenuItem type="checkbox" checked>
|
||||
Web Design
|
||||
</SlMenuItem>
|
||||
<SlMenuItem type="checkbox">Web Development</SlMenuItem>
|
||||
<SlMenuItem type="checkbox">Marketing</SlMenuItem>
|
||||
</SlMenu>
|
||||
</SlDropdown>
|
||||
</SlBreadcrumbItem>
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
|
||||
[component-header:sl-button-group]
|
||||
|
||||
Button groups can be used to group related buttons into sections.
|
||||
|
||||
```html preview
|
||||
<sl-button-group>
|
||||
<sl-button-group label="Alignment">
|
||||
<sl-button>Left</sl-button>
|
||||
<sl-button>Center</sl-button>
|
||||
<sl-button>Right</sl-button>
|
||||
@@ -16,7 +14,7 @@ Button groups can be used to group related buttons into sections.
|
||||
import { SlButton, SlButtonGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton>Left</SlButton>
|
||||
<SlButton>Center</SlButton>
|
||||
<SlButton>Right</SlButton>
|
||||
@@ -31,7 +29,7 @@ const App = () => (
|
||||
All button sizes are supported, but avoid mixing sizes within the same button group.
|
||||
|
||||
```html preview
|
||||
<sl-button-group>
|
||||
<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>
|
||||
@@ -39,7 +37,7 @@ All button sizes are supported, but avoid mixing sizes within the same button gr
|
||||
|
||||
<br /><br />
|
||||
|
||||
<sl-button-group>
|
||||
<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>
|
||||
@@ -47,7 +45,7 @@ All button sizes are supported, but avoid mixing sizes within the same button gr
|
||||
|
||||
<br /><br />
|
||||
|
||||
<sl-button-group>
|
||||
<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>
|
||||
@@ -59,7 +57,7 @@ import { SlButton, SlButtonGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton size="small">Left</SlButton>
|
||||
<SlButton size="small">Center</SlButton>
|
||||
<SlButton size="small">Right</SlButton>
|
||||
@@ -68,7 +66,7 @@ const App = () => (
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton size="medium">Left</SlButton>
|
||||
<SlButton size="medium">Center</SlButton>
|
||||
<SlButton size="medium">Right</SlButton>
|
||||
@@ -77,7 +75,7 @@ const App = () => (
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton size="large">Left</SlButton>
|
||||
<SlButton size="large">Center</SlButton>
|
||||
<SlButton size="large">Right</SlButton>
|
||||
@@ -88,10 +86,10 @@ const App = () => (
|
||||
|
||||
### Theme Buttons
|
||||
|
||||
Theme buttons are supported through the button's `type` attribute.
|
||||
Theme buttons are supported through the button's `variant` attribute.
|
||||
|
||||
```html preview
|
||||
<sl-button-group>
|
||||
<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>
|
||||
@@ -99,7 +97,7 @@ Theme buttons are supported through the button's `type` attribute.
|
||||
|
||||
<br /><br />
|
||||
|
||||
<sl-button-group>
|
||||
<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>
|
||||
@@ -107,7 +105,7 @@ Theme buttons are supported through the button's `type` attribute.
|
||||
|
||||
<br /><br />
|
||||
|
||||
<sl-button-group>
|
||||
<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>
|
||||
@@ -115,7 +113,7 @@ Theme buttons are supported through the button's `type` attribute.
|
||||
|
||||
<br /><br />
|
||||
|
||||
<sl-button-group>
|
||||
<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>
|
||||
@@ -123,7 +121,7 @@ Theme buttons are supported through the button's `type` attribute.
|
||||
|
||||
<br /><br />
|
||||
|
||||
<sl-button-group>
|
||||
<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>
|
||||
@@ -135,7 +133,7 @@ import { SlButton, SlButtonGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton variant="primary">Left</SlButton>
|
||||
<SlButton variant="primary">Center</SlButton>
|
||||
<SlButton variant="primary">Right</SlButton>
|
||||
@@ -144,7 +142,7 @@ const App = () => (
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton variant="success">Left</SlButton>
|
||||
<SlButton variant="success">Center</SlButton>
|
||||
<SlButton variant="success">Right</SlButton>
|
||||
@@ -153,7 +151,7 @@ const App = () => (
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton variant="neutral">Left</SlButton>
|
||||
<SlButton variant="neutral">Center</SlButton>
|
||||
<SlButton variant="neutral">Right</SlButton>
|
||||
@@ -162,7 +160,7 @@ const App = () => (
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton variant="warning">Left</SlButton>
|
||||
<SlButton variant="warning">Center</SlButton>
|
||||
<SlButton variant="warning">Right</SlButton>
|
||||
@@ -171,7 +169,7 @@ const App = () => (
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton variant="danger">Left</SlButton>
|
||||
<SlButton variant="danger">Center</SlButton>
|
||||
<SlButton variant="danger">Right</SlButton>
|
||||
@@ -185,7 +183,7 @@ const App = () => (
|
||||
Pill buttons are supported through the button's `pill` attribute.
|
||||
|
||||
```html preview
|
||||
<sl-button-group>
|
||||
<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>
|
||||
@@ -193,7 +191,7 @@ Pill buttons are supported through the button's `pill` attribute.
|
||||
|
||||
<br /><br />
|
||||
|
||||
<sl-button-group>
|
||||
<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>
|
||||
@@ -201,7 +199,7 @@ Pill buttons are supported through the button's `pill` attribute.
|
||||
|
||||
<br /><br />
|
||||
|
||||
<sl-button-group>
|
||||
<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>
|
||||
@@ -213,7 +211,7 @@ import { SlButton, SlButtonGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton size="small" pill>
|
||||
Left
|
||||
</SlButton>
|
||||
@@ -228,7 +226,7 @@ const App = () => (
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton size="medium" pill>
|
||||
Left
|
||||
</SlButton>
|
||||
@@ -243,7 +241,7 @@ const App = () => (
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton size="large" pill>
|
||||
Left
|
||||
</SlButton>
|
||||
@@ -263,7 +261,7 @@ const App = () => (
|
||||
Dropdowns can be placed inside button groups as long as the trigger is an `<sl-button>` element.
|
||||
|
||||
```html preview
|
||||
<sl-button-group>
|
||||
<sl-button-group label="Example Button Group">
|
||||
<sl-button>Button</sl-button>
|
||||
<sl-button>Button</sl-button>
|
||||
<sl-dropdown>
|
||||
@@ -281,7 +279,7 @@ Dropdowns can be placed inside button groups as long as the trigger is an `<sl-b
|
||||
import { SlButton, SlButtonGroup, SlDropdown, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Example Button Group">
|
||||
<SlButton>Button</SlButton>
|
||||
<SlButton>Button</SlButton>
|
||||
<SlDropdown>
|
||||
@@ -303,7 +301,7 @@ const App = () => (
|
||||
Create a split button using a button and a dropdown. Use a [visually hidden](/components/visually-hidden) label to ensure the dropdown is accessible to users with assistive devices.
|
||||
|
||||
```html preview
|
||||
<sl-button-group>
|
||||
<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>
|
||||
@@ -322,7 +320,7 @@ Create a split button using a button and a dropdown. Use a [visually hidden](/co
|
||||
import { SlButton, SlButtonGroup, SlDropdown, SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Example Button Group">
|
||||
<SlButton variant="primary">Save</SlButton>
|
||||
<SlDropdown placement="bottom-end">
|
||||
<SlButton slot="trigger" variant="primary" caret></SlButton>
|
||||
@@ -341,7 +339,7 @@ const App = () => (
|
||||
Buttons can be wrapped in tooltips to provide more detail when the user interacts with them.
|
||||
|
||||
```html preview
|
||||
<sl-button-group>
|
||||
<sl-button-group label="Alignment">
|
||||
<sl-tooltip content="I'm on the left">
|
||||
<sl-button>Left</sl-button>
|
||||
</sl-tooltip>
|
||||
@@ -361,7 +359,7 @@ import { SlButton, SlButtonGroup, SlTooltip } from '@shoelace-style/shoelace/dis
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlTooltip content="I'm on the left">
|
||||
<SlButton>Left</SlButton>
|
||||
</SlTooltip>
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-button]
|
||||
|
||||
Buttons represent actions that are available to the user.
|
||||
|
||||
```html preview
|
||||
<sl-button>Button</sl-button>
|
||||
```
|
||||
@@ -136,7 +134,7 @@ const App = () => (
|
||||
|
||||
### Circle Buttons
|
||||
|
||||
Use the `circle` attribute to create circular icon 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>
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-card]
|
||||
|
||||
Cards can be used to group related subjects in a container.
|
||||
|
||||
```html preview
|
||||
<sl-card class="card-overview">
|
||||
<img
|
||||
@@ -130,7 +128,6 @@ Headers can be used to display titles and more.
|
||||
<sl-card class="card-header">
|
||||
<div slot="header">
|
||||
Header Title
|
||||
|
||||
<sl-icon-button name="gear" label="Settings"></sl-icon-button>
|
||||
</div>
|
||||
|
||||
@@ -206,7 +203,7 @@ Footers can be used to display actions, summaries, or other relevant content.
|
||||
|
||||
<div slot="footer">
|
||||
<sl-rating></sl-rating>
|
||||
<sl-button slot="footer" variant="primary">Preview</sl-button>
|
||||
<sl-button variant="primary">Preview</sl-button>
|
||||
</div>
|
||||
</sl-card>
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-checkbox]
|
||||
|
||||
Checkboxes allow the user to toggle an option on or off.
|
||||
|
||||
```html preview
|
||||
<sl-checkbox>Checkbox</sl-checkbox>
|
||||
```
|
||||
@@ -60,6 +58,32 @@ import { SlCheckbox } from '@shoelace-style/shoelace/dist/react';
|
||||
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';
|
||||
|
||||
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.
|
||||
@@ -74,14 +98,17 @@ Use the `setCustomValidity()` method to set a custom validation message. This wi
|
||||
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(() => {
|
||||
checkbox.setCustomValidity(errorMessage);
|
||||
});
|
||||
|
||||
// Update validity on change
|
||||
checkbox.addEventListener('sl-change', () => {
|
||||
checkbox.setCustomValidity(checkbox.checked ? '' : errorMessage);
|
||||
});
|
||||
|
||||
// Handle submit
|
||||
form.addEventListener('submit', event => {
|
||||
event.preventDefault();
|
||||
@@ -93,19 +120,24 @@ Use the `setCustomValidity()` method to set a custom validation message. This wi
|
||||
```jsx react
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { SlButton, SlCheckbox } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
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}>
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-color-picker]
|
||||
|
||||
Color pickers allow the user to select a color.
|
||||
|
||||
```html preview
|
||||
<sl-color-picker label="Select a color"></sl-color-picker>
|
||||
```
|
||||
@@ -34,7 +32,7 @@ const App = () => <SlColorPicker value="#4a90e2" label="Select a color" />;
|
||||
|
||||
### Opacity
|
||||
|
||||
Use the `opacity` attribute to enable the opacity slider. When this is enabled, the value will be displayed as HEXA, RGBA, or HSLA 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
|
||||
<sl-color-picker value="#f5a623ff" opacity label="Select a color"></sl-color-picker>
|
||||
@@ -48,7 +46,7 @@ const App = () => <SlColorPicker opacity label="Select a color" />;
|
||||
|
||||
### Formats
|
||||
|
||||
Set the color picker's format with the `format` attribute. Valid options include `hex`, `rgb`, and `hsl`. Note that the color picker's input will accept any parsable format (including CSS color names) regardless of this option.
|
||||
Set the color picker's format with the `format` attribute. Valid options include `hex`, `rgb`, `hsl`, and `hsv`. Note that the color picker's input will accept any parsable format (including CSS color names) regardless of this option.
|
||||
|
||||
To prevent users from toggling the format themselves, add the `no-format-toggle` attribute.
|
||||
|
||||
@@ -56,6 +54,7 @@ To prevent users from toggling the format themselves, add the `no-format-toggle`
|
||||
<sl-color-picker format="hex" value="#4a90e2" label="Select a color"></sl-color-picker>
|
||||
<sl-color-picker format="rgb" value="rgb(80, 227, 194)" label="Select a color"></sl-color-picker>
|
||||
<sl-color-picker format="hsl" value="hsl(290, 87%, 47%)" label="Select a color"></sl-color-picker>
|
||||
<sl-color-picker format="hsv" value="hsv(55, 89%, 97%)" label="Select a color"></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
@@ -66,10 +65,39 @@ const App = () => (
|
||||
<SlColorPicker format="hex" value="#4a90e2" />
|
||||
<SlColorPicker format="rgb" value="rgb(80, 227, 194)" />
|
||||
<SlColorPicker format="hsl" value="hsl(290, 87%, 47%)" />
|
||||
<SlColorPicker format="hsv" value="hsv(55, 89%, 97%)" />
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Swatches
|
||||
|
||||
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
|
||||
<sl-color-picker
|
||||
label="Select a color"
|
||||
swatches="
|
||||
#d0021b; #f5a623; #f8e71c; #8b572a; #7ed321; #417505; #bd10e0; #9013fe;
|
||||
#4a90e2; #50e3c2; #b8e986; #000; #444; #888; #ccc; #fff;
|
||||
"
|
||||
></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlColorPicker
|
||||
label="Select a color"
|
||||
swatches="
|
||||
#d0021b; #f5a623; #f8e71c; #8b572a; #7ed321; #417505; #bd10e0; #9013fe;
|
||||
#4a90e2; #50e3c2; #b8e986; #000; #444; #888; #ccc; #fff;
|
||||
"
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
### Sizes
|
||||
|
||||
Use the `size` attribute to change the color picker's trigger size.
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
[component-header:sl-details]
|
||||
|
||||
Details show a brief summary and expand to show additional content.
|
||||
|
||||
```html preview
|
||||
<sl-details summary="Toggle Me">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
@@ -48,6 +46,52 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### Customizing the Summary Icon
|
||||
|
||||
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
|
||||
<sl-details summary="Toggle Me" class="custom-icons">
|
||||
<sl-icon name="plus-square" slot="expand-icon"></sl-icon>
|
||||
<sl-icon name="dash-square" slot="collapse-icon"></sl-icon>
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
</sl-details>
|
||||
|
||||
<style>
|
||||
sl-details.custom-icons::part(summary-icon) {
|
||||
/* Disable the expand/collapse animation */
|
||||
rotate: none;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlDetails, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
sl-details.custom-icon::part(summary-icon) {
|
||||
/* Disable the expand/collapse animation */
|
||||
rotate: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlDetails summary="Toggle Me" class="custom-icon">
|
||||
<SlIcon name="plus-square" slot="expand-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
|
||||
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||
consequat.
|
||||
</SlDetails>
|
||||
|
||||
<style>{css}</style>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### 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 `sl-show` event.
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
[component-header:sl-dialog]
|
||||
|
||||
Dialogs, sometimes called "modals", appear above the page and require the user's immediate attention.
|
||||
|
||||
```html preview
|
||||
<sl-dialog label="Dialog" class="dialog-overview">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
@@ -153,6 +151,59 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
### Header Actions
|
||||
|
||||
The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/components/icon-button) if needed.
|
||||
|
||||
```html preview
|
||||
<sl-dialog label="Dialog" class="dialog-header-actions">
|
||||
<sl-icon-button class="new-window" slot="header-actions" name="box-arrow-up-right"></sl-icon-button>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||
</sl-dialog>
|
||||
|
||||
<sl-button>Open Dialog</sl-button>
|
||||
|
||||
<script>
|
||||
const dialog = document.querySelector('.dialog-header-actions');
|
||||
const openButton = dialog.nextElementSibling;
|
||||
const closeButton = dialog.querySelector('sl-button[slot="footer"]');
|
||||
const newWindowButton = dialog.querySelector('.new-window');
|
||||
|
||||
openButton.addEventListener('click', () => dialog.show());
|
||||
closeButton.addEventListener('click', () => dialog.hide());
|
||||
newWindowButton.addEventListener('click', () => window.open(location.href));
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDialog, SlIconButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SlDialog label="Dialog" open={open} onSlAfterHide={() => setOpen(false)}>
|
||||
<SlIconButton
|
||||
class="new-window"
|
||||
slot="header-actions"
|
||||
name="box-arrow-up-right"
|
||||
onClick={() => window.open(location.href)}
|
||||
/>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||
Close
|
||||
</SlButton>
|
||||
</SlDialog>
|
||||
|
||||
<SlButton onClick={() => setOpen(true)}>Open Dialog</SlButton>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Preventing the Dialog from Closing
|
||||
|
||||
By default, dialogs will close when the user clicks the close button, clicks the overlay, or presses the <kbd>Escape</kbd> key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur.
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-divider]
|
||||
|
||||
Dividers are used to visually separate or group elements.
|
||||
|
||||
```html preview
|
||||
<sl-divider></sl-divider>
|
||||
```
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
[component-header:sl-drawer]
|
||||
|
||||
Drawers slide in from a container to expose additional options and information.
|
||||
|
||||
```html preview
|
||||
<sl-drawer label="Drawer" class="drawer-overview">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
@@ -182,7 +180,9 @@ const App = () => {
|
||||
|
||||
### Contained to an Element
|
||||
|
||||
By default, the drawer slides out of its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport. To make the drawer slide out of its parent element, add the `contained` attribute and `position: relative` to the parent.
|
||||
By default, drawers slide out of their [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport. To make a drawer slide out of a parent element, add the `contained` attribute to the drawer and apply `position: relative` to its parent.
|
||||
|
||||
Unlike normal drawers, contained drawers are not modal. This means they do not show an overlay, they do not trap focus, and they are not dismissible with <kbd>Escape</kbd>. This is intentional to allow users to interact with elements outside of the drawer.
|
||||
|
||||
```html preview
|
||||
<div
|
||||
@@ -196,14 +196,14 @@ By default, the drawer slides out of its [containing block](https://developer.mo
|
||||
</sl-drawer>
|
||||
</div>
|
||||
|
||||
<sl-button>Open Drawer</sl-button>
|
||||
<sl-button>Toggle Drawer</sl-button>
|
||||
|
||||
<script>
|
||||
const drawer = document.querySelector('.drawer-contained');
|
||||
const openButton = drawer.parentElement.nextElementSibling;
|
||||
const closeButton = drawer.querySelector('sl-button[variant="primary"]');
|
||||
|
||||
openButton.addEventListener('click', () => drawer.show());
|
||||
openButton.addEventListener('click', () => (drawer.open = !drawer.open));
|
||||
closeButton.addEventListener('click', () => drawer.hide());
|
||||
</script>
|
||||
```
|
||||
@@ -228,7 +228,14 @@ const App = () => {
|
||||
>
|
||||
The drawer will be contained to this box. This content won't shift or be affected in any way when the drawer
|
||||
opens.
|
||||
<SlDrawer label="Drawer" contained open={open} onSlAfterHide={() => setOpen(false)} style={{ '--size': '50%' }}>
|
||||
<SlDrawer
|
||||
label="Drawer"
|
||||
contained
|
||||
no-modal
|
||||
open={open}
|
||||
onSlAfterHide={() => setOpen(false)}
|
||||
style={{ '--size': '50%' }}
|
||||
>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||
Close
|
||||
@@ -340,6 +347,54 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
### Header Actions
|
||||
|
||||
The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/components/icon-button) if needed.
|
||||
|
||||
```html preview
|
||||
<sl-drawer label="Drawer" class="drawer-header-actions">
|
||||
<sl-icon-button class="new-window" slot="header-actions" name="box-arrow-up-right"></sl-icon-button>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||
</sl-drawer>
|
||||
|
||||
<sl-button>Open Drawer</sl-button>
|
||||
|
||||
<script>
|
||||
const drawer = document.querySelector('.drawer-header-actions');
|
||||
const openButton = drawer.nextElementSibling;
|
||||
const closeButton = drawer.querySelector('sl-button[variant="primary"]');
|
||||
const newWindowButton = drawer.querySelector('.new-window');
|
||||
|
||||
openButton.addEventListener('click', () => drawer.show());
|
||||
closeButton.addEventListener('click', () => drawer.hide());
|
||||
newWindowButton.addEventListener('click', () => window.open(location.href));
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDrawer, SlIconButton } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SlDrawer label="Drawer" open={open} onSlAfterHide={() => setOpen(false)}>
|
||||
<SlIconButton slot="header-actions" name="box-arrow-up-right" onClick={() => window.open(location.href)} />
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||
Close
|
||||
</SlButton>
|
||||
</SlDrawer>
|
||||
|
||||
<SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Preventing the Drawer from Closing
|
||||
|
||||
By default, drawers will close when the user clicks the close button, clicks the overlay, or presses the <kbd>Escape</kbd> key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur.
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-dropdown]
|
||||
|
||||
Dropdowns expose additional content that "drops down" in a panel.
|
||||
|
||||
Dropdowns consist of a trigger and a panel. By default, activating the trigger will expose the panel and interacting outside of the panel will close it.
|
||||
|
||||
Dropdowns are designed to work well with [menus](/components/menu) to provide a list of options the user can select from. However, dropdowns can also be used in lower-level applications (e.g. [color picker](/components/color-picker) and [select](/components/select)). The API gives you complete control over showing, hiding, and positioning the panel.
|
||||
@@ -16,7 +14,7 @@ Dropdowns are designed to work well with [menus](/components/menu) to provide a
|
||||
<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 checked>Checked</sl-menu-item>
|
||||
<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>
|
||||
@@ -44,7 +42,9 @@ const App = () => (
|
||||
<SlMenuItem>Dropdown Item 2</SlMenuItem>
|
||||
<SlMenuItem>Dropdown Item 3</SlMenuItem>
|
||||
<SlDivider />
|
||||
<SlMenuItem checked>Checked</SlMenuItem>
|
||||
<SlMenuItem type="checkbox" checked>
|
||||
Checkbox
|
||||
</SlMenuItem>
|
||||
<SlMenuItem disabled>Disabled</SlMenuItem>
|
||||
<SlDivider />
|
||||
<SlMenuItem>
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-format-bytes]
|
||||
|
||||
Formats a number as a human readable bytes value.
|
||||
|
||||
```html preview
|
||||
<div class="format-bytes-overview">
|
||||
The file is <sl-format-bytes value="1000"></sl-format-bytes> in size. <br /><br />
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-format-date]
|
||||
|
||||
Formats a date/time using the specified locale and options.
|
||||
|
||||
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
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-format-number]
|
||||
|
||||
Formats a number using the specified locale and options.
|
||||
|
||||
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
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-icon-button]
|
||||
|
||||
Icons buttons are simple, icon-only buttons that can be used for actions and in toolbars.
|
||||
|
||||
For a full list of icons that come bundled with Shoelace, refer to the [icon component](/components/icon).
|
||||
|
||||
```html preview
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-icon]
|
||||
|
||||
Icons are symbols that can be used to represent various options within an application.
|
||||
|
||||
Shoelace comes bundled with over 1,500 icons courtesy of the [Bootstrap Icons](https://icons.getbootstrap.com/) project. These icons are part of the `default` icon library. If you prefer, you can register [custom icon libraries](#icon-libraries) as well.
|
||||
|
||||
?> Depending on how you're loading Shoelace, you may need to copy icon assets and/or [set the base path](getting-started/installation#setting-the-base-path) so Shoelace knows where to load them from. Otherwise, icons may not appear and you'll see 404 Not Found errors in the dev console.
|
||||
@@ -22,9 +20,9 @@ All available icons in the `default` icon library are shown below. Click or tap
|
||||
<sl-icon slot="prefix" name="search"></sl-icon>
|
||||
</sl-input>
|
||||
<sl-select value="outline">
|
||||
<sl-menu-item value="outline">Outlined</sl-menu-item>
|
||||
<sl-menu-item value="fill">Filled</sl-menu-item>
|
||||
<sl-menu-item value="all">All icons</sl-menu-item>
|
||||
<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>
|
||||
@@ -637,6 +635,19 @@ If you want to change the icons Shoelace uses internally, you can register an ic
|
||||
|
||||
<!-- 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 => {
|
||||
@@ -660,19 +671,23 @@ If you want to change the icons Shoelace uses internally, you can register an ic
|
||||
<use xlink:href="/assets/icons/sprite.svg#${i.name}"></use>
|
||||
</svg>
|
||||
`;
|
||||
list.appendChild(item);
|
||||
|
||||
const tooltip = document.createElement('sl-tooltip');
|
||||
tooltip.content = i.name;
|
||||
|
||||
tooltip.appendChild(item);
|
||||
list.appendChild(tooltip);
|
||||
// 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');
|
||||
tooltip.content = 'Copied!';
|
||||
setTimeout(() => tooltip.content = i.name, 1000);
|
||||
|
||||
if (tooltip) {
|
||||
tooltip.content = 'Copied!';
|
||||
setTimeout(() => tooltip.content = i.name, 1000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-image-comparer]
|
||||
|
||||
Compare visual differences between similar photos with a sliding panel.
|
||||
|
||||
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
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-include]
|
||||
|
||||
Includes give you the power to embed external HTML files into the page.
|
||||
|
||||
Included files are asynchronously requested using `window.fetch()`. Requests are cached, so the same file can be included multiple times, but only one request will be made.
|
||||
|
||||
The included content will be inserted into the `<sl-include>` element's default slot so it can be easily accessed and styled through the light DOM.
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-input]
|
||||
|
||||
Inputs collect data from the user.
|
||||
|
||||
```html preview
|
||||
<sl-input></sl-input>
|
||||
```
|
||||
@@ -79,25 +77,13 @@ const App = () => <SlInput placeholder="Clearable" clearable />;
|
||||
Add the `password-toggle` attribute to add a toggle button that will show the password when activated.
|
||||
|
||||
```html preview
|
||||
<sl-input type="password" placeholder="Password Toggle" size="small" password-toggle></sl-input>
|
||||
<br />
|
||||
<sl-input type="password" placeholder="Password Toggle" size="medium" password-toggle></sl-input>
|
||||
<br />
|
||||
<sl-input type="password" placeholder="Password Toggle" size="large" password-toggle></sl-input>
|
||||
<sl-input type="password" placeholder="Password Toggle" password-toggle></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlInput type="password" placeholder="Password Toggle" size="small" password-toggle />
|
||||
<br />
|
||||
<SlInput type="password" placeholder="Password Toggle" size="medium" password-toggle />
|
||||
<br />
|
||||
<SlInput type="password" placeholder="Password Toggle" size="large" password-toggle />
|
||||
</>
|
||||
);
|
||||
const App = () => <SlInput type="password" placeholder="Password Toggle" size="medium" password-toggle />;
|
||||
```
|
||||
|
||||
### Filled Inputs
|
||||
@@ -114,6 +100,46 @@ import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
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';
|
||||
|
||||
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';
|
||||
|
||||
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.
|
||||
@@ -166,58 +192,6 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` attribute to disable an input.
|
||||
|
||||
```html preview
|
||||
<sl-input placeholder="Disabled" size="small" disabled></sl-input>
|
||||
<br />
|
||||
<sl-input placeholder="Disabled" size="medium" disabled></sl-input>
|
||||
<br />
|
||||
<sl-input placeholder="Disabled" size="large" disabled></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlInput placeholder="Disabled" size="small" disabled />
|
||||
<br />
|
||||
<SlInput placeholder="Disabled" size="medium" disabled />
|
||||
<br />
|
||||
<SlInput placeholder="Disabled" size="large" 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';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlInput placeholder="Small" size="small" />
|
||||
<br />
|
||||
<SlInput placeholder="Medium" size="medium" />
|
||||
<br />
|
||||
<SlInput placeholder="Large" size="large" />
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Prefix & Suffix Icons
|
||||
|
||||
Use the `prefix` and `suffix` slots to add icons.
|
||||
@@ -264,27 +238,36 @@ const App = () => (
|
||||
|
||||
### Customizing Label Position
|
||||
|
||||
Use parts to customize the label's 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"></sl-input><br />
|
||||
<sl-input class="label-on-left" label="Email" type="email"></sl-input>
|
||||
<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: flex;
|
||||
display: grid;
|
||||
grid: auto / var(--label-width) 1fr;
|
||||
gap: var(--sl-spacing-3x-small) var(--gap-width);
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.label-on-left::part(form-control-label) {
|
||||
flex: 0 0 auto;
|
||||
width: 60px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.label-on-left::part(form-control-input) {
|
||||
flex: 1 1 auto;
|
||||
.label-on-left::part(form-control-help-text) {
|
||||
grid-column-start: 2;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
|
||||
[component-header:sl-menu-item]
|
||||
|
||||
Menu items provide options for the user to pick from in a menu.
|
||||
|
||||
```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 checked>Checked</sl-menu-item>
|
||||
<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>
|
||||
@@ -33,7 +31,9 @@ const App = () => (
|
||||
<SlMenuItem>Option 2</SlMenuItem>
|
||||
<SlMenuItem>Option 3</SlMenuItem>
|
||||
<SlDivider />
|
||||
<SlMenuItem checked>Checked</SlMenuItem>
|
||||
<SlMenuItem type="checkbox" checked>
|
||||
Checkbox
|
||||
</SlMenuItem>
|
||||
<SlMenuItem disabled>Disabled</SlMenuItem>
|
||||
<SlDivider />
|
||||
<SlMenuItem>
|
||||
@@ -50,30 +50,6 @@ const App = () => (
|
||||
|
||||
## Examples
|
||||
|
||||
### Checked
|
||||
|
||||
Use the `checked` attribute to draw menu items in a checked state.
|
||||
|
||||
```html preview
|
||||
<sl-menu style="max-width: 200px;">
|
||||
<sl-menu-item>Option 1</sl-menu-item>
|
||||
<sl-menu-item checked>Option 2</sl-menu-item>
|
||||
<sl-menu-item>Option 3</sl-menu-item>
|
||||
</sl-menu>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlMenu style={{ maxWidth: '200px' }}>
|
||||
<SlMenuItem>Option 1</SlMenuItem>
|
||||
<SlMenuItem checked>Option 2</SlMenuItem>
|
||||
<SlMenuItem>Option 3</SlMenuItem>
|
||||
</SlMenu>
|
||||
);
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
Add the `disabled` attribute to disable the menu item so it cannot be selected.
|
||||
@@ -152,6 +128,34 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### 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>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlMenu, SlMenuItem } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
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>
|
||||
);
|
||||
```
|
||||
|
||||
### 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.
|
||||
@@ -161,6 +165,10 @@ The `value` attribute can be used to assign a hidden value, such as a unique ide
|
||||
<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>
|
||||
@@ -169,11 +177,12 @@ The `value` attribute can be used to assign a hidden value, such as a unique ide
|
||||
menu.addEventListener('sl-select', event => {
|
||||
const item = event.detail.item;
|
||||
|
||||
// Toggle checked state
|
||||
item.checked = !item.checked;
|
||||
|
||||
// Log value
|
||||
console.log(`Selected value: ${item.value}`);
|
||||
if (item.type === 'checkbox') {
|
||||
console.log(`Selected value: ${item.value} (${item.checked ? 'checked' : 'unchecked'})`);
|
||||
} else {
|
||||
console.log(`Selected value: ${item.value}`);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-menu-label]
|
||||
|
||||
Menu labels are used to describe a group of menu items.
|
||||
|
||||
```html preview
|
||||
<sl-menu style="max-width: 200px;">
|
||||
<sl-menu-label>Fruits</sl-menu-label>
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-menu]
|
||||
|
||||
Menus provide a list of options for the user to choose from.
|
||||
|
||||
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
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-mutation-observer]
|
||||
|
||||
The Mutation Observer component offers a thin, declarative interface to the [`MutationObserver API`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).
|
||||
|
||||
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
|
||||
|
||||
79
docs/components/option.md
Normal file
79
docs/components/option.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# Option
|
||||
|
||||
[component-header:sl-option]
|
||||
|
||||
```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, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
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, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
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>
|
||||
```
|
||||
|
||||
[component-metadata:sl-option]
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
[component-header:sl-popup]
|
||||
|
||||
Popup is a utility that lets you declaratively anchor "popup" containers to another element.
|
||||
|
||||
This component's name is inspired by [`<popup>`](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Popup/explainer.md). It uses [Floating UI](https://floating-ui.com/) under the hood to provide a well-tested, lightweight, and fully declarative positioning utility for tooltips, dropdowns, and more.
|
||||
|
||||
Popup doesn't provide any styles — just positioning! The popup's preferred placement, distance, and skidding (offset) can be configured using attributes. An arrow that points to the anchor can be shown and customized to your liking. Additional positioning options are available and described in more detail below.
|
||||
|
||||
!> Popup is a low-level utility built specifically for positioning elements. Do not mistake it for a [tooltip](/components/tooltip) or similar because _it does not facilitate an accessible experience!_ Almost every correct usage of `<sl-popup>` will involve building other components. It should rarely, if ever, occur directly in your HTML.
|
||||
|
||||
```html preview
|
||||
<div class="popup-overview">
|
||||
<sl-popup placement="top" active>
|
||||
@@ -17,18 +17,18 @@ Popup doesn't provide any styles — just positioning! The popup's preferred pla
|
||||
|
||||
<div class="popup-overview-options">
|
||||
<sl-select label="Placement" name="placement" value="top" class="popup-overview-select">
|
||||
<sl-menu-item value="top">top</sl-menu-item>
|
||||
<sl-menu-item value="top-start">top-start</sl-menu-item>
|
||||
<sl-menu-item value="top-end">top-end</sl-menu-item>
|
||||
<sl-menu-item value="bottom">bottom</sl-menu-item>
|
||||
<sl-menu-item value="bottom-start">bottom-start</sl-menu-item>
|
||||
<sl-menu-item value="bottom-end">bottom-end</sl-menu-item>
|
||||
<sl-menu-item value="right">right</sl-menu-item>
|
||||
<sl-menu-item value="right-start">right-start</sl-menu-item>
|
||||
<sl-menu-item value="right-end">right-end</sl-menu-item>
|
||||
<sl-menu-item value="left">left</sl-menu-item>
|
||||
<sl-menu-item value="left-start">left-start</sl-menu-item>
|
||||
<sl-menu-item value="left-end">left-end</sl-menu-item>
|
||||
<sl-option value="top">top</sl-option>
|
||||
<sl-option value="top-start">top-start</sl-option>
|
||||
<sl-option value="top-end">top-end</sl-option>
|
||||
<sl-option value="bottom">bottom</sl-option>
|
||||
<sl-option value="bottom-start">bottom-start</sl-option>
|
||||
<sl-option value="bottom-end">bottom-end</sl-option>
|
||||
<sl-option value="right">right</sl-option>
|
||||
<sl-option value="right-start">right-start</sl-option>
|
||||
<sl-option value="right-end">right-end</sl-option>
|
||||
<sl-option value="left">left</sl-option>
|
||||
<sl-option value="left-start">left-start</sl-option>
|
||||
<sl-option value="left-end">left-end</sl-option>
|
||||
</sl-select>
|
||||
<sl-input type="number" name="distance" label="distance" value="0"></sl-input>
|
||||
<sl-input type="number" name="skidding" label="Skidding" value="0"></sl-input>
|
||||
@@ -382,18 +382,18 @@ Since placement is preferred when using `flip`, you can observe the popup's curr
|
||||
</sl-popup>
|
||||
|
||||
<sl-select label="Placement" value="top">
|
||||
<sl-menu-item value="top">top</sl-menu-item>
|
||||
<sl-menu-item value="top-start">top-start</sl-menu-item>
|
||||
<sl-menu-item value="top-end">top-end</sl-menu-item>
|
||||
<sl-menu-item value="bottom">bottom</sl-menu-item>
|
||||
<sl-menu-item value="bottom-start">bottom-start</sl-menu-item>
|
||||
<sl-menu-item value="bottom-end">bottom-end</sl-menu-item>
|
||||
<sl-menu-item value="right">right</sl-menu-item>
|
||||
<sl-menu-item value="right-start">right-start</sl-menu-item>
|
||||
<sl-menu-item value="right-end">right-end</sl-menu-item>
|
||||
<sl-menu-item value="left">left</sl-menu-item>
|
||||
<sl-menu-item value="left-start">left-start</sl-menu-item>
|
||||
<sl-menu-item value="left-end">left-end</sl-menu-item>
|
||||
<sl-option value="top">top</sl-option>
|
||||
<sl-option value="top-start">top-start</sl-option>
|
||||
<sl-option value="top-end">top-end</sl-option>
|
||||
<sl-option value="bottom">bottom</sl-option>
|
||||
<sl-option value="bottom-start">bottom-start</sl-option>
|
||||
<sl-option value="bottom-end">bottom-end</sl-option>
|
||||
<sl-option value="right">right</sl-option>
|
||||
<sl-option value="right-start">right-start</sl-option>
|
||||
<sl-option value="right-end">right-end</sl-option>
|
||||
<sl-option value="left">left</sl-option>
|
||||
<sl-option value="left-start">left-start</sl-option>
|
||||
<sl-option value="left-end">left-end</sl-option>
|
||||
</sl-select>
|
||||
</div>
|
||||
|
||||
@@ -525,7 +525,7 @@ Use the `distance` attribute to change the distance between the popup and its an
|
||||
const popup = container.querySelector('sl-popup');
|
||||
const distance = container.querySelector('sl-range');
|
||||
|
||||
distance.addEventListener('sl-change', () => (popup.distance = distance.value));
|
||||
distance.addEventListener('sl-input', () => (popup.distance = distance.value));
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -621,7 +621,7 @@ The `skidding` attribute is similar to `distance`, but instead allows you to off
|
||||
const popup = container.querySelector('sl-popup');
|
||||
const skidding = container.querySelector('sl-range');
|
||||
|
||||
skidding.addEventListener('sl-change', () => (popup.skidding = skidding.value));
|
||||
skidding.addEventListener('sl-input', () => (popup.skidding = skidding.value));
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -692,25 +692,25 @@ By default, the arrow will be aligned as close to the center of the _anchor_ as
|
||||
|
||||
<div class="popup-arrow-options">
|
||||
<sl-select label="Placement" name="placement" value="top" class="popup-overview-select">
|
||||
<sl-menu-item value="top">top</sl-menu-item>
|
||||
<sl-menu-item value="top-start">top-start</sl-menu-item>
|
||||
<sl-menu-item value="top-end">top-end</sl-menu-item>
|
||||
<sl-menu-item value="bottom">bottom</sl-menu-item>
|
||||
<sl-menu-item value="bottom-start">bottom-start</sl-menu-item>
|
||||
<sl-menu-item value="bottom-end">bottom-end</sl-menu-item>
|
||||
<sl-menu-item value="right">right</sl-menu-item>
|
||||
<sl-menu-item value="right-start">right-start</sl-menu-item>
|
||||
<sl-menu-item value="right-end">right-end</sl-menu-item>
|
||||
<sl-menu-item value="left">left</sl-menu-item>
|
||||
<sl-menu-item value="left-start">left-start</sl-menu-item>
|
||||
<sl-menu-item value="left-end">left-end</sl-menu-item>
|
||||
<sl-option value="top">top</sl-option>
|
||||
<sl-option value="top-start">top-start</sl-option>
|
||||
<sl-option value="top-end">top-end</sl-option>
|
||||
<sl-option value="bottom">bottom</sl-option>
|
||||
<sl-option value="bottom-start">bottom-start</sl-option>
|
||||
<sl-option value="bottom-end">bottom-end</sl-option>
|
||||
<sl-option value="right">right</sl-option>
|
||||
<sl-option value="right-start">right-start</sl-option>
|
||||
<sl-option value="right-end">right-end</sl-option>
|
||||
<sl-option value="left">left</sl-option>
|
||||
<sl-option value="left-start">left-start</sl-option>
|
||||
<sl-option value="left-end">left-end</sl-option>
|
||||
</sl-select>
|
||||
|
||||
<sl-select label="Arrow Placement" name="arrow-placement" value="anchor">
|
||||
<sl-menu-item value="anchor">anchor</sl-menu-item>
|
||||
<sl-menu-item value="start">start</sl-menu-item>
|
||||
<sl-menu-item value="end">end</sl-menu-item>
|
||||
<sl-menu-item value="center">center</sl-menu-item>
|
||||
<sl-option value="anchor">anchor</sl-option>
|
||||
<sl-option value="start">start</sl-option>
|
||||
<sl-option value="end">end</sl-option>
|
||||
<sl-option value="center">center</sl-option>
|
||||
</sl-select>
|
||||
</div>
|
||||
|
||||
@@ -881,10 +881,10 @@ Use the `sync` attribute to make the popup the same width or height as the ancho
|
||||
</sl-popup>
|
||||
|
||||
<sl-select value="width" label="Sync">
|
||||
<sl-menu-item value="width">Width</sl-menu-item>
|
||||
<sl-menu-item value="height">Height</sl-menu-item>
|
||||
<sl-menu-item value="both">Both</sl-menu-item>
|
||||
<sl-menu-item value="">None</sl-menu-item>
|
||||
<sl-option value="width">Width</sl-option>
|
||||
<sl-option value="height">Height</sl-option>
|
||||
<sl-option value="both">Both</sl-option>
|
||||
<sl-option value="">None</sl-option>
|
||||
</sl-select>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-progress-bar]
|
||||
|
||||
Progress bars are used to show the status of an ongoing operation.
|
||||
|
||||
```html preview
|
||||
<sl-progress-bar value="50"></sl-progress-bar>
|
||||
```
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-progress-ring]
|
||||
|
||||
Progress rings are used to show the progress of a determinate operation in a circular fashion.
|
||||
|
||||
```html preview
|
||||
<sl-progress-ring value="25"></sl-progress-ring>
|
||||
```
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-qr-code]
|
||||
|
||||
Generates a [QR code](https://www.qrcode.com/) and renders it using the [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API).
|
||||
|
||||
QR codes are useful for providing small pieces of information to users who can quickly scan them with a smartphone. Most smartphones have built-in QR code scanners, so simply pointing the camera at a QR code will decode it and allow the user to visit a website, dial a phone number, read a message, etc.
|
||||
|
||||
```html preview
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-radio-button]
|
||||
|
||||
Radios buttons allow the user to select a single option from a group using a button-like control.
|
||||
|
||||
Radio buttons are designed to be used with [radio groups](/components/radio-group). When a radio button has focus, the arrow keys can be used to change the selected option just like standard radio controls.
|
||||
|
||||
```html preview
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-radio-group]
|
||||
|
||||
Radio groups are used to group multiple [radios](/components/radio) or [radio buttons](/components/radio-button) so they function as a single form control.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option" name="a" value="1">
|
||||
<sl-radio value="1">Option 1</sl-radio>
|
||||
@@ -26,12 +24,12 @@ const App = () => (
|
||||
|
||||
## Examples
|
||||
|
||||
### Showing the Label
|
||||
### Help Text
|
||||
|
||||
You can show the fieldset and legend that wraps the radio group using the `fieldset` attribute. If you don't use this option, you should still provide a label so screen readers announce the control correctly.
|
||||
Add descriptive help text to a radio group with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option" name="a" value="1" fieldset>
|
||||
<sl-radio-group label="Select an option" help-text="Choose the most appropriate option." name="a" value="1">
|
||||
<sl-radio value="1">Option 1</sl-radio>
|
||||
<sl-radio value="2">Option 2</sl-radio>
|
||||
<sl-radio value="3">Option 3</sl-radio>
|
||||
@@ -42,7 +40,7 @@ You can show the fieldset and legend that wraps the radio group using the `field
|
||||
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRadioGroup label="Select an option" name="a" value="1" fieldset>
|
||||
<SlRadioGroup label="Select an option" help-text="Choose the most appropriate option." name="a" value="1">
|
||||
<SlRadio value="1">Option 1</SlRadio>
|
||||
<SlRadio value="2">Option 2</SlRadio>
|
||||
<SlRadio value="3">Option 3</SlRadio>
|
||||
@@ -55,7 +53,7 @@ const App = () => (
|
||||
[Radio buttons](/components/radio-button) offer an alternate way to display radio controls. In this case, an internal [button group](/components/button-group) is used to group the buttons into a single, cohesive control.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option" name="a" value="1">
|
||||
<sl-radio-group label="Select an option" help-text="Select an option that makes you proud." name="a" value="1">
|
||||
<sl-radio-button value="1">Option 1</sl-radio-button>
|
||||
<sl-radio-button value="2">Option 2</sl-radio-button>
|
||||
<sl-radio-button value="3">Option 3</sl-radio-button>
|
||||
@@ -156,7 +154,7 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
#### Custom Validity
|
||||
### 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.
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-radio]
|
||||
|
||||
Radios allow the user to select a single option from a group.
|
||||
|
||||
Radios are designed to be used with [radio groups](/components/radio-group).
|
||||
|
||||
```html preview
|
||||
@@ -80,4 +78,28 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
## Sizes
|
||||
|
||||
Use the `size` attribute to change a radio's size.
|
||||
|
||||
```html preview
|
||||
<sl-radio size="small">Small</sl-radio>
|
||||
<sl-radio size="medium">Medium</sl-radio>
|
||||
<sl-radio size="large">Large</sl-radio>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRadio } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlRadio size="small">Small</SlRadio>
|
||||
<br />
|
||||
<SlRadio size="medium">Medium</SlRadio>
|
||||
<br />
|
||||
<SlRadio size="large">Large</SlRadio>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-radio]
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-range]
|
||||
|
||||
Ranges allow the user to select a single value within a given range using a slider.
|
||||
|
||||
```html preview
|
||||
<sl-range></sl-range>
|
||||
```
|
||||
@@ -23,7 +21,7 @@ const App = () => <SlRange />;
|
||||
Use the `label` attribute to give the range an accessible label. For labels that contain HTML, use the `label` slot instead.
|
||||
|
||||
```html preview
|
||||
<sl-range label="Volume" min="0" max="100"></sl-input>
|
||||
<sl-range label="Volume" min="0" max="100"></sl-range>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
@@ -37,12 +35,7 @@ const App = () => <SlRange label="Volume" min={0} max={100} />;
|
||||
Add descriptive help text to a range with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.
|
||||
|
||||
```html preview
|
||||
<sl-range
|
||||
label="Volume"
|
||||
help-text="Controls the volume of the current song."
|
||||
min="0"
|
||||
max="100"
|
||||
></sl-input>
|
||||
<sl-range label="Volume" help-text="Controls the volume of the current song." min="0" max="100"></sl-range>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-rating]
|
||||
|
||||
Ratings give users a way to quickly view and provide feedback.
|
||||
|
||||
```html preview
|
||||
<sl-rating label="Rating"></sl-rating>
|
||||
```
|
||||
@@ -58,7 +56,7 @@ import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
const App = () => <SlRating label="Rating" precision={0.5} value={2.5} />;
|
||||
```
|
||||
|
||||
## Symbol Sizes
|
||||
### Symbol Sizes
|
||||
|
||||
Set the `--symbol-size` custom property to adjust the size.
|
||||
|
||||
@@ -100,6 +98,99 @@ import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
const App = () => <SlRating label="Rating" disabled value={3} />;
|
||||
```
|
||||
|
||||
### Detecting Hover
|
||||
|
||||
Use the `sl-hover` event to detect when the user hovers over (or touch and drag) the rating. This lets you hook into values as the user interacts with the rating, but before they select a value.
|
||||
|
||||
The event has a payload with `phase` and `value` properties. The `phase` property tells when hovering starts, moves to a new value, and ends. The `value` property tells what the rating's value would be if the user were to commit to the hovered value.
|
||||
|
||||
```html preview
|
||||
<div class="detect-hover">
|
||||
<sl-rating label="Rating"></sl-rating>
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const rating = document.querySelector('.detect-hover > sl-rating');
|
||||
const span = rating.nextElementSibling;
|
||||
const terms = ['No rating', 'Terrible', 'Bad', 'OK', 'Good', 'Excellent'];
|
||||
|
||||
rating.addEventListener('sl-hover', event => {
|
||||
span.textContent = terms[event.detail.value];
|
||||
|
||||
// Clear feedback when hovering stops
|
||||
if (event.detail.phase === 'end') {
|
||||
span.textContent = '';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.detect-hover span {
|
||||
position: relative;
|
||||
top: -4px;
|
||||
left: 8px;
|
||||
border-radius: var(--sl-border-radius-small);
|
||||
background: var(--sl-color-neutral-900);
|
||||
color: var(--sl-color-neutral-0);
|
||||
text-align: center;
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
.detect-hover span:empty {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { useState } from 'react';
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const terms = ['No rating', 'Terrible', 'Bad', 'OK', 'Good', 'Excellent'];
|
||||
const css = `
|
||||
.detect-hover span {
|
||||
position: relative;
|
||||
top: -4px;
|
||||
left: 8px;
|
||||
border-radius: var(--sl-border-radius-small);
|
||||
background: var(--sl-color-neutral-900);
|
||||
color: var(--sl-color-neutral-0);
|
||||
text-align: center;
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
.detect-hover span:empty {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
function handleHover(event) {
|
||||
rating.addEventListener('sl-hover', event => {
|
||||
setFeedback(terms[event.detail.value]);
|
||||
|
||||
// Clear feedback when hovering stops
|
||||
if (event.detail.phase === 'end') {
|
||||
setFeedback('');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
const [feedback, setFeedback] = useState(true);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="detect-hover">
|
||||
<SlRating label="Rating" onSlHover={handleHover} />
|
||||
<span>{feedback}</span>
|
||||
</div>
|
||||
<style>{css}</style>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Custom Icons
|
||||
|
||||
You can provide custom icons by passing a function to the `getSymbol` property.
|
||||
@@ -114,7 +205,6 @@ You can provide custom icons by passing a function to the `getSymbol` property.
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import '@shoelace-style/shoelace/dist/components/icon/icon';
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
@@ -144,7 +234,6 @@ You can also use the `getSymbol` property to render different icons based on val
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import '@shoelace-style/shoelace/dist/components/icon/icon';
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
function getSymbol(value) {
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-relative-time]
|
||||
|
||||
Outputs a localized time phrase relative to the current date and time.
|
||||
|
||||
Localization is handled by the browser's [`Intl.RelativeTimeFormat` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat). No language packs are required.
|
||||
|
||||
```html preview
|
||||
@@ -21,8 +19,6 @@ The `date` attribute determines when the date/time is calculated from. It must b
|
||||
|
||||
?> When using strings, avoid ambiguous dates such as `03/04/2020` which can be interpreted as March 4 or April 3 depending on the user's browser and locale. Instead, always use a valid [ISO 8601 date time string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse#Date_Time_String_Format) to ensure the date will be parsed properly by all clients.
|
||||
|
||||
!> The `Intl.RelativeTimeFormat` API is available [in all major browsers](https://caniuse.com/mdn-javascript_builtins_intl_relativetimeformat), but it only became available to Safari in version 14. If you need to support Safari 13, you'll need to [use a polyfill](https://github.com/catamphetamine/relative-time-format).
|
||||
|
||||
## Examples
|
||||
|
||||
### Keeping Time in Sync
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-resize-observer]
|
||||
|
||||
The Resize Observer component offers a thin, declarative interface to the [`ResizeObserver API`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver).
|
||||
|
||||
The resize observer will report changes to the dimensions of the elements it wraps through the `sl-resize` event. When emitted, a collection of [`ResizeObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry) objects will be attached to `event.detail` that contains the target element and information about its dimensions.
|
||||
|
||||
```html preview
|
||||
|
||||
@@ -2,32 +2,28 @@
|
||||
|
||||
[component-header:sl-select]
|
||||
|
||||
Selects allow you to choose one or more items from a dropdown menu.
|
||||
|
||||
```html preview
|
||||
<sl-select>
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
<sl-divider></sl-divider>
|
||||
<sl-menu-item value="option-4">Option 4</sl-menu-item>
|
||||
<sl-menu-item value="option-5">Option 5</sl-menu-item>
|
||||
<sl-menu-item value="option-6">Option 6</sl-menu-item>
|
||||
<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-option value="option-4">Option 4</sl-option>
|
||||
<sl-option value="option-5">Option 5</sl-option>
|
||||
<sl-option value="option-6">Option 6</sl-option>
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlDivider, SlMenuItem, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSelect>
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
<SlDivider />
|
||||
<SlMenuItem value="option-4">Option 4</SlMenuItem>
|
||||
<SlMenuItem value="option-5">Option 5</SlMenuItem>
|
||||
<SlMenuItem value="option-6">Option 6</SlMenuItem>
|
||||
<SlOption value="option-1">Option 1</SlOption>
|
||||
<SlOption value="option-2">Option 2</SlOption>
|
||||
<SlOption value="option-3">Option 3</SlOption>
|
||||
<SlOption value="option-4">Option 4</SlOption>
|
||||
<SlOption value="option-5">Option 5</SlOption>
|
||||
<SlOption value="option-6">Option 6</SlOption>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
@@ -42,20 +38,20 @@ Use the `label` attribute to give the select an accessible label. For labels tha
|
||||
|
||||
```html preview
|
||||
<sl-select label="Select one">
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
<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 { SlMenuItem, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSelect label="Select one">
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
<SlOption value="option-1">Option 1</SlOption>
|
||||
<SlOption value="option-2">Option 2</SlOption>
|
||||
<SlOption value="option-3">Option 3</SlOption>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
@@ -66,20 +62,20 @@ Add descriptive help text to a select with the `help-text` attribute. For help t
|
||||
|
||||
```html preview
|
||||
<sl-select label="Experience" help-text="Please tell us your skill level.">
|
||||
<sl-menu-item value="1">Novice</sl-menu-item>
|
||||
<sl-menu-item value="2">Intermediate</sl-menu-item>
|
||||
<sl-menu-item value="3">Advanced</sl-menu-item>
|
||||
<sl-option value="1">Novice</sl-option>
|
||||
<sl-option value="2">Intermediate</sl-option>
|
||||
<sl-option value="3">Advanced</sl-option>
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlMenuItem, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSelect label="Experience" help-text="Please tell us your skill level.">
|
||||
<SlMenuItem value="1">Novice</SlMenuItem>
|
||||
<SlMenuItem value="2">Intermediate</SlMenuItem>
|
||||
<SlMenuItem value="3">Advanced</SlMenuItem>
|
||||
<SlOption value="1">Novice</SlOption>
|
||||
<SlOption value="2">Intermediate</SlOption>
|
||||
<SlOption value="3">Advanced</SlOption>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
@@ -90,44 +86,44 @@ Use the `placeholder` attribute to add a placeholder.
|
||||
|
||||
```html preview
|
||||
<sl-select placeholder="Select one">
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
<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 { SlMenuItem, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSelect placeholder="Select one">
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
<SlOption value="option-1">Option 1</SlOption>
|
||||
<SlOption value="option-2">Option 2</SlOption>
|
||||
<SlOption value="option-3">Option 3</SlOption>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
|
||||
### Clearable
|
||||
|
||||
Use the `clearable` attribute to make the control clearable.
|
||||
Use the `clearable` attribute to make the control clearable. The clear button only appears when an option is selected.
|
||||
|
||||
```html preview
|
||||
<sl-select placeholder="Clearable" clearable>
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
<sl-select clearable value="option-1">
|
||||
<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 { SlMenuItem, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSelect placeholder="Clearable" clearable>
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
<SlOption value="option-1">Option 1</SlOption>
|
||||
<SlOption value="option-2">Option 2</SlOption>
|
||||
<SlOption value="option-3">Option 3</SlOption>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
@@ -138,20 +134,20 @@ Add the `filled` attribute to draw a filled select.
|
||||
|
||||
```html preview
|
||||
<sl-select filled>
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
<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 { SlMenuItem, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSelect filled>
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
<SlOption value="option-1">Option 1</SlOption>
|
||||
<SlOption value="option-2">Option 2</SlOption>
|
||||
<SlOption value="option-3">Option 3</SlOption>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
@@ -162,20 +158,20 @@ Use the `pill` attribute to give selects rounded edges.
|
||||
|
||||
```html preview
|
||||
<sl-select pill>
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
<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 { SlMenuItem, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSelect pill>
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
<SlOption value="option-1">Option 1</SlOption>
|
||||
<SlOption value="option-2">Option 2</SlOption>
|
||||
<SlOption value="option-3">Option 3</SlOption>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
@@ -186,227 +182,167 @@ Use the `disabled` attribute to disable a select.
|
||||
|
||||
```html preview
|
||||
<sl-select placeholder="Disabled" disabled>
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
<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 { SlMenuItem, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSelect placeholder="Disabled" disabled>
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
<SlOption value="option-1">Option 1</SlOption>
|
||||
<SlOption value="option-2">Option 2</SlOption>
|
||||
<SlOption value="option-3">Option 3</SlOption>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
|
||||
### Setting the Selection
|
||||
|
||||
Use the `value` attribute to set the current selection. When users interact with the control, its `value` will update to reflect the newly selected menu item's value. Note that the value must be an array when using the [`multiple`](#multiple) option.
|
||||
|
||||
```html preview
|
||||
<sl-select value="option-2">
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlDivider, SlMenuItem, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSelect value="option-2">
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
|
||||
### Setting the Selection Imperatively
|
||||
|
||||
To programmatically set the selection, update the `value` property as shown below. Note that the value must be an array when using the [`multiple`](#multiple) option.
|
||||
|
||||
```html preview
|
||||
<div class="selecting-example">
|
||||
<sl-select>
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
</sl-select>
|
||||
|
||||
<br />
|
||||
|
||||
<sl-button data-option="option-1">Set 1</sl-button>
|
||||
<sl-button data-option="option-2">Set 2</sl-button>
|
||||
<sl-button data-option="option-3">Set 3</sl-button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const container = document.querySelector('.selecting-example');
|
||||
const select = container.querySelector('sl-select');
|
||||
|
||||
[...container.querySelectorAll('sl-button')].map(button => {
|
||||
button.addEventListener('click', () => {
|
||||
select.value = button.dataset.option;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlMenuItem, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => {
|
||||
const [value, setValue] = useState('option-1');
|
||||
|
||||
return (
|
||||
<>
|
||||
<SlSelect value={value} onSlChange={event => setValue(event.target.value)}>
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
</SlSelect>
|
||||
|
||||
<br />
|
||||
|
||||
<SlButton onClick={() => setValue('option-1')}>Set 1</SlButton>
|
||||
<SlButton onClick={() => setValue('option-2')}>Set 2</SlButton>
|
||||
<SlButton onClick={() => setValue('option-3')}>Set 3</SlButton>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Multiple
|
||||
|
||||
To allow multiple options to be selected, use the `multiple` attribute. With this option, `value` will be an array of strings instead of a string. It's a good practice to use `clearable` when this option is enabled.
|
||||
To allow multiple options to be selected, use the `multiple` attribute. It's a good practice to use `clearable` when this option is enabled. To set multiple values at once, set `value` to a space-delimited list of values.
|
||||
|
||||
```html preview
|
||||
<sl-select placeholder="Select a few" multiple clearable>
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
<sl-divider></sl-divider>
|
||||
<sl-menu-item value="option-4">Option 4</sl-menu-item>
|
||||
<sl-menu-item value="option-5">Option 5</sl-menu-item>
|
||||
<sl-menu-item value="option-6">Option 6</sl-menu-item>
|
||||
<sl-select label="Select a Few" value="option-1 option-2 option-3" multiple clearable>
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2">Option 2</sl-option>
|
||||
<sl-option value="option-3">Option 3</sl-option>
|
||||
<sl-option value="option-4">Option 4</sl-option>
|
||||
<sl-option value="option-5">Option 5</sl-option>
|
||||
<sl-option value="option-6">Option 6</sl-option>
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlDivider, SlMenuItem, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSelect placeholder="Select a few" multiple clearable>
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
<SlDivider />
|
||||
<SlMenuItem value="option-4">Option 4</SlMenuItem>
|
||||
<SlMenuItem value="option-5">Option 5</SlMenuItem>
|
||||
<SlMenuItem value="option-6">Option 6</SlMenuItem>
|
||||
<SlSelect label="Select a Few" value="option-1 option-2 option-3" multiple clearable>
|
||||
<SlOption value="option-1">Option 1</SlOption>
|
||||
<SlOption value="option-2">Option 2</SlOption>
|
||||
<SlOption value="option-3">Option 3</SlOption>
|
||||
<SlOption value="option-4">Option 4</SlOption>
|
||||
<SlOption value="option-5">Option 5</SlOption>
|
||||
<SlOption value="option-6">Option 6</SlOption>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
|
||||
?> When using the `multiple` option, the value will be an array instead of a string. You may need to [set the selection imperatively](#setting-the-selection-imperatively) unless you're using a framework that supports binding properties declaratively.
|
||||
?> Note that multi-select options may wrap, causing the control to expand vertically. You can use the `max-options-visible` attribute to control the maximum number of selected options to show at once.
|
||||
|
||||
### Grouping Options
|
||||
### Setting Initial Values
|
||||
|
||||
Options can be grouped visually using menu labels and dividers.
|
||||
Use the `value` attribute to set the initial selection. When using `multiple`, use space-delimited values to select more than one option.
|
||||
|
||||
```html preview
|
||||
<sl-select placeholder="Select one">
|
||||
<sl-menu-label>Group 1</sl-menu-label>
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
<sl-divider></sl-divider>
|
||||
<sl-menu-label>Group 2</sl-menu-label>
|
||||
<sl-menu-item value="option-4">Option 4</sl-menu-item>
|
||||
<sl-menu-item value="option-5">Option 5</sl-menu-item>
|
||||
<sl-menu-item value="option-6">Option 6</sl-menu-item>
|
||||
<sl-select value="option-1 option-2" multiple clearable>
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2">Option 2</sl-option>
|
||||
<sl-option value="option-3">Option 3</sl-option>
|
||||
<sl-option value="option-4">Option 4</sl-option>
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlDivider, SlMenuItem, SlMenuLabel, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
import { SlDivider, SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSelect placeholder="Select one">
|
||||
<SlMenuLabel>Group 1</SlMenuLabel>
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
<SlDivider></SlDivider>
|
||||
<SlMenuLabel>Group 2</SlMenuLabel>
|
||||
<SlMenuItem value="option-4">Option 4</SlMenuItem>
|
||||
<SlMenuItem value="option-5">Option 5</SlMenuItem>
|
||||
<SlMenuItem value="option-6">Option 6</SlMenuItem>
|
||||
<SlSelect value="option-1 option-2" multiple clearable>
|
||||
<SlOption value="option-1">Option 1</SlOption>
|
||||
<SlOption value="option-2">Option 2</SlOption>
|
||||
<SlOption value="option-3">Option 3</SlOption>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
|
||||
### Grouping Options
|
||||
|
||||
Use `<sl-divider>` to group listbox items visually. You can also use `<small>` to provide labels, but they won't be announced by most assistive devices.
|
||||
|
||||
```html preview
|
||||
<sl-select>
|
||||
<small>Section 1</small>
|
||||
<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-divider></sl-divider>
|
||||
<small>Section 2</small>
|
||||
<sl-option value="option-4">Option 4</sl-option>
|
||||
<sl-option value="option-5">Option 5</sl-option>
|
||||
<sl-option value="option-6">Option 6</sl-option>
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSelect>
|
||||
<SlOption value="option-1">Option 1</SlOption>
|
||||
<SlOption value="option-2">Option 2</SlOption>
|
||||
<SlOption value="option-3">Option 3</SlOption>
|
||||
<SlOption value="option-4">Option 4</SlOption>
|
||||
<SlOption value="option-5">Option 5</SlOption>
|
||||
<SlOption value="option-6">Option 6</SlOption>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
|
||||
### Sizes
|
||||
|
||||
Use the `size` attribute to change a select's size.
|
||||
Use the `size` attribute to change a select's size. Note that size does not apply to listbox options.
|
||||
|
||||
```html preview
|
||||
<sl-select placeholder="Small" size="small" multiple>
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
<sl-select placeholder="Small" size="small">
|
||||
<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>
|
||||
|
||||
<br />
|
||||
|
||||
<sl-select placeholder="Medium" size="medium" multiple>
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
<sl-select placeholder="Medium" size="medium">
|
||||
<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>
|
||||
|
||||
<br />
|
||||
|
||||
<sl-select placeholder="Large" size="large" multiple>
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
<sl-select placeholder="Large" size="large">
|
||||
<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 { SlMenuItem, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
import { SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlSelect placeholder="Small" size="small" multiple>
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
<SlSelect placeholder="Small" size="small">
|
||||
<SlOption value="option-1">Option 1</SlOption>
|
||||
<SlOption value="option-2">Option 2</SlOption>
|
||||
<SlOption value="option-3">Option 3</SlOption>
|
||||
</SlSelect>
|
||||
|
||||
<br />
|
||||
|
||||
<SlSelect placeholder="Medium" size="medium" multiple>
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
<SlSelect placeholder="Medium" size="medium">
|
||||
<SlOption value="option-1">Option 1</SlOption>
|
||||
<SlOption value="option-2">Option 2</SlOption>
|
||||
<SlOption value="option-3">Option 3</SlOption>
|
||||
</SlSelect>
|
||||
|
||||
<br />
|
||||
|
||||
<SlSelect placeholder="Large" size="large" multiple>
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
<SlSelect placeholder="Large" size="large">
|
||||
<SlOption value="option-1">Option 1</SlOption>
|
||||
<SlOption value="option-2">Option 2</SlOption>
|
||||
<SlOption value="option-3">Option 3</SlOption>
|
||||
</SlSelect>
|
||||
</>
|
||||
);
|
||||
@@ -414,88 +350,82 @@ const App = () => (
|
||||
|
||||
### Placement
|
||||
|
||||
The preferred placement of the select's menu can be set with the `placement` attribute. Note that the actual position may vary to ensure the panel remains in the viewport. Valid placements are `top` and `bottom`.
|
||||
The preferred placement of the select's listbox can be set with the `placement` attribute. Note that the actual position may vary to ensure the panel remains in the viewport. Valid placements are `top` and `bottom`.
|
||||
|
||||
```html preview
|
||||
<sl-select placement="top">
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
<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 {
|
||||
SlMenuItem,
|
||||
SlOption,
|
||||
SlSelect
|
||||
} from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSelect placement="top">
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
<SlOption value="option-1">Option 1</SlOption>
|
||||
<SlOption value="option-2">Option 2</SlOption>
|
||||
<SlOption value="option-3">Option 3</SlOption>
|
||||
</SlDropdown>
|
||||
);
|
||||
```
|
||||
|
||||
### Prefix & Suffix Icons
|
||||
### Prefix Icons
|
||||
|
||||
Use the `prefix` and `suffix` slots to add icons.
|
||||
Use the `prefix` slot to prepend an icon to the control.
|
||||
|
||||
```html preview
|
||||
<sl-select placeholder="Small" size="small">
|
||||
<sl-select placeholder="Small" size="small" clearable>
|
||||
<sl-icon name="house" slot="prefix"></sl-icon>
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
<sl-icon name="chat" slot="suffix"></sl-icon>
|
||||
<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>
|
||||
<br />
|
||||
<sl-select placeholder="Medium" size="medium">
|
||||
<sl-select placeholder="Medium" size="medium" clearable>
|
||||
<sl-icon name="house" slot="prefix"></sl-icon>
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
<sl-icon name="chat" slot="suffix"></sl-icon>
|
||||
<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>
|
||||
<br />
|
||||
<sl-select placeholder="Large" size="large">
|
||||
<sl-select placeholder="Large" size="large" clearable>
|
||||
<sl-icon name="house" slot="prefix"></sl-icon>
|
||||
<sl-menu-item value="option-1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="option-2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="option-3">Option 3</sl-menu-item>
|
||||
<sl-icon name="chat" slot="suffix"></sl-icon>
|
||||
<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 { SlIcon, SlMenuItem, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
import { SlIcon, SlOption, SlSelect } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlSelect placeholder="Small" size="small">
|
||||
<SlIcon name="house" slot="prefix"></SlIcon>
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
<SlIcon name="chat" slot="suffix"></SlIcon>
|
||||
<SlOption value="option-1">Option 1</SlOption>
|
||||
<SlOption value="option-2">Option 2</SlOption>
|
||||
<SlOption value="option-3">Option 3</SlOption>
|
||||
</SlSelect>
|
||||
<br />
|
||||
<SlSelect placeholder="Medium" size="medium">
|
||||
<SlIcon name="house" slot="prefix"></SlIcon>
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
<SlIcon name="chat" slot="suffix"></SlIcon>
|
||||
<SlOption value="option-1">Option 1</SlOption>
|
||||
<SlOption value="option-2">Option 2</SlOption>
|
||||
<SlOption value="option-3">Option 3</SlOption>
|
||||
</SlSelect>
|
||||
<br />
|
||||
<SlSelect placeholder="Large" size="large">
|
||||
<SlIcon name="house" slot="prefix"></SlIcon>
|
||||
<SlMenuItem value="option-1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="option-2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="option-3">Option 3</SlMenuItem>
|
||||
<SlIcon name="chat" slot="suffix"></SlIcon>
|
||||
<SlOption value="option-1">Option 1</SlOption>
|
||||
<SlOption value="option-2">Option 2</SlOption>
|
||||
<SlOption value="option-3">Option 3</SlOption>
|
||||
</SlSelect>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-skeleton]
|
||||
|
||||
Skeletons are used to show where content will eventually be drawn.
|
||||
|
||||
These are simple containers for scaffolding layouts that mimic what users will see when content has finished loading. This prevents large areas of empty space during asynchronous operations.
|
||||
|
||||
Skeletons try not to be opinionated, as there are endless possibilities for designing layouts. Therefore, you'll likely use more than one skeleton to create the effect you want. If you find yourself using them frequently, consider creating a template that renders them with the desired arrangement and styles.
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-spinner]
|
||||
|
||||
Spinners are used to show the progress of an indeterminate operation.
|
||||
|
||||
```html preview
|
||||
<sl-spinner></sl-spinner>
|
||||
```
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-split-panel]
|
||||
|
||||
Split panels display two adjacent panels, allowing the user to reposition them.
|
||||
|
||||
```html preview
|
||||
<sl-split-panel>
|
||||
<div
|
||||
@@ -381,9 +379,9 @@ Try resizing the example below with each option and notice how the panels respon
|
||||
</sl-split-panel>
|
||||
|
||||
<sl-select label="Primary Panel" value="" style="max-width: 200px; margin-top: 1rem;">
|
||||
<sl-menu-item value="">None</sl-menu-item>
|
||||
<sl-menu-item value="start">Start</sl-menu-item>
|
||||
<sl-menu-item value="end">End</sl-menu-item>
|
||||
<sl-option value="">None</sl-option>
|
||||
<sl-option value="start">Start</sl-option>
|
||||
<sl-option value="end">End</sl-option>
|
||||
</sl-select>
|
||||
</div>
|
||||
|
||||
@@ -585,11 +583,11 @@ const App = () => (
|
||||
|
||||
### Customizing the Divider
|
||||
|
||||
You can target the `divider` part to apply CSS properties to the divider. To add a handle, slot an icon or another element into the `handle` slot. When customizing the divider, make sure to think about focus styles for keyboard users.
|
||||
You can target the `divider` part to apply CSS properties to the divider. To add a custom handle, slot an icon into the `divider` slot. When customizing the divider, make sure to think about focus styles for keyboard users.
|
||||
|
||||
```html preview
|
||||
<sl-split-panel style="--divider-width: 20px;">
|
||||
<sl-icon slot="handle" name="grip-vertical"></sl-icon>
|
||||
<sl-icon slot="divider" name="grip-vertical"></sl-icon>
|
||||
<div
|
||||
slot="start"
|
||||
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
|
||||
@@ -610,7 +608,7 @@ import { SlSplitPanel, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSplitPanel style={{ '--divider-width': '20px' }}>
|
||||
<SlIcon slot="handle" name="grip-vertical" />
|
||||
<SlIcon slot="divider" name="grip-vertical" />
|
||||
<div
|
||||
slot="start"
|
||||
style={{
|
||||
@@ -642,9 +640,9 @@ const App = () => (
|
||||
Here's a more elaborate example that changes the divider's color and width and adds a styled handle.
|
||||
|
||||
```html preview
|
||||
<div class="split-panel-handle">
|
||||
<div class="split-panel-divider">
|
||||
<sl-split-panel>
|
||||
<sl-icon slot="handle" name="grip-vertical"></sl-icon>
|
||||
<sl-icon slot="divider" name="grip-vertical"></sl-icon>
|
||||
<div
|
||||
slot="start"
|
||||
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;"
|
||||
@@ -661,15 +659,15 @@ Here's a more elaborate example that changes the divider's color and width and a
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.split-panel-handle sl-split-panel {
|
||||
.split-panel-divider sl-split-panel {
|
||||
--divider-width: 2px;
|
||||
}
|
||||
|
||||
.split-panel-handle sl-split-panel::part(divider) {
|
||||
.split-panel-divider sl-split-panel::part(divider) {
|
||||
background-color: var(--sl-color-pink-600);
|
||||
}
|
||||
|
||||
.split-panel-handle sl-icon {
|
||||
.split-panel-divider sl-icon {
|
||||
position: absolute;
|
||||
border-radius: var(--sl-border-radius-small);
|
||||
background: var(--sl-color-pink-600);
|
||||
@@ -677,11 +675,11 @@ Here's a more elaborate example that changes the divider's color and width and a
|
||||
padding: 0.5rem 0.125rem;
|
||||
}
|
||||
|
||||
.split-panel-handle sl-split-panel::part(divider):focus-visible {
|
||||
.split-panel-divider sl-split-panel::part(divider):focus-visible {
|
||||
background-color: var(--sl-color-primary-600);
|
||||
}
|
||||
|
||||
.split-panel-handle sl-split-panel:focus-within sl-icon {
|
||||
.split-panel-divider sl-split-panel:focus-within sl-icon {
|
||||
background-color: var(--sl-color-primary-600);
|
||||
color: var(--sl-color-neutral-0);
|
||||
}
|
||||
@@ -692,15 +690,15 @@ Here's a more elaborate example that changes the divider's color and width and a
|
||||
import { SlSplitPanel, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
.split-panel-handle sl-split-panel {
|
||||
.split-panel-divider sl-split-panel {
|
||||
--divider-width: 2px;
|
||||
}
|
||||
|
||||
.split-panel-handle sl-split-panel::part(divider) {
|
||||
.split-panel-divider sl-split-panel::part(divider) {
|
||||
background-color: var(--sl-color-pink-600);
|
||||
}
|
||||
|
||||
.split-panel-handle sl-icon {
|
||||
.split-panel-divider sl-icon {
|
||||
position: absolute;
|
||||
border-radius: var(--sl-border-radius-small);
|
||||
background: var(--sl-color-pink-600);
|
||||
@@ -708,11 +706,11 @@ const css = `
|
||||
padding: .5rem .125rem;
|
||||
}
|
||||
|
||||
.split-panel-handle sl-split-panel::part(divider):focus-visible {
|
||||
.split-panel-divider sl-split-panel::part(divider):focus-visible {
|
||||
background-color: var(--sl-color-primary-600);
|
||||
}
|
||||
|
||||
.split-panel-handle sl-split-panel:focus-within sl-icon {
|
||||
.split-panel-divider sl-split-panel:focus-within sl-icon {
|
||||
background-color: var(--sl-color-primary-600);
|
||||
color: var(--sl-color-neutral-0);
|
||||
}
|
||||
@@ -720,9 +718,9 @@ const css = `
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<div className="split-panel-handle">
|
||||
<div className="split-panel-divider">
|
||||
<SlSplitPanel>
|
||||
<SlIcon slot="handle" name="grip-vertical" />
|
||||
<SlIcon slot="divider" name="grip-vertical" />
|
||||
<div
|
||||
slot="start"
|
||||
style={{
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-switch]
|
||||
|
||||
Switches allow the user to toggle an option on or off.
|
||||
|
||||
```html preview
|
||||
<sl-switch>Switch</sl-switch>
|
||||
```
|
||||
@@ -46,12 +44,38 @@ import { SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
const App = () => <SlSwitch disabled>Disabled</SlSwitch>;
|
||||
```
|
||||
|
||||
### Custom Size
|
||||
## Sizes
|
||||
|
||||
Use the available custom properties to make the switch a different size.
|
||||
Use the `size` attribute to change a switch's size.
|
||||
|
||||
```html preview
|
||||
<sl-switch style="--width: 80px; --height: 32px; --thumb-size: 26px;">Really big</sl-switch>
|
||||
<sl-switch size="small">Small</sl-switch>
|
||||
<br />
|
||||
<sl-switch size="medium">Medium</sl-switch>
|
||||
<br />
|
||||
<sl-switch size="large">Large</sl-switch>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlSwitch size="small">Small</SlSwitch>
|
||||
<br />
|
||||
<SlSwitch size="medium">Medium</SlSwitch>
|
||||
<br />
|
||||
<SlSwitch size="large">Large</SlSwitch>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Custom Styles
|
||||
|
||||
Use the available custom properties to change how the switch is styled.
|
||||
|
||||
```html preview
|
||||
<sl-switch style="--width: 80px; --height: 40px; --thumb-size: 36px;">Really big</sl-switch>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-tab-group]
|
||||
|
||||
Tab groups organize content into a container that shows one section at a time.
|
||||
|
||||
Tab groups make use of [tabs](/components/tab) and [tab panels](/components/tab-panel). Each tab must be slotted into the `nav` slot and its `panel` must refer to a tab panel of the same name.
|
||||
|
||||
```html preview
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-tab-panel]
|
||||
|
||||
Tab panels are used inside [tab groups](/components/tab-group) to display tabbed content.
|
||||
|
||||
```html preview
|
||||
<sl-tab-group>
|
||||
<sl-tab slot="nav" panel="general">General</sl-tab>
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-tab]
|
||||
|
||||
Tabs are used inside [tab groups](/components/tab-group) to represent and activate [tab panels](/components/tab-panel).
|
||||
|
||||
```html preview
|
||||
<sl-tab>Tab</sl-tab>
|
||||
<sl-tab active>Active</sl-tab>
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-tag]
|
||||
|
||||
Tags are used as labels to organize things or to indicate a selection.
|
||||
|
||||
```html preview
|
||||
<sl-tag variant="primary">Primary</sl-tag>
|
||||
<sl-tag variant="success">Success</sl-tag>
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-textarea]
|
||||
|
||||
Textareas collect data from the user and allow multiple lines of text.
|
||||
|
||||
```html preview
|
||||
<sl-textarea></sl-textarea>
|
||||
```
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-tooltip]
|
||||
|
||||
Tooltips display additional information based on a specific action.
|
||||
|
||||
A tooltip's target is its _first child element_, so you should only wrap one element inside of the tooltip. If you need the tooltip to show up for multiple elements, nest them inside a container first.
|
||||
|
||||
Tooltips use `display: contents` so they won't interfere with how elements are positioned in a flex or grid layout.
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-tree-item]
|
||||
|
||||
A tree item serves as a hierarchical node that lives inside a [tree](/components/tree).
|
||||
|
||||
```html preview
|
||||
<sl-tree>
|
||||
<sl-tree-item>
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
[component-header:sl-tree]
|
||||
|
||||
Trees allow you to display a hierarchical list of selectable [tree items](/components/tree-item). Items with children can be expanded and collapsed as desired by the user.
|
||||
|
||||
```html preview
|
||||
<sl-tree>
|
||||
<sl-tree-item>
|
||||
@@ -81,9 +79,9 @@ The `selection` attribute lets you change the selection behavior of the tree.
|
||||
|
||||
```html preview
|
||||
<sl-select id="selection-mode" value="single" label="Selection">
|
||||
<sl-menu-item value="single">Single</sl-menu-item>
|
||||
<sl-menu-item value="multiple">Multiple</sl-menu-item>
|
||||
<sl-menu-item value="leaf">Leaf</sl-menu-item>
|
||||
<sl-option value="single">Single</sl-option>
|
||||
<sl-option value="multiple">Multiple</sl-option>
|
||||
<sl-option value="leaf">Leaf</sl-option>
|
||||
</sl-select>
|
||||
|
||||
<br />
|
||||
@@ -289,12 +287,12 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
### Custom expand/collapse icons
|
||||
### Customizing the Expand and Collapse Icons
|
||||
|
||||
Use the `expand-icon` and `collapse-icon` slots to change the expand and collapse icons, respectively.
|
||||
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 `expand-button` part as shown below.
|
||||
|
||||
```html preview
|
||||
<sl-tree>
|
||||
<sl-tree class="custom-icons">
|
||||
<sl-icon name="plus-square" slot="expand-icon"></sl-icon>
|
||||
<sl-icon name="dash-square" slot="collapse-icon"></sl-icon>
|
||||
|
||||
@@ -324,6 +322,13 @@ Use the `expand-icon` and `collapse-icon` slots to change the expand and collaps
|
||||
<sl-tree-item>Fern</sl-tree-item>
|
||||
</sl-tree-item>
|
||||
</sl-tree>
|
||||
|
||||
<style>
|
||||
.custom-icons sl-tree-item::part(expand-button) {
|
||||
/* Disable the expand/collapse animation */
|
||||
rotate: none;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
@@ -400,11 +405,11 @@ Decorative icons can be used before labels to provide hints for each node.
|
||||
</sl-tree-item>
|
||||
<sl-tree-item>
|
||||
<sl-icon name="file-pdf"></sl-icon>
|
||||
final.pdg
|
||||
final.pdf
|
||||
</sl-tree-item>
|
||||
<sl-tree-item>
|
||||
<sl-icon name="file-bar-graph"></sl-icon>
|
||||
sales.txt
|
||||
sales.xls
|
||||
</sl-tree-item>
|
||||
</sl-tree-item>
|
||||
</sl-tree-item>
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
|
||||
[component-header:sl-visually-hidden]
|
||||
|
||||
The visually hidden utility makes content accessible to assistive devices without displaying it on the screen.
|
||||
|
||||
According to [The A11Y Project](https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/), "there are real world situations where visually hiding content may be appropriate, while the content should remain available to assistive technologies, such as screen readers. For instance, hiding a search field's label as a common magnifying glass icon is used in its stead."
|
||||
|
||||
Since visually hidden content can receive focus when tabbing, the element will become visible when something inside receives focus. This behavior is intentional, as sighted keyboards user won't be able to determine where the focus indicator is without it.
|
||||
|
||||
```html preview
|
||||
<div style="min-height: 100px;">
|
||||
<div style="min-height: 1.875rem;">
|
||||
<sl-visually-hidden>
|
||||
<a href="#">Skip to main content</a>
|
||||
</sl-visually-hidden>
|
||||
@@ -32,7 +30,7 @@ In this example, the link will open a new window. Screen readers will announce "
|
||||
|
||||
### Content Conveyed By Context
|
||||
|
||||
Adding a title or label may seem redundant at times, but they're very helpful for unsighted users. Rather than omit them, you can provide context to unsighted users with visually hidden content.
|
||||
Adding a label may seem redundant at times, but they're very helpful for unsighted users. Rather than omit them, you can provide context to unsighted users with visually hidden content that will be announced by assistive devices such as screen readers.
|
||||
|
||||
```html preview
|
||||
<sl-card style="width: 100%; max-width: 360px;">
|
||||
|
||||
@@ -28,14 +28,16 @@ setBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dis
|
||||
You'll need to tell Vue to ignore Shoelace components. This is pretty easy because they all start with `sl-`.
|
||||
|
||||
```js
|
||||
import { createApp } from 'vue';
|
||||
import Vue from 'vue';
|
||||
import App from './App.vue';
|
||||
|
||||
const app = createApp(App);
|
||||
Vue.config.ignoredElements = [/sl-/];
|
||||
|
||||
app.config.compilerOptions.isCustomElement = tag => tag.startsWith('sl-');
|
||||
const app = new Vue({
|
||||
render: h => h(App)
|
||||
});
|
||||
|
||||
app.mount('#app');
|
||||
app.$mount('#app');
|
||||
```
|
||||
|
||||
Now you can start using Shoelace components in your app!
|
||||
@@ -56,31 +58,32 @@ One caveat is there's currently [no support for v-model on custom elements](http
|
||||
|
||||
```html
|
||||
<!-- This doesn't work -->
|
||||
<sl-input v-model="name">
|
||||
<!-- This works, but it's a bit longer -->
|
||||
<sl-input :value="name" @input="name = $event.target.value"></sl-input
|
||||
></sl-input>
|
||||
<sl-input v-model="name"></sl-input>
|
||||
<!-- This works, but it's a bit longer -->
|
||||
<sl-input :value="name" @input="name = $event.target.value"></sl-input>
|
||||
```
|
||||
|
||||
If that's too verbose for your liking, you can use a custom directive instead. [This utility](https://www.npmjs.com/package/@shoelace-style/vue-sl-model) adds a custom directive that will work just like `v-model` but for Shoelace components. To install it, use this command.
|
||||
|
||||
```bash
|
||||
npm install @shoelace-style/vue-sl-model
|
||||
npm install @shoelace-style/vue-sl-model@1
|
||||
```
|
||||
|
||||
Next, import the directive and enable it like this.
|
||||
|
||||
```js
|
||||
import Vue from 'vue';
|
||||
import ShoelaceModelDirective from '@shoelace-style/vue-sl-model';
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(ShoelaceModelDirective);
|
||||
Vue.use(ShoelaceModelDirective);
|
||||
Vue.config.ignoredElements = [/sl-/];
|
||||
|
||||
app.config.compilerOptions.isCustomElement = tag => tag.startsWith('sl-');
|
||||
const app = new Vue({
|
||||
render: h => h(App)
|
||||
});
|
||||
|
||||
app.mount('#app');
|
||||
app.$mount('#app');
|
||||
```
|
||||
|
||||
Now you can use the `v-sl-model` directive to keep your data in sync!
|
||||
@@ -89,4 +92,4 @@ Now you can use the `v-sl-model` directive to keep your data in sync!
|
||||
<sl-input v-sl-model="name"></sl-input>
|
||||
```
|
||||
|
||||
?> Are you using Shoelace with Vue? [Help us improve this page!](https://github.com/shoelace-style/shoelace/blob/next/docs/frameworks/vue.md)
|
||||
?> Are you using Shoelace with Vue? [Help us improve this page!](https://github.com/shoelace-style/shoelace/blob/next/docs/frameworks/vue-2.md)
|
||||
|
||||
@@ -4,13 +4,9 @@ Every Shoelace component makes use of a [shadow DOM](https://developer.mozilla.o
|
||||
|
||||
Shoelace solves this problem by using the [`formdata`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/formdata_event) event, which is [available in all modern browsers](https://caniuse.com/mdn-api_htmlformelement_formdata_event). This means, when a form is submitted, Shoelace form controls will automatically append their values to the `FormData` object that's used to submit the form. In most cases, things will "just work." However, if you're using a form serialization library, it might need to be adapted to recognize Shoelace form controls.
|
||||
|
||||
?> If you're using an older browser that doesn't support the `formdata` event, a lightweight polyfill will be automatically applied to ensure forms submit as expected.
|
||||
?> Shoelace uses event listeners to intercept the form's `formdata` and `submit` events. This allows it to inject data and trigger validation as necessary. If you're also attaching an event listener to the form, _you must attach it after Shoelace form controls are connected to the DOM_, otherwise your logic will run before Shoelace has a chance to inject form data and validate form controls.
|
||||
|
||||
## A Note About Event Handling
|
||||
|
||||
Shoelace uses event listeners to intercept the form's `formdata` and `submit` events. This allows it to inject data and trigger validation as necessary. If you're also attaching an event listener to the form, _you must attach it after Shoelace form controls are connected to the DOM_, otherwise your logic will run before Shoelace has a chance to inject form data and validate form controls.
|
||||
|
||||
## Form Serialization
|
||||
## Data Serialization
|
||||
|
||||
Serialization is just a fancy word for collecting form data. If you're relying on standard form submissions, e.g. `<form action="...">`, you can probably skip this section. However, most modern apps use the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) or a library such as [axios](https://github.com/axios/axios) to submit forms using JavaScript.
|
||||
|
||||
@@ -36,36 +32,37 @@ const data = serialize(form);
|
||||
|
||||
This results in an object with name/value pairs that map to each form control. If more than one form control shares the same name, the values will be passed as an array, e.g. `{ name: ['value1', 'value2'] }`.
|
||||
|
||||
## Form Control Validation
|
||||
## Constraint Validation
|
||||
|
||||
Client-side validation can be enabled through the browser's [Constraint Validation API](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation) for Shoelace form controls. You can activate it using attributes such as `required`, `pattern`, `minlength`, and `maxlength`. Shoelace implements many of the same attributes as native form controls, but check each form control's documentation for a list of all supported properties.
|
||||
Client-side validation can be enabled through the browser's [Constraint Validation API](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation) for Shoelace form controls. You can activate it using attributes such as `required`, `pattern`, `minlength`, `maxlength`, etc. Shoelace implements many of the same attributes as native form controls, but check the documentation for a list of supported properties for each component.
|
||||
|
||||
As the user interacts with a form control, its `invalid` attribute will reflect its validity based on its current value and the constraints that have been defined. When a form control is invalid, the containing form will not be submitted. Instead, the browser will show the user a relevant error message. If you don't want to use client-side validation, you can suppress this behavior by adding `novalidate` to the surrounding `<form>` element.
|
||||
If you don't want to use client-side validation, you can suppress this behavior by adding `novalidate` to the surrounding `<form>` element.
|
||||
|
||||
All form controls support validation, but not all validation props are available for every component. Refer to a component's documentation to see which validation props it supports.
|
||||
?> If this syntax looks unfamiliar, don't worry! Most of what you're learning on this page is platform knowledge that applies to regular form controls, too.
|
||||
|
||||
!> Client-side validation can be used to improve the UX of forms, but it is not a replacement for server-side validation. **You should always validate and sanitize user input on the server!**
|
||||
|
||||
### Required Fields
|
||||
|
||||
To make a field required, use the `required` prop. The form will not be submitted if a required form control is empty.
|
||||
To make a field required, use the `required` attribute. Required fields will automatically receive a `*` after their labels. This is configurable through the `--sl-input-required-content` custom property.
|
||||
|
||||
The form will not be submitted if a required field is incomplete.
|
||||
|
||||
```html preview
|
||||
<form class="input-validation-required">
|
||||
<sl-input name="name" label="Name" required></sl-input>
|
||||
<br />
|
||||
<sl-select label="Favorite Animal" clearable required>
|
||||
<sl-menu-item value="birds">Birds</sl-menu-item>
|
||||
<sl-menu-item value="cats">Cats</sl-menu-item>
|
||||
<sl-menu-item value="dogs">Dogs</sl-menu-item>
|
||||
<sl-menu-item value="other">Other</sl-menu-item>
|
||||
<sl-option value="birds">Birds</sl-option>
|
||||
<sl-option value="cats">Cats</sl-option>
|
||||
<sl-option value="dogs">Dogs</sl-option>
|
||||
<sl-option value="other">Other</sl-option>
|
||||
</sl-select>
|
||||
<br />
|
||||
<sl-textarea name="comment" label="Comment" required></sl-textarea>
|
||||
<br />
|
||||
<sl-checkbox required>Check me before submitting</sl-checkbox>
|
||||
<br /><br />
|
||||
<sl-button type="reset" variant="default">Reset</sl-button>
|
||||
<sl-button type="submit" variant="primary">Submit</sl-button>
|
||||
</form>
|
||||
|
||||
@@ -119,8 +116,8 @@ To restrict a value to a specific [pattern](https://developer.mozilla.org/en-US/
|
||||
<form class="input-validation-pattern">
|
||||
<sl-input name="letters" required label="Letters" pattern="[A-Za-z]+"></sl-input>
|
||||
<br />
|
||||
<sl-button type="reset" variant="default">Reset</sl-button>
|
||||
<sl-button type="submit" variant="primary">Submit</sl-button>
|
||||
<sl-button type="reset" variant="default">Reset</sl-button>
|
||||
</form>
|
||||
|
||||
<script type="module">
|
||||
@@ -159,12 +156,12 @@ Some input types will automatically trigger constraints, such as `email` and `ur
|
||||
|
||||
```html preview
|
||||
<form class="input-validation-type">
|
||||
<sl-input variant="email" label="Email" placeholder="you@example.com" required></sl-input>
|
||||
<sl-input type="email" label="Email" placeholder="you@example.com" required></sl-input>
|
||||
<br />
|
||||
<sl-input variant="url" label="URL" placeholder="https://example.com/" required></sl-input>
|
||||
<sl-input type="url" label="URL" placeholder="https://example.com/" required></sl-input>
|
||||
<br />
|
||||
<sl-button type="reset" variant="default">Reset</sl-button>
|
||||
<sl-button type="submit" variant="primary">Submit</sl-button>
|
||||
<sl-button type="reset" variant="default">Reset</sl-button>
|
||||
</form>
|
||||
|
||||
<script type="module">
|
||||
@@ -187,9 +184,9 @@ const App = () => {
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<SlInput variant="email" label="Email" placeholder="you@example.com" required />
|
||||
<SlInput type="email" label="Email" placeholder="you@example.com" required />
|
||||
<br />
|
||||
<SlInput variant="url" label="URL" placeholder="https://example.com/" required />
|
||||
<SlInput type="url" label="URL" placeholder="https://example.com/" required />
|
||||
<br />
|
||||
<SlButton type="submit" variant="primary">
|
||||
Submit
|
||||
@@ -199,16 +196,16 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
### Custom Validation
|
||||
### Custom Error Messages
|
||||
|
||||
To create a custom validation error, pass a non-empty string to the `setCustomValidity()` method. This will override any existing validation constraints. The form will not be submitted when a custom validity is set and the browser will show a validation error when the containing form is submitted. To make the input valid again, call `setCustomValidity()` again with an empty string.
|
||||
|
||||
```html preview
|
||||
<form class="input-validation-custom">
|
||||
<sl-input label="Type 'shoelace'" required></sl-input>
|
||||
<sl-input label="Type “shoelace”" required></sl-input>
|
||||
<br />
|
||||
<sl-button type="reset" variant="default">Reset</sl-button>
|
||||
<sl-button type="submit" variant="primary">Submit</sl-button>
|
||||
<sl-button type="reset" variant="default">Reset</sl-button>
|
||||
</form>
|
||||
|
||||
<script type="module">
|
||||
@@ -267,76 +264,237 @@ const App = () => {
|
||||
|
||||
?> Custom validation can be applied to any form control that supports the `setCustomValidity()` method. It is not limited to inputs and textareas.
|
||||
|
||||
### Custom Validation Styles
|
||||
## Custom Validation Styles
|
||||
|
||||
The `invalid` attribute reflects the form control's validity, so you can style invalid fields using the `[invalid]` selector. The example below demonstrates how you can give erroneous fields a different appearance. Type something other than "shoelace" to demonstrate this.
|
||||
Due to the many ways form controls are used, Shoelace doesn't provide out of the box validation styles for form controls as part of its default theme. Instead, the following attributes will be applied to reflect a control's validity as users interact with it. You can use them to create custom styles for any of the validation states you're interested in.
|
||||
|
||||
- `data-required` - the form control is required
|
||||
- `data-optional` - the form control is optional
|
||||
- `data-invalid` - the form control is currently invalid
|
||||
- `data-valid` - the form control is currently valid
|
||||
- `data-user-invalid` - the form control is currently invalid and the user has interacted with it
|
||||
- `data-user-valid` - the form control is currently valid and the user has interacted with it
|
||||
|
||||
These attributes map to the browser's built-in pseudo classes for validation: [`:required`](https://developer.mozilla.org/en-US/docs/Web/CSS/:required), [`:optional`](https://developer.mozilla.org/en-US/docs/Web/CSS/:optional), [`:invalid`](https://developer.mozilla.org/en-US/docs/Web/CSS/:invalid), [`:valid`](https://developer.mozilla.org/en-US/docs/Web/CSS/:valid), and the proposed [`:user-invalid`](https://developer.mozilla.org/en-US/docs/Web/CSS/:user-invalid) and [`:user-valid`](https://developer.mozilla.org/en-US/docs/Web/CSS/:user-valid).
|
||||
|
||||
?> In the future, data attributes will be replaced with custom pseudo classes such as `:--valid` and `:--invalid`. Shoelace is using data attributes as a workaround until browsers support custom states through [`ElementInternals.states`](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/states).
|
||||
|
||||
### Styling Invalid Form Controls
|
||||
|
||||
You can target validity using any of the aforementioned data attributes, but it's usually preferable to target `data-user-invalid` and `data-user-valid` since they get applied only after a user interaction such as typing or submitting. This prevents empty form controls from appearing invalid immediately, which often results in a poor user experience.
|
||||
|
||||
This example demonstrates custom validation styles using `data-user-invalid` and `data-user-valid`. Try Typing in the fields to see how validity changes with user input.
|
||||
|
||||
```html preview
|
||||
<sl-input class="custom-input" label="Type Something" required pattern="shoelace">
|
||||
<small slot="help-text">Please enter "shoelace" to continue</small>
|
||||
</sl-input>
|
||||
<form class="validity-styles">
|
||||
<sl-input
|
||||
name="name"
|
||||
label="Name"
|
||||
help-text="What would you like people to call you?"
|
||||
autocomplete="off"
|
||||
required
|
||||
></sl-input>
|
||||
|
||||
<style>
|
||||
.custom-input[invalid]:not([disabled])::part(label),
|
||||
.custom-input[invalid]:not([disabled])::part(help-text) {
|
||||
color: var(--sl-color-danger-600);
|
||||
}
|
||||
<sl-select name="animal" label="Favorite Animal" help-text="Select the best option." clearable required>
|
||||
<sl-option value="birds">Birds</sl-option>
|
||||
<sl-option value="cats">Cats</sl-option>
|
||||
<sl-option value="dogs">Dogs</sl-option>
|
||||
<sl-option value="other">Other</sl-option>
|
||||
</sl-select>
|
||||
|
||||
.custom-input[invalid]:not([disabled])::part(base) {
|
||||
border-color: var(--sl-color-danger-500);
|
||||
}
|
||||
<sl-checkbox value="accept" required>Accept terms and conditions</sl-checkbox>
|
||||
|
||||
.custom-input[invalid]:focus-within::part(base) {
|
||||
box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-color-danger-500);
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const css = `
|
||||
.custom-input[invalid]:not([disabled])::part(label),
|
||||
.custom-input[invalid]:not([disabled])::part(help-text) {
|
||||
color: var(--sl-color-danger-600);
|
||||
}
|
||||
|
||||
.custom-input[invalid]:not([disabled])::part(base) {
|
||||
border-color: var(--sl-color-danger-500);
|
||||
}
|
||||
|
||||
.custom-input[invalid]:focus-within::part(base) {
|
||||
box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-color-danger-500);
|
||||
}
|
||||
`;
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlInput className="custom-input" required pattern="shoelace">
|
||||
<small slot="help-text">Please enter "shoelace" to continue</small>
|
||||
</SlInput>
|
||||
|
||||
<style>{css}</style>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Third-party Validation
|
||||
|
||||
To opt out of the browser's built-in validation and use your own, add the `novalidate` attribute to the form. This will ignore all constraints and prevent the browser from showing its own warnings when form controls are invalid.
|
||||
|
||||
Remember that the `invalid` attribute on form controls reflects validity as defined by the [Constraint Validation API](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation). You can set it initially, but the `invalid` attribute will update as the user interacts with the form control. As such, you should not rely on it to set invalid styles using a custom validation library.
|
||||
|
||||
Instead, toggle a class and target it in your stylesheet as shown below.
|
||||
|
||||
```html
|
||||
<form novalidate>
|
||||
<sl-input class="invalid"></sl-input>
|
||||
<sl-button type="submit" variant="primary">Submit</sl-button>
|
||||
<sl-button type="reset" variant="default">Reset</sl-button>
|
||||
</form>
|
||||
|
||||
<script type="module">
|
||||
const form = document.querySelector('.validity-styles');
|
||||
form.addEventListener('submit', event => {
|
||||
event.preventDefault();
|
||||
alert('All fields are valid!');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
sl-input.invalid {
|
||||
...;
|
||||
.validity-styles sl-input,
|
||||
.validity-styles sl-select,
|
||||
.validity-styles sl-checkbox {
|
||||
display: block;
|
||||
margin-bottom: var(--sl-spacing-medium);
|
||||
}
|
||||
|
||||
/* user invalid styles */
|
||||
.validity-styles sl-input[data-user-invalid]::part(base),
|
||||
.validity-styles sl-select[data-user-invalid]::part(combobox),
|
||||
.validity-styles sl-checkbox[data-user-invalid]::part(control) {
|
||||
border-color: var(--sl-color-danger-600);
|
||||
}
|
||||
|
||||
.validity-styles [data-user-invalid]::part(form-control-label),
|
||||
.validity-styles [data-user-invalid]::part(form-control-help-text),
|
||||
.validity-styles sl-checkbox[data-user-invalid]::part(label) {
|
||||
color: var(--sl-color-danger-700);
|
||||
}
|
||||
|
||||
.validity-styles sl-checkbox[data-user-invalid]::part(control) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.validity-styles sl-input:focus-within[data-user-invalid]::part(base),
|
||||
.validity-styles sl-select:focus-within[data-user-invalid]::part(combobox),
|
||||
.validity-styles sl-checkbox:focus-within[data-user-invalid]::part(control) {
|
||||
border-color: var(--sl-color-danger-600);
|
||||
box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-color-danger-300);
|
||||
}
|
||||
|
||||
/* User valid styles */
|
||||
.validity-styles sl-input[data-user-valid]::part(base),
|
||||
.validity-styles sl-select[data-user-valid]::part(combobox),
|
||||
.validity-styles sl-checkbox[data-user-valid]::part(control) {
|
||||
border-color: var(--sl-color-success-600);
|
||||
}
|
||||
|
||||
.validity-styles [data-user-valid]::part(form-control-label),
|
||||
.validity-styles [data-user-valid]::part(form-control-help-text),
|
||||
.validity-styles sl-checkbox[data-user-valid]::part(label) {
|
||||
color: var(--sl-color-success-700);
|
||||
}
|
||||
|
||||
.validity-styles sl-checkbox[data-user-valid]::part(control) {
|
||||
background-color: var(--sl-color-success-600);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.validity-styles sl-input:focus-within[data-user-valid]::part(base),
|
||||
.validity-styles sl-select:focus-within[data-user-valid]::part(combobox),
|
||||
.validity-styles sl-checkbox:focus-within[data-user-valid]::part(control) {
|
||||
border-color: var(--sl-color-success-600);
|
||||
box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-color-success-300);
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## Inline Form Validation
|
||||
|
||||
By default, Shoelace form controls use the browser's tooltip-style error messages. No mechanism is provided to show errors inline, as there are too many opinions on how that would work when combined with native form controls and other custom elements. You can, however, implement your own solution using the following technique.
|
||||
|
||||
To disable the browser's error messages, you need to cancel the `sl-invalid` event. Then you can apply your own inline validation errors. This example demonstrates a primitive way to do this.
|
||||
|
||||
```html preview
|
||||
<form class="inline-validation">
|
||||
<sl-input
|
||||
name="name"
|
||||
label="Name"
|
||||
help-text="What would you like people to call you?"
|
||||
autocomplete="off"
|
||||
required
|
||||
></sl-input>
|
||||
|
||||
<div id="name-error" aria-live="polite" hidden></div>
|
||||
|
||||
<sl-button type="submit" variant="primary">Submit</sl-button>
|
||||
<sl-button type="reset" variant="default">Reset</sl-button>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
const form = document.querySelector('.inline-validation');
|
||||
const nameError = document.querySelector('#name-error');
|
||||
|
||||
// A form control is invalid
|
||||
form.addEventListener(
|
||||
'sl-invalid',
|
||||
event => {
|
||||
// Suppress the browser's constraint validation message
|
||||
event.preventDefault();
|
||||
|
||||
nameError.textContent = `Error: ${event.target.validationMessage}`;
|
||||
nameError.hidden = false;
|
||||
|
||||
event.target.focus();
|
||||
},
|
||||
{ capture: true } // you must use capture since sl-invalid doesn't bubble!
|
||||
);
|
||||
|
||||
// Handle form submit
|
||||
form.addEventListener('submit', event => {
|
||||
event.preventDefault();
|
||||
nameError.hidden = true;
|
||||
nameError.textContent = '';
|
||||
setTimeout(() => alert('All fields are valid'), 50);
|
||||
});
|
||||
|
||||
// Handle form reset
|
||||
form.addEventListener('reset', event => {
|
||||
nameError.hidden = true;
|
||||
nameError.textContent = '';
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#name-error {
|
||||
font-size: var(--sl-input-help-text-font-size-medium);
|
||||
color: var(--sl-color-danger-700);
|
||||
}
|
||||
|
||||
#name-error ~ sl-button {
|
||||
margin-top: var(--sl-spacing-medium);
|
||||
}
|
||||
|
||||
.inline-validation sl-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* user invalid styles */
|
||||
.inline-validation sl-input[data-user-invalid]::part(base) {
|
||||
border-color: var(--sl-color-danger-600);
|
||||
}
|
||||
|
||||
.inline-validation [data-user-invalid]::part(form-control-label),
|
||||
.inline-validation [data-user-invalid]::part(form-control-help-text) {
|
||||
color: var(--sl-color-danger-700);
|
||||
}
|
||||
|
||||
.inline-validation sl-input:focus-within[data-user-invalid]::part(base) {
|
||||
border-color: var(--sl-color-danger-600);
|
||||
box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-color-danger-300);
|
||||
}
|
||||
|
||||
/* User valid styles */
|
||||
.inline-validation sl-input[data-user-valid]::part(base) {
|
||||
border-color: var(--sl-color-success-600);
|
||||
}
|
||||
|
||||
.inline-validation [data-user-valid]::part(form-control-label),
|
||||
.inline-validation [data-user-valid]::part(form-control-help-text) {
|
||||
color: var(--sl-color-success-700);
|
||||
}
|
||||
|
||||
.inline-validation sl-checkbox[data-user-valid]::part(control) {
|
||||
background-color: var(--sl-color-success-600);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.inline-validation sl-input:focus-within[data-user-valid]::part(base) {
|
||||
border-color: var(--sl-color-success-600);
|
||||
box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-color-success-300);
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
!> This example is meant to demonstrate the concept of providing your own error messages inline. It is not intended to scale to more complex forms. Users who want this functionality are encouraged to build a more appropriate validation solution using the techniques shown below. Depending on how you implement this feature, custom error messages may affect the accessibility of your form controls.
|
||||
|
||||
## Getting Associated Form Controls
|
||||
|
||||
At this time, using [`HTMLFormElement.elements`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/elements) will not return Shoelace form controls because the browser is unaware of their status as custom element form controls. Fortunately, Shoelace provides an `elements()` function that does something very similar. However, instead of returning an [`HTMLFormControlsCollection`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormControlsCollection), it returns an array of HTML and Shoelace form controls in the order they appear in the DOM.
|
||||
|
||||
```js
|
||||
import { getFormControls } from '@shoelace-style/shoelace/dist/utilities/form.js';
|
||||
|
||||
const form = document.querySelector('#my-form');
|
||||
const formControls = getFormControls(form);
|
||||
|
||||
console.log(formControls); // e.g. [input, sl-input, ...]
|
||||
```
|
||||
|
||||
?> You probably don't need this function! If you're gathering form data for submission, you probably want to use [Data Serialization](#data-serializing) instead.
|
||||
|
||||
@@ -4,7 +4,7 @@ Shoelace components are just regular HTML elements, or [custom elements](https:/
|
||||
|
||||
If you're new to custom elements, often referred to as "web components," this section will familiarize you with how to use them.
|
||||
|
||||
## Properties
|
||||
## Attributes & Properties
|
||||
|
||||
Many components have properties that can be set using attributes. For example, buttons accept a `size` attribute that maps to the `size` property which dictates the button's size.
|
||||
|
||||
@@ -18,7 +18,7 @@ Some properties are boolean, so they only have true/false values. To activate a
|
||||
<sl-button disabled>Click me</sl-button>
|
||||
```
|
||||
|
||||
In rare cases, a property may require an array, an object, or a function. For example, to customize the color picker's list of preset swatches, you set the `swatches` property to an array of colors. This can be done with JavaScript.
|
||||
In rare cases, a property may require an array, an object, or a function. For example, to customize the color picker's list of preset swatches, you set the `swatches` property to an array of colors. This must be done with JavaScript.
|
||||
|
||||
```html
|
||||
<sl-color-picker></sl-color-picker>
|
||||
@@ -149,15 +149,45 @@ A clever way to use this method is to hide the `<body>` with `opacity: 0` and ad
|
||||
</script>
|
||||
```
|
||||
|
||||
## Component Rendering and Updating
|
||||
|
||||
Shoelace components are built with [Lit](https://lit.dev/), a tiny library that makes authoring custom elements easier, more maintainable, and a lot of fun! As a Shoelace user, here is some helpful information about rendering and updating you should probably be aware of.
|
||||
|
||||
To optimize performance and reduce re-renders, Lit batches component updates. This means changing multiple attributes or properties at the same time will result in just a single re-render. In most cases, this isn't an issue, but there may be times you'll need to wait for the component to update before continuing.
|
||||
|
||||
Consider this example. We're going to change the `checked` property of the checkbox and observe its corresponding `checked` attribute, which happens to reflect.
|
||||
|
||||
```js
|
||||
const checkbox = document.querySelector('sl-checkbox');
|
||||
checkbox.checked = true;
|
||||
|
||||
console.log(checkbox.hasAttribute('checked')); // false
|
||||
```
|
||||
|
||||
Most devs will expect this to be `true` instead of `false`, but the component hasn't had a chance to re-render yet so the attribute doesn't exist when `hasAttribute()` is called. Since changes are batched, we need to wait for the update before proceeding. This can be done using the `updateComplete` property, which is available on all Lit-based components.
|
||||
|
||||
```js
|
||||
const checkbox = document.querySelector('sl-checkbox');
|
||||
checkbox.checked = true;
|
||||
|
||||
checkbox.updateComplete.then(() => {
|
||||
console.log(checkbox.hasAttribute('checked')); // true
|
||||
});
|
||||
```
|
||||
|
||||
This time we see an empty string, which means the boolean attribute is now present!
|
||||
|
||||
?> Avoid using `setTimeout()` or `requestAnimationFrame()` in situations like this. They might work, but it's far more reliable to use `updateComplete` instead.
|
||||
|
||||
## Code Completion
|
||||
|
||||
### VS Code
|
||||
|
||||
Shoelace ships with a file called `vscode.html-custom-data.json` that can be used to describe its components to Visual Studio Code. This enables code completion for Shoelace components (also known as "code hinting" or "IntelliSense"). To enable it, you need to tell VS Code where the file is.
|
||||
Shoelace ships with a file called `vscode.html-custom-data.json` that can be used to describe it's custom elements to Visual Studio Code. This enables code completion for Shoelace components (also known as "code hinting" or "IntelliSense"). To enable it, you need to tell VS Code where the file is.
|
||||
|
||||
1. [Install Shoelace locally](/getting-started/installation#local-installation)
|
||||
2. Create a folder called `.vscode` at the root of your project
|
||||
3. Create a file inside the folder called `settings.json`
|
||||
2. If it doesn't already exist, create a folder called `.vscode` at the root of your project
|
||||
3. If it doesn't already exist, create a file inside that folder called `settings.json`
|
||||
4. Add the following to the file
|
||||
|
||||
```js
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
content="Shoelace provides a collection of professionally designed, every day UI components built on a framework-agnostic technology."
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="custom-elements-manifest" content="dist/custom-elements.json" />
|
||||
<meta name="custom-elements-manifest" content="/dist/custom-elements.json" />
|
||||
<meta property="og:title" content="Shoelace" />
|
||||
<meta
|
||||
property="og:description"
|
||||
|
||||
@@ -6,11 +6,236 @@ Components with the <sl-badge variant="warning" pill>Experimental</sl-badge> bad
|
||||
|
||||
New versions of Shoelace are released as-needed and generally occur when a critical mass of changes have accumulated. At any time, you can see what's coming in the next release by visiting [next.shoelace.style](https://next.shoelace.style).
|
||||
|
||||
_During the beta period, these restrictions may be relaxed in the event of a mission-critical bug._ 🐛
|
||||
## 2.1.0
|
||||
|
||||
- Added the `sl-focus` and `sl-blur` events to `<sl-color-picker>`
|
||||
- Added the `focus()` and `blur()` methods to `<sl-color-picker>`
|
||||
- Added the `sl-invalid` event to all form controls to enable custom validation logic [#1167](https://github.com/shoelace-style/shoelace/pull/1167)
|
||||
- Added `validity` and `validationMessage` properties to all form controls [#1167](https://github.com/shoelace-style/shoelace/pull/1167)
|
||||
- Added the `rel` attribute to `<sl-button>` to allow users to create button links that point to specific targets [#1200](https://github.com/shoelace-style/shoelace/issues/1200)
|
||||
- Fixed a bug in `<sl-animated-image>` where the play and pause buttons were transposed [#1147](https://github.com/shoelace-style/shoelace/issues/1147)
|
||||
- Fixed a bug that prevented `web-types.json` from being generated [#1154](https://github.com/shoelace-style/shoelace/discussions/1154)
|
||||
- Fixed a bug in `<sl-color-picker>` that prevented `sl-change` and `sl-input` from emitting when using the eye dropper [#1157](https://github.com/shoelace-style/shoelace/issues/1157)
|
||||
- Fixed a bug in `<sl-dropdown>` that prevented keyboard users from selecting menu items when using the keyboard [#1165](https://github.com/shoelace-style/shoelace/issues/1165)
|
||||
- Fixed a bug in the template for `<sl-select>` that caused the `form-control-help-text` part to not be in the same location as other form controls [#1178](https://github.com/shoelace-style/shoelace/issues/1178)
|
||||
- Fixed a bug in `<sl-checkbox>` and `<sl-switch>` that caused the browser to scroll incorrectly when focusing on a control in a container with overflow [#1169](https://github.com/shoelace-style/shoelace/issues/1169)
|
||||
- Fixed a bug in `<sl-menu-item>` that caused the `click` event to be emitted when the item was disabled [#1113](https://github.com/shoelace-style/shoelace/issues/1113)
|
||||
- Fixed a bug in form controls that erroneously prevented validation states from being set when `novalidate` was used on the containing form [#1164](https://github.com/shoelace-style/shoelace/issues/1164)
|
||||
- Fixed a bug in `<sl-checkbox>` that caused the required asterisk to appear before the label in Chrome
|
||||
- Fixed a bug that prevented large form control labels from having the correct font size [#1195](https://github.com/shoelace-style/shoelace/pull/1195)
|
||||
- Improved the behavior of `<sl-dropdown>` in Safari so keyboard interaction works the same as in other browsers [#1177](https://github.com/shoelace-style/shoelace/issues/1177)
|
||||
- Improved the [icons](/components/icon) page so it's not as sluggish in Safari [#1122](https://github.com/shoelace-style/shoelace/issues/1122)
|
||||
- Improved the accessibility of `<sl-switch>` when used in forced-colors / Windows High Contrast mode [#1114](https://github.com/shoelace-style/shoelace/issues/1114)
|
||||
- Improved user interaction heuristics for all form controls [#1175](https://github.com/shoelace-style/shoelace/issues/1175)
|
||||
|
||||
## 2.0.0
|
||||
|
||||
This is the first stable release of Shoelace 2, meaning breaking changes to the API will no longer be accepted for this version. Development of Shoelace 2.0 started in January 2020. The first beta was released on [July 15, 2020](https://github.com/shoelace-style/shoelace/releases/tag/v2.0.0-beta.1). Since then, Shoelace has grown quite a bit! Here are some stats from the project as of January 24, 2023:
|
||||
|
||||
- 55 components have been built
|
||||
- [Over 2,500 commits](https://github.com/shoelace-style/shoelace/commits/next) have been made to the project
|
||||
- [88 beta versions](https://github.com/shoelace-style/shoelace/tags) have been released
|
||||
- [85 people](https://github.com/shoelace-style/shoelace/graphs/contributors) have contributed to the project
|
||||
- [669 issues](https://github.com/shoelace-style/shoelace/issues?q=is%3Aissue+is%3Aclosed) have been filed on GitHub
|
||||
- [274 pull requests](https://github.com/shoelace-style/shoelace/pulls) have been opened
|
||||
- [More than 150 discussions](https://github.com/shoelace-style/shoelace/discussions) have been started on GitHub
|
||||
- [Over 500 people](https://discord.com/invite/mg8f26C) have joined the Shoelace community on Discord
|
||||
- [Over 300 million CDN hits](https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace) per month
|
||||
- [Over 13,000 npm downloads](https://www.npmjs.com/package/@shoelace-style/shoelace) per week
|
||||
- [73rd most popular project](https://www.jsdelivr.com/statistics) on jsDelivr
|
||||
- [#2 product of the day](https://www.producthunt.com/products/shoelace-css) on Product Hunt (July 25, 2020)
|
||||
|
||||
I'd like to extend a very special thank you to every single contributor who worked to make this possible. Everyone who's filed a bug, submitted a PR, requested a feature, started a discussion, helped with testing, and advocated for the project. You are just as responsible for Shoelace's success as I am. I'd also like to thank the folks at [Font Awesome](https://fontawesome.com/) for recognizing Shoelace's potential and [believing in me](https://blog.fontawesome.com/shoelace-joins-font-awesome/) to make it happen.
|
||||
|
||||
Thank you! And keep building _awesome_ stuff!
|
||||
|
||||
Without further ado, here are the notes for this release.
|
||||
|
||||
- Added support for the `inert` attribute on `<sl-menu-item>` to allow hidden menu items to not accept focus [#1107](https://github.com/shoelace-style/shoelace/issues/1107)
|
||||
- Added the `tag` part to `<sl-select>`
|
||||
- Added `sl-hover` event to `<sl-rating>` [#1125](https://github.com/shoelace-style/shoelace/issues/1125)
|
||||
- Added the `@documentation` tag with a link to the docs for each component
|
||||
- Added the `form` attribute to all form controls to allow placing them outside of a `<form>` element [#1130](https://github.com/shoelace-style/shoelace/issues/1130)
|
||||
- Added the `getFormControls()` function as an alternative to `HTMLFormElement.elements`
|
||||
- Added missing docs for the `header-actions` slot in `<sl-dialog>` and `<sl-drawer>`
|
||||
- Added `hue-slider-handle` and `opacity-slider-handle` parts to `<sl-color-picker>` and correct other part names in the docs [#1142](https://github.com/shoelace-style/shoelace/issues/1142)
|
||||
- Fixed a bug in `<sl-select>` that prevented placeholders from showing when `multiple` was used [#1109](https://github.com/shoelace-style/shoelace/issues/1109)
|
||||
- Fixed a bug in `<sl-select>` that caused tags to not be rounded when using the `pill` attribute [#1117](https://github.com/shoelace-style/shoelace/issues/1117)
|
||||
- Fixed a bug in `<sl-select>` where the `sl-change` and `sl-input` events didn't weren't emitted when removing tags [#1119](https://github.com/shoelace-style/shoelace/issues/1119)
|
||||
- Fixed a bug in `<sl-select>` that caused the listbox to scroll to the first selected item when selecting multiple items [#1138](https://github.com/shoelace-style/shoelace/issues/1138)
|
||||
- Fixed a bug in `<sl-select>` where the input color and input hover color wasn't using the correct design tokens [#1143](https://github.com/shoelace-style/shoelace/issues/1143)
|
||||
- Fixed a bug in `<sl-color-picker>` that logged a console error when parsing swatches with whitespace
|
||||
- Fixed a bug in `<sl-color-picker>` that caused selected colors to be wrong due to incorrect HSV calculations
|
||||
- Fixed a bug in `<sl-color-picker>` that prevented the initial value from being set correct when assigned as a property [#1141](https://github.com/shoelace-style/shoelace/issues/1141)
|
||||
- Fixed a bug in `<sl-radio-button>` that caused the checked button's right border to be incorrect [#1110](https://github.com/shoelace-style/shoelace/issues/1110)
|
||||
- Fixed a bug in `<sl-spinner>` that caused the animation to stop working correctly in Safari [#1121](https://github.com/shoelace-style/shoelace/issues/1121)
|
||||
- Fixed a bug that prevented the entire `<sl-tab-panel>` to be hidden when inactive
|
||||
- Fixed a bug that caused the value of `<sl-radio-group>` to be `undefined` depending on where the radio was activated [#1134](https://github.com/shoelace-style/shoelace/issues/1134)
|
||||
- Fixed a bug that caused body content to shift when scroll locking was enabled [#1132](https://github.com/shoelace-style/shoelace/issues/1132)
|
||||
- Fixed a bug in `<sl-icon>` that caused icons to sometimes be clipped in Safari
|
||||
- Fixed a bug that prevented label colors from inheriting by default in `<sl-checkbox>`, `<sl-radio>`, and `<sl-switch>`
|
||||
- Fixed a bug in `<sl-radio-group>` that caused an extra margin between the host element and the internal fieldset [#1139](https://github.com/shoelace-style/shoelace/issues/1139)
|
||||
- Refactored the `ShoelaceFormControl` interface to remove the `invalid` property, allowing a more intuitive API for controlling validation internally
|
||||
- Renamed the internal `FormSubmitController` to `FormControlController` to better reflect what it's used for
|
||||
- Updated Lit to 2.6.1
|
||||
- Updated Floating UI to 1.1.0
|
||||
- Updated all other dependencies to latest versions
|
||||
|
||||
## 2.0.0-beta.88
|
||||
|
||||
This release includes a complete rewrite of `<sl-select>` to improve accessibility and simplify its internals.
|
||||
|
||||
- 🚨 BREAKING: rewrote `<sl-select>`
|
||||
- Accessibility has been significantly improved, especially in screen readers
|
||||
- You must use `<sl-option>` instead of `<sl-menu-item>` for options now
|
||||
- The `suffix` slot was removed because it was confusing to users and its position made the clear button inaccessible
|
||||
- The `max-tags-visible` attribute has been renamed to `max-options-visible`
|
||||
- Many parts have been removed or renamed (please see the docs for more details)
|
||||
- 🚨 BREAKING: removed the `sl-label-change` event from `<sl-menu-item>` (listen for `slotchange` instead)
|
||||
- 🚨 BREAKING: removed type to select logic from `<sl-menu>` (this was added specifically for `<sl-select>` which no longer uses `<sl-menu>`)
|
||||
- 🚨 BREAKING: swatches in `<sl-color-picker>` are no longer present by default (but you can set them using the `swatches` attribute now)
|
||||
- 🚨 BREAKING: improved the accessibility of `<sl-menu-item>` so checked items are announced as such
|
||||
- Checkbox menu items must now have `type="checkbox"` before applying the `checked` attribute
|
||||
- Checkbox menu items will now toggle their `checked` state on their own when selected
|
||||
- Disabled menu items will now receive focus, but are still not selectable
|
||||
- Added the `<sl-option>` component
|
||||
- Added Traditional Chinese translation [#1086](https://github.com/shoelace-style/shoelace/pull/1086)
|
||||
- Added support for `swatches` to be an attribute of `<sl-color-picker>` so swatches can be defined declaratively (it was previously a property; use a `;` to separate color values)
|
||||
- Fixed a bug in `<sl-tree-item>` where the checked/indeterminate states could get out of sync when using the `multiple` option [#1076](https://github.com/shoelace-style/shoelace/issues/1076)
|
||||
- Fixed a bug in `<sl-tree>` that caused `sl-selection-change` to emit before the DOM updated [#1096](https://github.com/shoelace-style/shoelace/issues/1096)
|
||||
- Fixed a bug that prevented `<sl-switch>` from submitting a default value of `on` when no value was provided [#1103](https://github.com/shoelace-style/shoelace/discussions/1103)
|
||||
- Fixed a bug in `<sl-textarea>` that caused the scrollbar to show sometimes when using `resize="auto"`
|
||||
- Fixed a bug in `<sl-input>` and `<sl-textarea>` that caused its validation states to be out of sync in some cases [#1063](https://github.com/shoelace-style/shoelace/issues/1063)
|
||||
- Reorganized all components to make class structures more consistent
|
||||
- Updated some incorrect default values for design tokens in the docs [#1097](https://github.com/shoelace-style/shoelace/issues/1097)
|
||||
- Updated non-public fields to use the `private` keyword (these were previously private only by convention, but now TypeScript will warn you)
|
||||
- Updated the hover style of `<sl-menu-item>` to be consistent with `<sl-option>`
|
||||
- Updated the status of `<sl-tree>` and `<sl-tree-item>` from experimental to stable
|
||||
- Updated React wrappers to use the latest API from `@lit-labs/react` [#1090](https://github.com/shoelace-style/shoelace/pull/1090)
|
||||
- Updated Bootstrap Icons to 1.10.3
|
||||
|
||||
## 2.0.0-beta.87
|
||||
|
||||
- 🚨 BREAKING: changed the default size of medium checkboxes, radios, and switches to 18px instead of 16px
|
||||
- 🚨 BREAKING: renamed the `--sl-toggle-size` design token to `--sl-toggle-size-medium`
|
||||
- Added the `--sl-toggle-size-small` and `--sl-toggle-size-large` design tokens
|
||||
- Added the `size` attribute to `<sl-checkbox>`, `<sl-radio>`, and `<sl-switch>` [#1071](https://github.com/shoelace-style/shoelace/issues/1071)
|
||||
- Added the `sl-input` event to `<sl-checkbox>`, `<sl-color-picker>`, `<sl-radio>`, `<sl-range>`, and `<sl-switch>`
|
||||
- Added HSV format to `<sl-color-picker>` [#1072](https://github.com/shoelace-style/shoelace/pull/1072)
|
||||
- Fixed a bug in `<sl-color-picker>` that sometimes prevented the color from updating when clicking or tapping on the controls
|
||||
- Fixed a bug in `<sl-color-picker>` that prevented text from being entered in the color input
|
||||
- Fixed a bug in `<sl-input>` that caused the `sl-change` event to be incorrectly emitted when the value was set programmatically [#917](https://github.com/shoelace-style/shoelace/issues/917)
|
||||
- Fixed a bug in `<sl-input>` and `<sl-textarea>` that made it impossible to disable spell checking [#1061](https://github.com/shoelace-style/shoelace/issues/1061)
|
||||
- Fixed non-modal behaviors in `<sl-drawer>` when using the `contained` attribute [#1051](https://github.com/shoelace-style/shoelace/issues/1051)
|
||||
- Fixed a bug in `<sl-checkbox>` and `<sl-radio>` that caused the checked icons to not scale property when resized
|
||||
- Fixed a bug that broke React imports [#1050](https://github.com/shoelace-style/shoelace/pull/1050)
|
||||
- Refactored `<sl-color-picker>` to use `@ctrl/tinycolor` instead of `color` saving ~67KB [#1072](https://github.com/shoelace-style/shoelace/pull/1072)
|
||||
- Removed the `formdata` event polyfill since it's now available in the last two versions of all major browsers
|
||||
|
||||
## 2.0.0-beta.86
|
||||
|
||||
- 🚨 BREAKING: changed the default value of `date` in `<sl-relative-time>` to the current date instead of the Unix epoch
|
||||
- 🚨 BREAKING: removed the `handle-icon` part and slot from `<sl-image-comparer>` (use `handle` instead)
|
||||
- 🚨 BREAKING: removed the `handle` slot from `<sl-split-panel>` (use the `divider` slot instead)
|
||||
- 🚨 BREAKING: removed the `--box-shadow` custom property from `<sl-alert>` (apply a box shadow to `::part(base)` instead)
|
||||
- 🚨 BREAKING: removed the `play-icon` and `pause-icon` parts (use the `play-icon` and `pause-icon` slots instead)
|
||||
- Added `header-actions` slot to `<sl-dialog>` and `<sl-drawer>`
|
||||
- Added the `expand-icon` and `collapse-icon` slots to `<sl-details>` and refactored the icon animation [#1046](https://github.com/shoelace-style/shoelace/discussions/1046)
|
||||
- Added the `play-icon` and `pause-icon` slots to `<sl-animated-image>` so you can customize the default icons
|
||||
- Converted `isTreeItem()` export to a static method of `<sl-tree-item>`
|
||||
- Fixed a bug in `<sl-tree-item>` where `sl-selection-change` was emitted when the selection didn't change [#1030](https://github.com/shoelace-style/shoelace/pull/1030)
|
||||
- Fixed a bug in `<sl-button-group>` that caused the border to render incorrectly when hovering over icons inside buttons [#1035](https://github.com/shoelace-style/shoelace/issues/1035)
|
||||
- Fixed an incorrect default for `flip-fallback-strategy` in `<sl-popup>` that caused the fallback strategy to be `initial` instead of `best-fit`, which is inconsistent with Floating UI's default [#1036](https://github.com/shoelace-style/shoelace/issues/1036)
|
||||
- Fixed a bug where browser validation tooltips would show up when hovering over form controls [#1037](https://github.com/shoelace-style/shoelace/issues/1037)
|
||||
- Fixed a bug in `<sl-tab-group>` that sometimes caused the active tab indicator to not animate
|
||||
- Fixed a bug in `<sl-tree-item>` that caused the expand/collapse icon slot to be out of sync when the node is open initially
|
||||
- Fixed the mislabeled `handle-icon` slot in `<sl-image-comparer>` (it now points to the `<slot>`, not the slot's fallback content)
|
||||
- Fixed the border radius in `<sl-dropdown>` so it matches with nested `<sl-menu>` elements
|
||||
- Fixed a bug that caused all button values to appear in submitted form data even if they weren't the submitter
|
||||
- Improved IntelliSense in VS Code, courtesy of [Burton's amazing CEM Analyzer plugin](https://github.com/break-stuff/cem-plugin-vs-code-custom-data-generator)
|
||||
- Improved accessibility of `<sl-alert>` so the alert is announced and the close button has a label
|
||||
- Improved accessibility of `<sl-progress-ring>` so slotted labels are announced along with visually hidden labels
|
||||
- Refactored all styles and animations to use `translate`, `rotate`, and `scale` instead of `transform`
|
||||
- Removed slot wrappers from many components, allowing better control over user-applied styles
|
||||
- Removed unused aria attributes from `<sl-skeleton>`
|
||||
- Replaced the `x` icon in the system icon library with `x-lg` to improve icon consistency
|
||||
|
||||
## 2.0.0-beta.85
|
||||
|
||||
- Fixed a bug in `<sl-dropdown>` that caused containing dialogs, drawers, etc. to close when pressing <kbd>Escape</kbd> while focused [#1024](https://github.com/shoelace-style/shoelace/issues/1024)
|
||||
- Fixed a bug in `<sl-tree-item>` that allowed lazy nodes to be incorrectly selected [#1023](https://github.com/shoelace-style/shoelace/pull/1023)
|
||||
- Fixed a typing bug in `<sl-tree-item>` [#1026](https://github.com/shoelace-style/shoelace/pull/1026)
|
||||
- Updated Floating UI to 1.0.7 to fix a bug that prevented `hoist` from working correctly in `<sl-dropdown>` after a recent update [#1024](https://github.com/shoelace-style/shoelace/issues/1024)
|
||||
|
||||
## 2.0.0-beta.84
|
||||
|
||||
- 🚨 BREAKING: Removed the `fieldset` property from `<sl-radio-group>` (use CSS parts if you want to keep the border) [#965](https://github.com/shoelace-style/shoelace/issues/965)
|
||||
- 🚨 BREAKING: Removed `base` and `label` parts from `<sl-radio-group>` (use `form-control` and `form-control__label` instead) [#965](https://github.com/shoelace-style/shoelace/issues/965)
|
||||
- 🚨 BREAKING: Removed the `base` part from `<sl-icon>` (style the host element directly instead)
|
||||
- 🚨 BREAKING: Removed the `invalid` attribute from form controls (use `[data-invalid]` to target it with CSS)
|
||||
- Added validation states to all form controls to allow styling based on various validation states [#1011](https://github.com/shoelace-style/shoelace/issues/1011)
|
||||
- `data-required` - indicates that a value is required
|
||||
- `data-optional` - indicates that a value is NOT required
|
||||
- `data-invalid` - indicates that the form control is invalid
|
||||
- `data-valid` - indicates that the form control is valid
|
||||
- `data-user-invalid` - indicates the form control is invalid and the user has interacted with it
|
||||
- `data-user-valid` - indicates the form control is valid and the user has interacted with it
|
||||
- Added npm exports [#1020](https://github.com/shoelace-style/shoelace/pull/1020)
|
||||
- Added `checkValidity()` method to all form controls
|
||||
- Added `reportValidity()` method to `<sl-range>`
|
||||
- Added `button--checked` to `<sl-radio-button>` and `control--checked` to `<sl-radio>` to style just the checked state [#933](https://github.com/shoelace-style/shoelace/pull/933)
|
||||
- Added tests for `<sl-menu>`, `<sl-menu-item>`, `<sl-menu-label>`, `<sl-rating>`, `<sl-relative-time>`, `<sl-skeleton>`, `<sl-tab-panel>` and `<sl-tag>` [#935](https://github.com/shoelace-style/shoelace/pull/935)
|
||||
[#949](https://github.com/shoelace-style/shoelace/pull/949) [#951](https://github.com/shoelace-style/shoelace/pull/951) [#953](https://github.com/shoelace-style/shoelace/pull/953)
|
||||
[#956](https://github.com/shoelace-style/shoelace/pull/956) [#984](https://github.com/shoelace-style/shoelace/pull/984) [#991](https://github.com/shoelace-style/shoelace/pull/991)
|
||||
- Added translations for Hungarian, Turkish, English (United Kingdom) and German (Austria) [#982](https://github.com/shoelace-style/shoelace/pull/982) [#989](https://github.com/shoelace-style/shoelace/pull/989)
|
||||
- Added `--indicator-transition-duration` custom property to `<sl-progress-ring>` [#986](https://github.com/shoelace-style/shoelace/issues/986)
|
||||
- Added `--sl-input-required-content-color` custom property to all form controls [#948](https://github.com/shoelace-style/shoelace/pull/948)
|
||||
- Added the ability to cancel `sl-show` and `sl-hide` events in `<sl-details>` [#993](https://github.com/shoelace-style/shoelace/issues/993)
|
||||
- Added `focus()` and `blur()` methods to `<sl-radio-button>`
|
||||
- Added `stepUp()` and `stepDown()` methods to `<sl-input>` and `<sl-range>` [#1013](https://github.com/shoelace-style/shoelace/pull/1013)
|
||||
- Added `showPicker()` method to `<sl-input>` [#1013](https://github.com/shoelace-style/shoelace/pull/1013)
|
||||
- Added the `handle-icon` part to `<sl-image-comparer>`
|
||||
- Added `caret`, `check`, `grip-vertical`, `indeterminate`, and `radio` icons to the system library and removed `check-lg` [#985](https://github.com/shoelace-style/shoelace/issues/985)
|
||||
- Added the `loading` attribute to `<sl-avatar>` to allow lazy loading of image avatars [#1006](https://github.com/shoelace-style/shoelace/pull/1006)
|
||||
- Added `formenctype` attribute to `<sl-button>` [#1009](https://github.com/shoelace-style/shoelace/pull/1009)
|
||||
- Added `exports` to `package.json` and removed the `main` and `module` properties [#1007](https://github.com/shoelace-style/shoelace/pull/1007)
|
||||
- Fixed a bug in `<sl-card>` that prevented the border radius to apply correctly to the header [#934](https://github.com/shoelace-style/shoelace/pull/934)
|
||||
- Fixed a bug in `<sl-button-group>` where the inner border disappeared on focus [#980](https://github.com/shoelace-style/shoelace/pull/980)
|
||||
- Fixed a bug that caused prefix/suffix animations in `<sl-input>` to wobble [#996](https://github.com/shoelace-style/shoelace/issues/996)
|
||||
- Fixed a bug in `<sl-icon>` that prevented color from being set on the host element [#999](https://github.com/shoelace-style/shoelace/issues/999)
|
||||
- Fixed a bug in `<sl-dropdown>` where the `keydown` event erroneously propagated to ancestors when pressing <kbd>Escape</kbd> [#990](https://github.com/shoelace-style/shoelace/issues/990)
|
||||
- Fixed a bug that prevented arrow keys from scrolling content within `<sl-dialog>` and `<sl-drawer>` [#925](https://github.com/shoelace-style/shoelace/issues/925)
|
||||
- Fixed a bug that prevented <kbd>Escape</kbd> from closing `<sl-dialog>` and `<sl-drawer>` in some cases
|
||||
- Fixed a bug that caused forms to submit unexpectedly when selecting certain characters [#988](https://github.com/shoelace-style/shoelace/pull/988)
|
||||
- Fixed a bug in `<sl-radio-group>` that prevented the `invalid` property from correctly reflecting validity sometimes [#992](https://github.com/shoelace-style/shoelace/issues/992)
|
||||
- Fixed a bug in `<sl-tree>` that prevented selections from working correctly on dynamically added tree items [#963](https://github.com/shoelace-style/shoelace/issues/963)
|
||||
- Fixed module paths in `custom-elements.json` so they point to the dist file instead of the source file [#725](https://github.com/shoelace-style/shoelace/issues/725)
|
||||
- Fixed an incorrect return value for `reportValidity()` in `<sl-color-picker>`
|
||||
- Improved `<sl-badge>` to improve padding and render relative to the current font size
|
||||
- Improved how many components display in forced-colors mode / Windows High Contrast mode
|
||||
- Improved `<sl-color-picker>` so it's usable in forced-colors mode
|
||||
- Improved `<sl-dialog>` and `<sl-drawer>` so the panel is more visible in forced-colors mode
|
||||
- Improved `<sl-menu-item>` so selections are visible in forced-colors mode
|
||||
- Improved `<sl-progress-bar>` so it's visible in forced-colors mode
|
||||
- Improved `<sl-radio-button>` so checked states are visible in forced-colors mode
|
||||
- Improved `<sl-range>` so the thumb, track, and tooltips are visible in forced-colors mode
|
||||
- Improved `<sl-rating>` so icons are visible in forced-colors mode
|
||||
- Improved `<sl-split-panel>` so the divider is visible in forced-colors mode
|
||||
- Improved `<sl-tree-item>` so selected items are visible in forced-colors mode
|
||||
- Improved `<sl-tab-group>` so tabs are cleaner and easier to understand in forced-colors mode
|
||||
- Improved positioning of the menu in `<sl-select>` so you can customize the menu width [#1018](https://github.com/shoelace-style/shoelace/issues/1018)
|
||||
- Moved all component descriptions to `@summary` to get them within documentation tools [#962](https://github.com/shoelace-style/shoelace/pull/962)
|
||||
- Refactored form controls to use the `ShoelaceFormControl` interface to improve type safety and consistency
|
||||
- Updated Lit to 2.4.1
|
||||
- Updated `@shoelace-style/localize` t0 3.0.3 to support for extended language codes
|
||||
- Updated Bootstrap Icons to 1.10.2
|
||||
- Updated TypeScript to 4.8.4
|
||||
- Updated esbuild to 0.15.14
|
||||
- Updated all other dependencies to latest versions
|
||||
|
||||
## 2.0.0-beta.83
|
||||
|
||||
This release removes the `<sl-responsive-media>` component. When this component was introduced, support for [`aspect-radio`](https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio)) wasn't great. These days, [the property is supported](https://caniuse.com/mdn-css_properties_aspect-ratio) by all of Shoelace's target browsers, making a dedicated component redundant.
|
||||
This release removes the `<sl-responsive-media>` component. When this component was introduced, support for [`aspect-ratio`](https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio)) wasn't great. These days, [the property is supported](https://caniuse.com/mdn-css_properties_aspect-ratio) by all of Shoelace's target browsers, making a dedicated component redundant.
|
||||
|
||||
- 🚨 BREAKING: Removed `<sl-responsive-media>` (use the well-supported `aspect-ratio` CSS property instead)
|
||||
- 🚨 BREAKING: Changed the `toggle-password` attribute of `<sl-input>` to `password-toggle` for consistency
|
||||
@@ -418,7 +643,7 @@ Thank you for your help and patience with testing Shoelace. I promise, we're not
|
||||
- Improved a11y of the scroll buttons in `<sl-tab-group>`
|
||||
- Improved a11y of the close button in `<sl-tab>`
|
||||
- Improved a11y of `<sl-tab-panel>` by removing an invalid `aria-selected` attribute [#579](https://github.com/shoelace-style/shoelace/issues/579)
|
||||
- Improved a11y of `<sl-icon>` by not using a variation of the `name` attribute for labels (use the `label` prop instead)
|
||||
- Improved a11y of `<sl-icon>` by not using a variation of the `name` attribute for labels (use the `label` attribute instead)
|
||||
- Moved `role` from the shadow root to the host element in `<sl-menu>`
|
||||
- Removed redundant `role="menu"` in `<sl-dropdown>`
|
||||
- Slightly faster animations for showing and hiding `<sl-dropdown>`
|
||||
@@ -480,7 +705,7 @@ This release is the second attempt at unbundling dependencies. This will be a br
|
||||
Shoelace doesn't have a lot of dependencies, but this release unbundles most of them so you can potentially save some extra kilobytes. This will be a breaking change only if your configuration _does not_ support bare module specifiers. CDN users and bundler users will be unaffected.
|
||||
|
||||
- 🚨 BREAKING: renamed the `sl-clear` event to `sl-remove`, the `clear-button` part to `remove-button`, and the `clearable` property to `removable` in `<sl-tag>`
|
||||
- Added the `disabled` prop to `<sl-resize-observer>`
|
||||
- Added the `disabled` attribute to `<sl-resize-observer>`
|
||||
- Fixed a bug in `<sl-mutation-observer>` where setting `disabled` initially didn't work
|
||||
- Unbundled dependencies and configured external imports to be packaged with bare module specifiers
|
||||
|
||||
|
||||
@@ -188,8 +188,6 @@ Please do not make any changes to `prettier.config.cjs` without consulting the m
|
||||
|
||||
Components should be composable, meaning you can easily reuse them with and within other components. This reduces the overall size of the library, expedites feature development, and maintains a consistent user experience.
|
||||
|
||||
The `<sl-select>` component, for example, makes use of the dropdown, input, menu, and menu item components. Because it's offloading most of its functionality and styles to lower-level components, the select component remains lightweight and its appearance is consistent with other form controls and menus.
|
||||
|
||||
### Component Structure
|
||||
|
||||
All components have a host element, which is a reference to the `<sl-*>` element itself. Make sure to always set the host element's `display` property to the appropriate value depending on your needs, as the default is `inline` per the custom element spec.
|
||||
@@ -202,6 +200,23 @@ All components have a host element, which is a reference to the `<sl-*>` element
|
||||
|
||||
Aside from `display`, avoid setting styles on the host element when possible. The reason for this is that styles applied to the host element are not encapsulated. Instead, create a base element that wraps the component's internals and style that instead. This convention also makes it easier to use BEM in components, as the base element can serve as the "block" entity.
|
||||
|
||||
When authoring components, please try to follow the same structure and conventions found in other components. Classes, for example, generally follow this structure:
|
||||
|
||||
- Static properties/methods
|
||||
- Private/public properties (that are _not_ reactive)
|
||||
- `@query` decorators
|
||||
- `@state` decorators
|
||||
- `@property` decorators
|
||||
- Lifecycle methods (`connectedCallback()`, `disconnectedCallback()`, `firstUpdated()`, etc.)
|
||||
- Private methods
|
||||
- `@watch` decorators
|
||||
- Public methods
|
||||
- The `render()` method
|
||||
|
||||
Please avoid using the `public` keyword for class fields. It's simply too verbose when combined with decorators, property names, and arguments. However, _please do_ add `private` in front of any property or method that is intended to be private.
|
||||
|
||||
?> This might seem like a lot, but it's fairly intuitive once you start working with the library. However, don't let this structure prevent you from submitting a PR. [Code can change](https://www.abeautifulsite.net/posts/code-can-change/) and nobody will chastise you for "getting it wrong." At the same time, encouraging consistency helps keep the library maintainable and easy for others to understand. (A lint rule that helps with things like this would be a very welcome PR!)
|
||||
|
||||
### Class Names
|
||||
|
||||
All components use a [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM), so styles are completely encapsulated from the rest of the document. As a result, class names used _inside_ a component won't conflict with class names _outside_ the component, so we're free to name them anything we want.
|
||||
@@ -224,12 +239,34 @@ When a component relies on the presence of slotted content to do something, don'
|
||||
|
||||
See the source of card, dialog, or drawer for examples.
|
||||
|
||||
### Dynamic Slot Names and Expand/Collapse Icons
|
||||
|
||||
A pattern has been established in `<sl-details>` and `<sl-tree-item>` for expand/collapse icons that animate on open/close. In short, create two slots called `expand-icon` and `collapse-icon` and render them both in the DOM, using CSS to show/hide only one based on the current open state. Avoid conditionally rendering them. Also avoid using dynamic slot names, such as `<slot name=${open ? 'open' : 'closed'}>`, because Firefox will not animate them.
|
||||
|
||||
There should be a container element immediately surrounding both slots. The container should be animated with CSS by default and it should have a part so the user can override the animation or disable it. Please refer to the source and documentation for `<sl-details>` and/or `<sl-tree-item>` for details.
|
||||
|
||||
### Fallback Content in Slots
|
||||
|
||||
When providing fallback content inside of `<slot>` elements, avoid adding parts, e.g.:
|
||||
|
||||
```html
|
||||
<slot name="icon">
|
||||
<sl-icon part="close-icon"></sl-icon>
|
||||
</slot>
|
||||
```
|
||||
|
||||
This creates confusion because the part will be documented, but it won't work when the user slots in their own content. The recommended way to customize this example is for the user to slot in their own content and target its styles with CSS as needed.
|
||||
|
||||
### Custom Events
|
||||
|
||||
Components must only emit custom events, and all custom events must start with `sl-` as a namespace. For compatibility with frameworks that utilize DOM templates, custom events must have lowercase, kebab-style names. For example, use `sl-change` instead of `slChange`.
|
||||
|
||||
This convention avoids the problem of browsers lowercasing attributes, causing some frameworks to be unable to listen to them. This problem isn't specific to one framework, but [Vue's documentation](https://vuejs.org/v2/guide/components-custom-events.html#Event-Names) provides a good explanation of the problem.
|
||||
|
||||
### Change Events
|
||||
|
||||
When change events are emitted by Shoelace components, they should be named `sl-change` and they should only be emitted as a result of user input. Programmatic changes, such as setting `el.value = '…'` _should not_ result in a change event being emitted. This is consistent with how native form controls work.
|
||||
|
||||
### CSS Custom Properties
|
||||
|
||||
To expose custom properties as part of a component's API, scope them to the `:host` block.
|
||||
@@ -276,7 +313,7 @@ This convention can be relaxed when the developer experience is greatly improved
|
||||
|
||||
### Naming CSS Parts
|
||||
|
||||
While CSS parts can be named [virtually anything](https://www.abeautifulsite.net/posts/valid-names-for-css-parts/), within Shoelace they must use the kebab-case convention and lowercase letters. Modifiers must be delimited by `--` like in BEM. This is useful for allowing users to target parts with various states, such as `my-part--focus`.
|
||||
While CSS parts can be named [virtually anything](https://www.abeautifulsite.net/posts/valid-names-for-css-parts/), within Shoelace they must use the kebab-case convention and lowercase letters. Additionally, [a BEM-inspired naming convention](https://www.abeautifulsite.net/posts/css-parts-inspired-by-bem/) is used to distinguish parts, subparts, and states.
|
||||
|
||||
When composing elements, use `part` to export the host element and `exportparts` to export its parts.
|
||||
|
||||
@@ -292,6 +329,20 @@ render() {
|
||||
|
||||
This results in a consistent, easy to understand structure for parts. In this example, the `icon` part will target the host element and the `icon__base` part will target the icon's `base` part.
|
||||
|
||||
### Dependencies
|
||||
|
||||
TL;DR – a component is a dependency if and only if it's rendered inside another component's shadow root.
|
||||
|
||||
Many Shoelace components use other Shoelace components internally. For example, `<sl-button>` uses both `<sl-icon>` and `<sl-spinner>` for its caret icon and loading state, respectively. Since these components appear in the button's shadow root, they are considered dependencies of Button. Since dependencies are automatically loaded, users only need to import the button and everything will work as expected.
|
||||
|
||||
Contrast this to `<sl-select>` and `<sl-option>`. At first, one might assume that Option is a dependency of Select. After all, you can't really use Select without slotting in at least one Option. However, Option _is not_ a dependency of Select! The reason is because no Option is rendered in the Select's shadow root. Since the options are provided by the user, it's up to them to import both components independently.
|
||||
|
||||
People often suggest that Shoelace should auto-load Select + Option, Menu + Menu Item, Breadcrumb + Breadcrumb Item, etc. Although some components are designed to work together, they're technically not dependencies so eagerly loading them may not be desirable. What if someone wants to roll their own component with a superset of features? They wouldn't be able to if Shoelace automatically imported it!
|
||||
|
||||
Similarly, in the case of `<sl-radio-group>` there was originally only `<sl-radio>`, but now you can use either `<sl-radio>` or `<sl-radio-button>` as child elements. Which component(s) should be auto-loaded dependencies in this case? Had Radio been a dependency of Radio Group, users that only wanted Radio Buttons would be forced to register both with no way to opt out and no way to provide their own customized version.
|
||||
|
||||
For non-dependencies, _the user_ should decide what gets registered, even if it comes with a minor inconvenience.
|
||||
|
||||
### Form Controls
|
||||
|
||||
Form controls should support submission and validation through the following conventions:
|
||||
@@ -302,3 +353,13 @@ Form controls should support submission and validation through the following con
|
||||
- All form controls must have an `invalid` property that reflects their validity
|
||||
- All form controls should mirror their native validation attributes such as `required`, `pattern`, `minlength`, `maxlength`, etc. when possible
|
||||
- All form controls must be tested to work with the standard `<form>` element
|
||||
|
||||
### System Icons
|
||||
|
||||
Avoid inlining SVG icons inside of templates. If a component requires an icon, make sure `<sl-icon>` is a dependency of the component and use the [system library](/components/icon#customizing-the-system-library):
|
||||
|
||||
```html
|
||||
<sl-icon library="system" name="..."></sl-icon>
|
||||
```
|
||||
|
||||
This will render the icons instantly whereas the default library will fetch them from a remote source. If an icon isn't available in the system library, you will need to add it to `library.system.ts`. Using the system library ensures that all icons load instantly and are customizable by users who wish to provide a custom resolver for the system library.
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
Border radius tokens are used to give sharp edges a more subtle, rounded effect. They use rem units so they scale with the base font size. The pixel values displayed are based on a 16px font size.
|
||||
|
||||
| Token | Value | Example |
|
||||
| ---------------------------- | -------------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| `--sl-border-radius-small` | 0.125rem (2px) | <div class="border-radius-demo" style="border-radius: var(--sl-border-radius-small);"></div> |
|
||||
| `--sl-border-radius-medium` | 0.25rem (4px) | <div class="border-radius-demo" style="border-radius: var(--sl-border-radius-medium);"></div> |
|
||||
| `--sl-border-radius-large` | 0.5rem (8px) | <div class="border-radius-demo" style="border-radius: var(--sl-border-radius-large);"></div> |
|
||||
| `--sl-border-radius-x-large` | 1rem (16px) | <div class="border-radius-demo" style="border-radius: var(--sl-border-radius-x-large);"></div> |
|
||||
| `--sl-border-radius-circle` | 50% | <div class="border-radius-demo" style="border-radius: var(--sl-border-radius-circle);"></div> |
|
||||
| `--sl-border-radius-pill` | 9999px | <div class="border-radius-demo" style="border-radius: var(--sl-border-radius-pill); width: 6rem;"></div> |
|
||||
| Token | Value | Example |
|
||||
| ---------------------------- | --------------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| `--sl-border-radius-small` | 0.1875rem (3px) | <div class="border-radius-demo" style="border-radius: var(--sl-border-radius-small);"></div> |
|
||||
| `--sl-border-radius-medium` | 0.25rem (4px) | <div class="border-radius-demo" style="border-radius: var(--sl-border-radius-medium);"></div> |
|
||||
| `--sl-border-radius-large` | 0.5rem (8px) | <div class="border-radius-demo" style="border-radius: var(--sl-border-radius-large);"></div> |
|
||||
| `--sl-border-radius-x-large` | 1rem (16px) | <div class="border-radius-demo" style="border-radius: var(--sl-border-radius-x-large);"></div> |
|
||||
| `--sl-border-radius-circle` | 50% | <div class="border-radius-demo" style="border-radius: var(--sl-border-radius-circle);"></div> |
|
||||
| `--sl-border-radius-pill` | 9999px | <div class="border-radius-demo" style="border-radius: var(--sl-border-radius-pill); width: 6rem;"></div> |
|
||||
|
||||
155
docs/tokens/more.md
Normal file
155
docs/tokens/more.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# More Design Tokens
|
||||
|
||||
All of the design tokens described herein are considered relatively stable. However, some changes might occur in future versions to address mission critical bugs or improvements. If such changes occur, they _will not_ be considered breaking changes and will be clearly documented in the [changelog](/resources/changelog).
|
||||
|
||||
Most design tokens are consistent across the light and dark theme. Those that vary will show both values.
|
||||
|
||||
?> Currently, the source of design tokens is considered to be [`light.css`](https://github.com/shoelace-style/shoelace/blob/next/src/themes/light.css). The dark theme, [dark.css](https://github.com/shoelace-style/shoelace/blob/next/src/themes/dark.css), mirrors all of the same tokens with dark mode-specific values where appropriate. Work is planned to move all design tokens to a single file, perhaps JSON or YAML, in the near future.
|
||||
|
||||
## Focus Rings
|
||||
|
||||
Focus ring tokens control the appearance of focus rings. Note that form inputs use `--sl-input-focus-ring-*` tokens instead.
|
||||
|
||||
| Token | Value |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------- |
|
||||
| `--sl-focus-ring-color` | var(--sl-color-primary-600) (light theme)<br>var(--sl-color-primary-700) (dark theme) |
|
||||
| `--sl-focus-ring-style` | solid |
|
||||
| `--sl-focus-ring-width` | 3px |
|
||||
| `--sl-focus-ring` | var(--sl-focus-ring-style) var(--sl-focus-ring-width) var(--sl-focus-ring-color) |
|
||||
| `--sl-focus-ring-offset` | 1px |
|
||||
|
||||
## Buttons
|
||||
|
||||
Button tokens control the appearance of buttons. In addition, buttons also currently use some form input tokens such as `--sl-input-height-*` and `--sl-input-border-*`. More button tokens may be added in the future to make it easier to style them more independently.
|
||||
|
||||
| Token | Value |
|
||||
| ------------------------------ | --------------------------- |
|
||||
| `--sl-button-font-size-small` | var(--sl-font-size-x-small) |
|
||||
| `--sl-button-font-size-medium` | var(--sl-font-size-small) |
|
||||
| `--sl-button-font-size-large` | var(--sl-font-size-medium) |
|
||||
|
||||
## Form Inputs
|
||||
|
||||
Form input tokens control the appearance of form controls such as [input](/components/input), [select](/components/select), [textarea](/components/textarea), etc.
|
||||
|
||||
| Token | Value |
|
||||
| --------------------------------------- | -------------------------------- |
|
||||
| `--sl-input-height-small` | 1.875rem; (30px @ 16px base) |
|
||||
| `--sl-input-height-medium` | 2.5rem; (40px @ 16px base) |
|
||||
| `--sl-input-height-large` | 3.125rem; (50px @ 16px base) |
|
||||
| `--sl-input-background-color` | var(--sl-color-neutral-0) |
|
||||
| `--sl-input-background-color-hover` | var(--sl-input-background-color) |
|
||||
| `--sl-input-background-color-focus` | var(--sl-input-background-color) |
|
||||
| `--sl-input-background-color-disabled` | var(--sl-color-neutral-100) |
|
||||
| `--sl-input-border-color` | var(--sl-color-neutral-300) |
|
||||
| `--sl-input-border-color-hover` | var(--sl-color-neutral-400) |
|
||||
| `--sl-input-border-color-focus` | var(--sl-color-primary-500) |
|
||||
| `--sl-input-border-color-disabled` | var(--sl-color-neutral-300) |
|
||||
| `--sl-input-border-width` | 1px |
|
||||
| `--sl-input-required-content` | "\*" |
|
||||
| `--sl-input-required-content-offset` | -2px |
|
||||
| `--sl-input-required-content-color` | var(--sl-input-label-color) |
|
||||
| `--sl-input-border-radius-small` | var(--sl-border-radius-medium) |
|
||||
| `--sl-input-border-radius-medium` | var(--sl-border-radius-medium) |
|
||||
| `--sl-input-border-radius-large` | var(--sl-border-radius-medium) |
|
||||
| `--sl-input-font-family` | var(--sl-font-sans) |
|
||||
| `--sl-input-font-weight` | var(--sl-font-weight-normal) |
|
||||
| `--sl-input-font-size-small` | var(--sl-font-size-small) |
|
||||
| `--sl-input-font-size-medium` | var(--sl-font-size-medium) |
|
||||
| `--sl-input-font-size-large` | var(--sl-font-size-large) |
|
||||
| `--sl-input-letter-spacing` | var(--sl-letter-spacing-normal) |
|
||||
| `--sl-input-color` | var(--sl-color-neutral-700) |
|
||||
| `--sl-input-color-hover` | var(--sl-color-neutral-700) |
|
||||
| `--sl-input-color-focus` | var(--sl-color-neutral-700) |
|
||||
| `--sl-input-color-disabled` | var(--sl-color-neutral-900) |
|
||||
| `--sl-input-icon-color` | var(--sl-color-neutral-500) |
|
||||
| `--sl-input-icon-color-hover` | var(--sl-color-neutral-600) |
|
||||
| `--sl-input-icon-color-focus` | var(--sl-color-neutral-600) |
|
||||
| `--sl-input-placeholder-color` | var(--sl-color-neutral-500) |
|
||||
| `--sl-input-placeholder-color-disabled` | var(--sl-color-neutral-600) |
|
||||
| `--sl-input-spacing-small` | var(--sl-spacing-small) |
|
||||
| `--sl-input-spacing-medium` | var(--sl-spacing-medium) |
|
||||
| `--sl-input-spacing-large` | var(--sl-spacing-large) |
|
||||
| `--sl-input-focus-ring-color` | hsl(198.6 88.7% 48.4% / 40%) |
|
||||
| `--sl-input-focus-ring-offset` | 0 |
|
||||
|
||||
## Filled Form Inputs
|
||||
|
||||
Filled form input tokens control the appearance of form controls using the `filled` variant.
|
||||
|
||||
| Token | Value |
|
||||
| --------------------------------------------- | --------------------------- |
|
||||
| `--sl-input-filled-background-color` | var(--sl-color-neutral-100) |
|
||||
| `--sl-input-filled-background-color-hover` | var(--sl-color-neutral-100) |
|
||||
| `--sl-input-filled-background-color-focus` | var(--sl-color-neutral-100) |
|
||||
| `--sl-input-filled-background-color-disabled` | var(--sl-color-neutral-100) |
|
||||
| `--sl-input-filled-color` | var(--sl-color-neutral-800) |
|
||||
| `--sl-input-filled-color-hover` | var(--sl-color-neutral-800) |
|
||||
| `--sl-input-filled-color-focus` | var(--sl-color-neutral-700) |
|
||||
| `--sl-input-filled-color-disabled` | var(--sl-color-neutral-800) |
|
||||
|
||||
## Form Labels
|
||||
|
||||
Form label tokens control the appearance of labels in form controls.
|
||||
|
||||
| Token | Value |
|
||||
| ----------------------------------- | -------------------------- |
|
||||
| `--sl-input-label-font-size-small` | var(--sl-font-size-small) |
|
||||
| `--sl-input-label-font-size-medium` | var(--sl-font-size-medium) |
|
||||
| `--sl-input-label-font-size-large` | var(--sl-font-size-large) |
|
||||
| `--sl-input-label-color` | inherit |
|
||||
|
||||
## Help Text
|
||||
|
||||
Help text tokens control the appearance of help text in form controls.
|
||||
|
||||
| Token | Value |
|
||||
| --------------------------------------- | --------------------------- |
|
||||
| `--sl-input-help-text-font-size-small` | var(--sl-font-size-x-small) |
|
||||
| `--sl-input-help-text-font-size-medium` | var(--sl-font-size-small) |
|
||||
| `--sl-input-help-text-font-size-large` | var(--sl-font-size-medium) |
|
||||
| `--sl-input-help-text-color` | var(--sl-color-neutral-500) |
|
||||
|
||||
## Toggles
|
||||
|
||||
Toggle tokens control the appearance of toggles such as [checkbox](/components/checkbox), [radio](/components/radio), [switch](/components/switch), etc.
|
||||
|
||||
| Token | Value |
|
||||
| ------------------------- | --------------------------- |
|
||||
| `--sl-toggle-size-small` | 0.875rem (14px @ 16px base) |
|
||||
| `--sl-toggle-size-medium` | 1.125rem (18px @ 16px base) |
|
||||
| `--sl-toggle-size-large` | 1.375rem (22px @ 16px base) |
|
||||
|
||||
## Overlays
|
||||
|
||||
Overlay tokens control the appearance of overlays as used in [dialog](/components/dialog), [drawer](/components/drawer), etc.
|
||||
|
||||
| Token | Value |
|
||||
| ------------------------------- | ------------------------- |
|
||||
| `--sl-overlay-background-color` | hsl(240 3.8% 46.1% / 33%) |
|
||||
|
||||
## Panels
|
||||
|
||||
Panel tokens control the appearance of panels such as those used in [dialog](/components/dialog), [drawer](/components/drawer), [menu](/components/menu), etc.
|
||||
|
||||
| Token | Value |
|
||||
| ----------------------------- | --------------------------- |
|
||||
| `--sl-panel-background-color` | var(--sl-color-neutral-0) |
|
||||
| `--sl-panel-border-color` | var(--sl-color-neutral-200) |
|
||||
| `--sl-panel-border-width` | 1px |
|
||||
|
||||
## Tooltips
|
||||
|
||||
Tooltip tokens control the appearance of tooltips. This includes the [tooltip](/components/tooltip) component as well as other implementations, such [range tooltips](/components/range).
|
||||
|
||||
| Token | Value |
|
||||
| ------------------------------- | ---------------------------------------------------- |
|
||||
| `--sl-tooltip-border-radius` | var(--sl-border-radius-medium) |
|
||||
| `--sl-tooltip-background-color` | var(--sl-color-neutral-800) |
|
||||
| `--sl-tooltip-color` | var(--sl-color-neutral-0) |
|
||||
| `--sl-tooltip-font-family` | var(--sl-font-sans) |
|
||||
| `--sl-tooltip-font-weight` | var(--sl-font-weight-normal) |
|
||||
| `--sl-tooltip-font-size` | var(--sl-font-size-small) |
|
||||
| `--sl-tooltip-line-height` | var(--sl-line-height-dense) |
|
||||
| `--sl-tooltip-padding` | var(--sl-spacing-2x-small) var(--sl-spacing-x-small) |
|
||||
| `--sl-tooltip-arrow-size` | 6px |
|
||||
@@ -10,7 +10,7 @@ The default font stack is designed to be simple and highly available on as many
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `--sl-font-sans` | -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol' | <span style="font-family: var(--sl-font-sans)">The quick brown fox jumped over the lazy dog.</span> |
|
||||
| `--sl-font-serif` | Georgia, 'Times New Roman', serif | <span style="font-family: var(--sl-font-serif)">The quick brown fox jumped over the lazy dog.</span> |
|
||||
| `--sl-font-mono` | Menlo, Monaco, 'Courier New', monospace | <span style="font-family: var(--sl-font-mono)">The quick brown fox jumped over the lazy dog.</span> |
|
||||
| `--sl-font-mono` | SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; | <span style="font-family: var(--sl-font-mono)">The quick brown fox jumped over the lazy dog.</span> |
|
||||
|
||||
## Font Size
|
||||
|
||||
@@ -41,18 +41,18 @@ Font sizes use `rem` units so they scale with the base font size. The pixel valu
|
||||
|
||||
| Token | Value | Example |
|
||||
| ---------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| `--sl-letter-spacing-denser` | ? | <span style="letter-spacing: var(--sl-letter-spacing-denser);">The quick brown fox jumped over the lazy dog.</span> |
|
||||
| `--sl-letter-spacing-denser` | -0.03em | <span style="letter-spacing: var(--sl-letter-spacing-denser);">The quick brown fox jumped over the lazy dog.</span> |
|
||||
| `--sl-letter-spacing-dense` | -0.015em | <span style="letter-spacing: var(--sl-letter-spacing-dense);">The quick brown fox jumped over the lazy dog.</span> |
|
||||
| `--sl-letter-spacing-normal` | normal | <span style="letter-spacing: var(--sl-letter-spacing-normal);">The quick brown fox jumped over the lazy dog.</span> |
|
||||
| `--sl-letter-spacing-loose` | 0.075em | <span style="letter-spacing: var(--sl-letter-spacing-loose);">The quick brown fox jumped over the lazy dog.</span> |
|
||||
| `--sl-letter-spacing-looser` | ? | <span style="letter-spacing: var(--sl-letter-spacing-looser);">The quick brown fox jumped over the lazy dog.</span> |
|
||||
| `--sl-letter-spacing-looser` | 0.15em | <span style="letter-spacing: var(--sl-letter-spacing-looser);">The quick brown fox jumped over the lazy dog.</span> |
|
||||
|
||||
## Line Height
|
||||
|
||||
| Token | Value | Example |
|
||||
| ------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `--sl-line-height-denser` | ? | <div style="line-height: var(--sl-line-height-denser);">The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.</div> |
|
||||
| `--sl-line-height-denser` | 1 | <div style="line-height: var(--sl-line-height-denser);">The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.</div> |
|
||||
| `--sl-line-height-dense` | 1.4 | <div style="line-height: var(--sl-line-height-dense);">The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.</div> |
|
||||
| `--sl-line-height-normal` | 1.8 | <div style="line-height: var(--sl-line-height-normal);">The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.</div> |
|
||||
| `--sl-line-height-loose` | 2.2 | <div style="line-height: var(--sl-line-height-loose);">The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.</div> |
|
||||
| `--sl-line-height-looser` | ? | <div style="line-height: var(--sl-line-height-looser);">The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.</div> |
|
||||
| `--sl-line-height-looser` | 2.6 | <div style="line-height: var(--sl-line-height-looser);">The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.</div> |
|
||||
|
||||
7470
package-lock.json
generated
7470
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
96
package.json
96
package.json
@@ -1,16 +1,26 @@
|
||||
{
|
||||
"name": "@shoelace-style/shoelace",
|
||||
"description": "A forward-thinking library of web components.",
|
||||
"version": "2.0.0-beta.83",
|
||||
"version": "2.1.0",
|
||||
"homepage": "https://github.com/shoelace-style/shoelace",
|
||||
"author": "Cory LaViska",
|
||||
"license": "MIT",
|
||||
"main": "dist/shoelace.js",
|
||||
"module": "dist/shoelace.js",
|
||||
"customElements": "dist/custom-elements.json",
|
||||
"web-types": "dist/web-types.json",
|
||||
"type": "module",
|
||||
"types": "dist/shoelace.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/shoelace.d.ts",
|
||||
"import": "./dist/shoelace.js"
|
||||
},
|
||||
"./dist/themes/*": "./dist/themes/*",
|
||||
"./dist/components/*": "./dist/components/*",
|
||||
"./dist/utilities/*": "./dist/utilities/*",
|
||||
"./dist/react": "./dist/react/index.js",
|
||||
"./dist/react/*": "./dist/react/*",
|
||||
"./dist/translations/*": "./dist/translations/*"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
@@ -41,72 +51,74 @@
|
||||
"lint:fix": "eslint src --max-warnings 0 --fix",
|
||||
"ts-check": "tsc --noEmit --project ./tsconfig.json",
|
||||
"create": "plop --plopfile scripts/plop/plopfile.js",
|
||||
"test": "web-test-runner",
|
||||
"test:component": "npm run test -- --group",
|
||||
"test:watch": "web-test-runner --watch",
|
||||
"test": "web-test-runner --group default",
|
||||
"test:component": "web-test-runner -- --watch --group",
|
||||
"test:watch": "web-test-runner --watch --group default",
|
||||
"spellcheck": "cspell \"**/*.{js,ts,json,html,css,md}\" --no-progress",
|
||||
"list-outdated-dependencies": "npm-check-updates --format repo --peer",
|
||||
"update-dependencies": "npm-check-updates --peer -u && npm install && npm run lint:fix && npm run prettier && npm run verify"
|
||||
"update-dependencies": "npm-check-updates --peer -u && npm install"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.0.0",
|
||||
"@lit-labs/react": "^1.0.7",
|
||||
"@ctrl/tinycolor": "^3.5.0",
|
||||
"@floating-ui/dom": "^1.1.0",
|
||||
"@lit-labs/react": "^1.1.1",
|
||||
"@shoelace-style/animations": "^1.1.0",
|
||||
"@shoelace-style/localize": "^3.0.1",
|
||||
"color": "4.2",
|
||||
"lit": "^2.2.8",
|
||||
"@shoelace-style/localize": "^3.0.4",
|
||||
"lit": "^2.6.1",
|
||||
"qr-creator": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@custom-elements-manifest/analyzer": "^0.6.4",
|
||||
"@open-wc/testing": "^3.1.6",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"@types/react": "^18.0.17",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
||||
"@typescript-eslint/parser": "^5.33.0",
|
||||
"@web/dev-server-esbuild": "^0.3.2",
|
||||
"@web/test-runner": "^0.14.0",
|
||||
"@web/test-runner-commands": "^0.6.4",
|
||||
"@web/test-runner-playwright": "^0.8.10",
|
||||
"bootstrap-icons": "^1.9.1",
|
||||
"browser-sync": "^2.27.10",
|
||||
"chalk": "^5.0.1",
|
||||
"@custom-elements-manifest/analyzer": "^0.6.8",
|
||||
"@open-wc/testing": "^3.1.7",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/react": "^18.0.26",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
||||
"@typescript-eslint/parser": "^5.48.1",
|
||||
"@web/dev-server-esbuild": "^0.3.3",
|
||||
"@web/test-runner": "^0.15.0",
|
||||
"@web/test-runner-commands": "^0.6.5",
|
||||
"@web/test-runner-playwright": "^0.9.0",
|
||||
"bootstrap-icons": "^1.10.3",
|
||||
"browser-sync": "^2.27.11",
|
||||
"cem-plugin-vs-code-custom-data-generator": "^1.4.1",
|
||||
"chalk": "^5.2.0",
|
||||
"command-line-args": "^5.2.1",
|
||||
"comment-parser": "^1.3.1",
|
||||
"cspell": "^6.6.1",
|
||||
"cspell": "^6.18.1",
|
||||
"del": "^7.0.0",
|
||||
"download": "^8.0.0",
|
||||
"esbuild": "^0.15.1",
|
||||
"eslint": "^8.21.0",
|
||||
"esbuild": "^0.16.17",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-plugin-chai-expect": "^3.0.0",
|
||||
"eslint-plugin-chai-friendly": "^0.7.2",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-lit": "^1.6.1",
|
||||
"eslint-plugin-lit-a11y": "^2.2.2",
|
||||
"eslint-plugin-import": "^2.27.4",
|
||||
"eslint-plugin-lit": "^1.8.2",
|
||||
"eslint-plugin-lit-a11y": "^2.3.0",
|
||||
"eslint-plugin-markdown": "^3.0.0",
|
||||
"eslint-plugin-wc": "^1.3.2",
|
||||
"eslint-plugin-sort-imports-es6-autofix": "^0.6.0",
|
||||
"eslint-plugin-wc": "^1.4.0",
|
||||
"front-matter": "^4.0.2",
|
||||
"get-port": "^6.1.2",
|
||||
"globby": "^13.1.2",
|
||||
"husky": "^8.0.1",
|
||||
"jsonata": "^1.8.6",
|
||||
"lint-staged": "^13.0.3",
|
||||
"globby": "^13.1.3",
|
||||
"husky": "^8.0.3",
|
||||
"jsonata": "^2.0.1",
|
||||
"lint-staged": "^13.1.0",
|
||||
"lunr": "^2.3.9",
|
||||
"npm-check-updates": "^16.0.5",
|
||||
"npm-check-updates": "^16.6.2",
|
||||
"open": "^8.4.0",
|
||||
"pascal-case": "^3.1.2",
|
||||
"plop": "^3.1.1",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier": "^2.8.2",
|
||||
"react": "^18.2.0",
|
||||
"recursive-copy": "^2.0.14",
|
||||
"sinon": "^14.0.0",
|
||||
"sinon": "^15.0.1",
|
||||
"source-map": "^0.7.4",
|
||||
"strip-css-comments": "^5.0.0",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "4.7.4",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "4.9.4",
|
||||
"user-agent-data-types": "^0.3.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
||||
@@ -28,7 +28,6 @@ fs.mkdirSync(outdir, { recursive: true });
|
||||
execSync(`node scripts/make-metadata.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
||||
execSync(`node scripts/make-search.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
||||
execSync(`node scripts/make-react.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
||||
execSync(`node scripts/make-vscode-data.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
||||
execSync(`node scripts/make-web-types.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
||||
execSync(`node scripts/make-themes.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
||||
execSync(`node scripts/make-icons.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
||||
@@ -47,6 +46,9 @@ fs.mkdirSync(outdir, { recursive: true });
|
||||
format: 'esm',
|
||||
target: 'es2017',
|
||||
entryPoints: [
|
||||
//
|
||||
// NOTE: Entry points must be mapped in package.json > exports, otherwise users won't be able to import them!
|
||||
//
|
||||
// The whole shebang
|
||||
'./src/shoelace.ts',
|
||||
// Components
|
||||
@@ -118,36 +120,14 @@ fs.mkdirSync(outdir, { recursive: true });
|
||||
routes: {
|
||||
'/dist': './dist'
|
||||
}
|
||||
},
|
||||
socket: {
|
||||
socketIoClientConfig: {
|
||||
// Configure socketIO to retry forever when disconnected to enable the auto-reattach timeout below to work
|
||||
reconnectionAttempts: Infinity,
|
||||
reconnectionDelay: 500,
|
||||
reconnectionDelayMax: 500,
|
||||
timeout: 1000
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Launch browser sync
|
||||
bs.init(browserSyncConfig, () => {
|
||||
// This init callback gets executed after the server has started
|
||||
const socketIoConfig = browserSyncConfig.socket.socketIoClientConfig;
|
||||
|
||||
// Wait enough time for any open, detached clients to have a chance to reconnect. This will be used to determine
|
||||
// if we reload an existing tab or open a new one.
|
||||
const tabReattachDelay = socketIoConfig.reconnectionDelayMax * 2 + socketIoConfig.timeout;
|
||||
|
||||
setTimeout(() => {
|
||||
const url = `http://localhost:${port}`;
|
||||
console.log(chalk.cyan(`Launched the Shoelace dev server at ${url} 🥾\n`));
|
||||
if (Object.keys(bs.sockets.sockets).length === 0) {
|
||||
open(url);
|
||||
} else {
|
||||
bs.reload();
|
||||
}
|
||||
}, tabReattachDelay);
|
||||
const url = `http://localhost:${port}`;
|
||||
console.log(chalk.cyan(`Launched the Shoelace dev server at ${url} 🥾\n`));
|
||||
open(url);
|
||||
});
|
||||
|
||||
// Rebuild and reload when source files change
|
||||
|
||||
@@ -3,7 +3,6 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import { deleteSync } from 'del';
|
||||
import { pascalCase } from 'pascal-case';
|
||||
import prettier from 'prettier';
|
||||
import prettierConfig from '../prettier.config.cjs';
|
||||
import { getAllComponents } from './shared.js';
|
||||
@@ -29,7 +28,7 @@ components.map(component => {
|
||||
const tagWithoutPrefix = component.tagName.replace(/^sl-/, '');
|
||||
const componentDir = path.join(reactDir, tagWithoutPrefix);
|
||||
const componentFile = path.join(componentDir, 'index.ts');
|
||||
const importPath = component.modulePath.replace(/^src\//, '').replace(/\.ts$/, '');
|
||||
const importPath = component.path;
|
||||
const events = (component.events || []).map(event => `${event.reactName}: '${event.name}'`).join(',\n');
|
||||
|
||||
fs.mkdirSync(componentDir, { recursive: true });
|
||||
@@ -40,14 +39,14 @@ components.map(component => {
|
||||
import { createComponent } from '@lit-labs/react';
|
||||
import Component from '../../${importPath}';
|
||||
|
||||
export default createComponent(
|
||||
React,
|
||||
'${component.tagName}',
|
||||
Component,
|
||||
{
|
||||
export default createComponent({
|
||||
tagName: '${component.tagName}',
|
||||
elementClass: Component,
|
||||
react: React,
|
||||
events: {
|
||||
${events}
|
||||
}
|
||||
);
|
||||
});
|
||||
`,
|
||||
Object.assign(prettierConfig, {
|
||||
parser: 'babel-ts'
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
//
|
||||
// This script generates vscode.html-custom-data.json (for IntelliSense).
|
||||
//
|
||||
// You must generate dist/custom-elements.json before running this script.
|
||||
//
|
||||
import commandLineArgs from 'command-line-args';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { getAllComponents } from './shared.js';
|
||||
|
||||
const { outdir } = commandLineArgs({ name: 'outdir', type: String });
|
||||
const metadata = JSON.parse(fs.readFileSync(path.join(outdir, 'custom-elements.json'), 'utf8'));
|
||||
|
||||
console.log('Generating custom data for VS Code');
|
||||
|
||||
const components = getAllComponents(metadata);
|
||||
const vscode = { tags: [] };
|
||||
|
||||
components.map(component => {
|
||||
const name = component.tagName;
|
||||
const attributes = component.attributes?.map(attr => {
|
||||
const type = attr.type?.text;
|
||||
let values = [];
|
||||
|
||||
if (type) {
|
||||
type.split('|').map(val => {
|
||||
val = val.trim();
|
||||
|
||||
// Only accept values that are strings and numbers
|
||||
const isString = val.startsWith(`'`);
|
||||
const isNumber = Number(val).toString() === val;
|
||||
|
||||
if (isString) {
|
||||
// Remove quotes
|
||||
val = val.replace(/^'/, '').replace(/'$/, '');
|
||||
}
|
||||
|
||||
if (isNumber || isString) {
|
||||
values.push({ name: val });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (values.length === 0) {
|
||||
values = undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
name: attr.name,
|
||||
description: attr.description,
|
||||
values
|
||||
};
|
||||
});
|
||||
|
||||
vscode.tags.push({ name, attributes });
|
||||
});
|
||||
|
||||
fs.writeFileSync(path.join(outdir, 'vscode.html-custom-data.json'), JSON.stringify(vscode, null, 2), 'utf8');
|
||||
@@ -64,6 +64,7 @@ const jsonataExprString = `{
|
||||
|
||||
// Run the conversion
|
||||
const expression = jsonata(jsonataExprString);
|
||||
const result = await expression.evaluate(metadata);
|
||||
|
||||
console.log('Generating web types');
|
||||
fs.writeFileSync(path.join(outdir, 'web-types.json'), JSON.stringify(expression.evaluate(metadata), null, 2), 'utf8');
|
||||
fs.writeFileSync(path.join(outdir, 'web-types.json'), JSON.stringify(result, null, 2), 'utf8');
|
||||
|
||||
@@ -7,8 +7,10 @@ import styles from './{{ tagWithoutPrefix tag }}.styles';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
* @summary Short summary of the component's intended use.
|
||||
* @documentation https://shoelace.style/components/{{ tagWithoutPrefix tag }}
|
||||
* @status experimental
|
||||
* @since 2.0
|
||||
*
|
||||
* @dependency sl-example
|
||||
*
|
||||
@@ -17,7 +19,7 @@ import type { CSSResultGroup } from 'lit';
|
||||
* @slot - The default slot.
|
||||
* @slot example - An example slot.
|
||||
*
|
||||
* @csspart base - The component's internal wrapper.
|
||||
* @csspart base - The component's base wrapper.
|
||||
*
|
||||
* @cssproperty --example - An example CSS custom property.
|
||||
*/
|
||||
@@ -27,10 +29,10 @@ export default class {{ properCase tag }} extends ShoelaceElement {
|
||||
|
||||
private readonly localize = new LocalizeController(this);
|
||||
|
||||
/** An example property. */
|
||||
@property() prop = 'example';
|
||||
/** An example attribute. */
|
||||
@property() attr = 'example';
|
||||
|
||||
@watch('someProp')
|
||||
@watch('someProperty')
|
||||
doSomething() {
|
||||
// Example event
|
||||
this.emit('sl-event-name');
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/** Gets an array of components from a CEM object. */
|
||||
export function getAllComponents(metadata) {
|
||||
const allComponents = [];
|
||||
|
||||
@@ -5,10 +6,10 @@ export function getAllComponents(metadata) {
|
||||
module.declarations?.map(declaration => {
|
||||
if (declaration.customElement) {
|
||||
const component = declaration;
|
||||
const modulePath = module.path;
|
||||
const path = module.path;
|
||||
|
||||
if (component) {
|
||||
allComponents.push(Object.assign(component, { modulePath }));
|
||||
allComponents.push(Object.assign(component, { path }));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -19,7 +19,6 @@ export default css`
|
||||
border: solid var(--sl-panel-border-width) var(--sl-panel-border-color);
|
||||
border-top-width: calc(var(--sl-panel-border-width) * 3);
|
||||
border-radius: var(--sl-border-radius-medium);
|
||||
box-shadow: var(--box-shadow);
|
||||
font-family: var(--sl-font-sans);
|
||||
font-size: var(--sl-font-size-small);
|
||||
font-weight: var(--sl-font-weight-normal);
|
||||
@@ -83,6 +82,7 @@ export default css`
|
||||
|
||||
.alert__message {
|
||||
flex: 1 1 auto;
|
||||
display: block;
|
||||
padding: var(--sl-spacing-large);
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -91,7 +91,7 @@ export default css`
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: var(--sl-font-size-large);
|
||||
font-size: var(--sl-font-size-medium);
|
||||
padding-inline-end: var(--sl-spacing-medium);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,91 +1,305 @@
|
||||
import { expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import { aTimeout, expect, fixture, html, oneEvent } from '@open-wc/testing';
|
||||
import { clickOnElement, moveMouseOnElement } from '../../internal/test';
|
||||
import { queryByTestId } from '../../internal/test/data-testid-helpers';
|
||||
import { resetMouse } from '@web/test-runner-commands';
|
||||
import sinon from 'sinon';
|
||||
import type SlAlert from './alert';
|
||||
import type SlIconButton from '../icon-button/icon-button';
|
||||
|
||||
const getAlertContainer = (alert: SlAlert): HTMLElement => {
|
||||
return alert.shadowRoot!.querySelector<HTMLElement>('[part="base"]')!;
|
||||
};
|
||||
|
||||
const expectAlertToBeVisible = (alert: SlAlert): void => {
|
||||
const alertContainer = getAlertContainer(alert);
|
||||
const style = window.getComputedStyle(alertContainer);
|
||||
expect(style.display).not.to.equal('none');
|
||||
expect(style.visibility).not.to.equal('hidden');
|
||||
expect(style.visibility).not.to.equal('collapse');
|
||||
};
|
||||
|
||||
const expectAlertToBeInvisible = (alert: SlAlert): void => {
|
||||
const alertContainer = getAlertContainer(alert);
|
||||
const style = window.getComputedStyle(alertContainer);
|
||||
expect(style.display, 'alert should be invisible').to.equal('none');
|
||||
};
|
||||
|
||||
const expectHideAndAfterHideToBeEmittedInCorrectOrder = async (alert: SlAlert, action: () => void | Promise<void>) => {
|
||||
const hidePromise = oneEvent(alert, 'sl-hide');
|
||||
const afterHidePromise = oneEvent(alert, 'sl-after-hide');
|
||||
let afterHideHappened = false;
|
||||
oneEvent(alert, 'sl-after-hide').then(() => (afterHideHappened = true));
|
||||
|
||||
action();
|
||||
|
||||
await hidePromise;
|
||||
expect(afterHideHappened).to.be.false;
|
||||
|
||||
await afterHidePromise;
|
||||
expectAlertToBeInvisible(alert);
|
||||
};
|
||||
|
||||
const expectShowAndAfterShowToBeEmittedInCorrectOrder = async (alert: SlAlert, action: () => void | Promise<void>) => {
|
||||
const showPromise = oneEvent(alert, 'sl-show');
|
||||
const afterShowPromise = oneEvent(alert, 'sl-after-show');
|
||||
let afterShowHappened = false;
|
||||
oneEvent(alert, 'sl-after-show').then(() => (afterShowHappened = true));
|
||||
|
||||
action();
|
||||
|
||||
await showPromise;
|
||||
expect(afterShowHappened).to.be.false;
|
||||
|
||||
await afterShowPromise;
|
||||
expectAlertToBeVisible(alert);
|
||||
};
|
||||
|
||||
const getCloseButton = (alert: SlAlert): SlIconButton | null | undefined =>
|
||||
alert.shadowRoot?.querySelector<SlIconButton>('[part="close-button"]');
|
||||
|
||||
describe('<sl-alert>', () => {
|
||||
it('should be visible with the open attribute', async () => {
|
||||
const el = await fixture<SlAlert>(html` <sl-alert open>I am an alert</sl-alert> `);
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part="base"]')!;
|
||||
let clock: sinon.SinonFakeTimers | null = null;
|
||||
|
||||
expect(base.hidden).to.be.false;
|
||||
afterEach(async () => {
|
||||
clock?.restore();
|
||||
await resetMouse();
|
||||
});
|
||||
|
||||
it('should not be visible without the open attribute', async () => {
|
||||
const el = await fixture<SlAlert>(html` <sl-alert>I am an alert</sl-alert> `);
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part="base"]')!;
|
||||
it('renders', async () => {
|
||||
const alert = await fixture<SlAlert>(html`<sl-alert open>I am an alert</sl-alert>`);
|
||||
|
||||
expect(base.hidden).to.be.true;
|
||||
expectAlertToBeVisible(alert);
|
||||
});
|
||||
|
||||
it('should emit sl-show and sl-after-show when calling show()', async () => {
|
||||
const el = await fixture<SlAlert>(html` <sl-alert>I am an alert</sl-alert> `);
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part="base"]')!;
|
||||
const showHandler = sinon.spy();
|
||||
const afterShowHandler = sinon.spy();
|
||||
it('is accessible', async () => {
|
||||
const alert = await fixture<SlAlert>(html`<sl-alert open>I am an alert</sl-alert>`);
|
||||
|
||||
el.addEventListener('sl-show', showHandler);
|
||||
el.addEventListener('sl-after-show', afterShowHandler);
|
||||
el.show();
|
||||
|
||||
await waitUntil(() => showHandler.calledOnce);
|
||||
await waitUntil(() => afterShowHandler.calledOnce);
|
||||
|
||||
expect(showHandler).to.have.been.calledOnce;
|
||||
expect(afterShowHandler).to.have.been.calledOnce;
|
||||
expect(base.hidden).to.be.false;
|
||||
await expect(alert).to.be.accessible();
|
||||
});
|
||||
|
||||
it('should emit sl-hide and sl-after-hide when calling hide()', async () => {
|
||||
const el = await fixture<SlAlert>(html` <sl-alert open>I am an alert</sl-alert> `);
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part="base"]')!;
|
||||
const hideHandler = sinon.spy();
|
||||
const afterHideHandler = sinon.spy();
|
||||
describe('alert visibility', () => {
|
||||
it('should be visible with the open attribute', async () => {
|
||||
const alert = await fixture<SlAlert>(html`<sl-alert open>I am an alert</sl-alert>`);
|
||||
|
||||
el.addEventListener('sl-hide', hideHandler);
|
||||
el.addEventListener('sl-after-hide', afterHideHandler);
|
||||
el.hide();
|
||||
expectAlertToBeVisible(alert);
|
||||
});
|
||||
|
||||
await waitUntil(() => hideHandler.calledOnce);
|
||||
await waitUntil(() => afterHideHandler.calledOnce);
|
||||
it('should not be visible without the open attribute', async () => {
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert>I am an alert</sl-alert>`);
|
||||
|
||||
expect(hideHandler).to.have.been.calledOnce;
|
||||
expect(afterHideHandler).to.have.been.calledOnce;
|
||||
expect(base.hidden).to.be.true;
|
||||
expectAlertToBeInvisible(alert);
|
||||
});
|
||||
|
||||
it('should emit sl-show and sl-after-show when calling show()', async () => {
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert>I am an alert</sl-alert>`);
|
||||
|
||||
expectAlertToBeInvisible(alert);
|
||||
|
||||
await expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, () => alert.show());
|
||||
});
|
||||
|
||||
it('should emit sl-hide and sl-after-hide when calling hide()', async () => {
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert open>I am an alert</sl-alert>`);
|
||||
|
||||
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => alert.hide());
|
||||
});
|
||||
|
||||
it('should emit sl-show and sl-after-show when setting open = true', async () => {
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert>I am an alert</sl-alert> `);
|
||||
|
||||
await expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, () => {
|
||||
alert.open = true;
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit sl-hide and sl-after-hide when setting open = false', async () => {
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert open>I am an alert</sl-alert> `);
|
||||
|
||||
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {
|
||||
alert.open = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit sl-show and sl-after-show when setting open = true', async () => {
|
||||
const el = await fixture<SlAlert>(html` <sl-alert>I am an alert</sl-alert> `);
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part="base"]')!;
|
||||
const showHandler = sinon.spy();
|
||||
const afterShowHandler = sinon.spy();
|
||||
describe('close button', () => {
|
||||
it('shows a close button if the alert has the closable attribute', () => async () => {
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert open closable>I am an alert</sl-alert> `);
|
||||
const closeButton = getCloseButton(alert);
|
||||
|
||||
el.addEventListener('sl-show', showHandler);
|
||||
el.addEventListener('sl-after-show', afterShowHandler);
|
||||
el.open = true;
|
||||
expect(closeButton).to.be.visible;
|
||||
});
|
||||
|
||||
await waitUntil(() => showHandler.calledOnce);
|
||||
await waitUntil(() => afterShowHandler.calledOnce);
|
||||
it('clicking the close button closes the alert', () => async () => {
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert open closable>I am an alert</sl-alert> `);
|
||||
const closeButton = getCloseButton(alert);
|
||||
|
||||
expect(showHandler).to.have.been.calledOnce;
|
||||
expect(afterShowHandler).to.have.been.calledOnce;
|
||||
expect(base.hidden).to.be.false;
|
||||
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {
|
||||
clickOnElement(closeButton!);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit sl-hide and sl-after-hide when setting open = false', async () => {
|
||||
const el = await fixture<SlAlert>(html` <sl-alert open>I am an alert</sl-alert> `);
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part="base"]')!;
|
||||
const hideHandler = sinon.spy();
|
||||
const afterHideHandler = sinon.spy();
|
||||
describe('toast', () => {
|
||||
const getToastStack = (): HTMLDivElement | null => document.querySelector<HTMLDivElement>('.sl-toast-stack');
|
||||
|
||||
el.addEventListener('sl-hide', hideHandler);
|
||||
el.addEventListener('sl-after-hide', afterHideHandler);
|
||||
el.open = false;
|
||||
const closeRemainingAlerts = async (): Promise<void> => {
|
||||
const toastStack = getToastStack();
|
||||
if (toastStack?.children) {
|
||||
for (const element of toastStack.children) {
|
||||
await (element as SlAlert).hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await waitUntil(() => hideHandler.calledOnce);
|
||||
await waitUntil(() => afterHideHandler.calledOnce);
|
||||
beforeEach(async () => {
|
||||
await closeRemainingAlerts();
|
||||
});
|
||||
|
||||
expect(hideHandler).to.have.been.calledOnce;
|
||||
expect(afterHideHandler).to.have.been.calledOnce;
|
||||
expect(base.hidden).to.be.true;
|
||||
it('can be rendered as a toast', async () => {
|
||||
const alert = await fixture<SlAlert>(html`<sl-alert>I am an alert</sl-alert>`);
|
||||
|
||||
expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, () => alert.toast());
|
||||
const toastStack = getToastStack();
|
||||
expect(toastStack).to.be.visible;
|
||||
expect(toastStack?.firstChild).to.be.equal(alert);
|
||||
});
|
||||
|
||||
it('resolves only after being closed', async () => {
|
||||
const alert = await fixture<SlAlert>(html`<sl-alert closable>I am an alert</sl-alert>`);
|
||||
|
||||
const afterShowEvent = oneEvent(alert, 'sl-after-show');
|
||||
let toastPromiseResolved = false;
|
||||
alert.toast().then(() => (toastPromiseResolved = true));
|
||||
|
||||
await afterShowEvent;
|
||||
expect(toastPromiseResolved).to.be.false;
|
||||
|
||||
const closePromise = oneEvent(alert, 'sl-after-hide');
|
||||
const closeButton = getCloseButton(alert);
|
||||
clickOnElement(closeButton!);
|
||||
|
||||
await closePromise;
|
||||
await aTimeout(0);
|
||||
|
||||
expect(toastPromiseResolved).to.be.true;
|
||||
});
|
||||
|
||||
const expectToastStack = () => {
|
||||
const toastStack = getToastStack();
|
||||
expect(toastStack).not.to.be.null;
|
||||
};
|
||||
|
||||
const expectNoToastStack = () => {
|
||||
const toastStack = getToastStack();
|
||||
expect(toastStack).to.be.null;
|
||||
};
|
||||
|
||||
const openToast = async (alert: SlAlert): Promise<void> => {
|
||||
const openPromise = oneEvent(alert, 'sl-after-show');
|
||||
alert.toast();
|
||||
await openPromise;
|
||||
};
|
||||
|
||||
const closeToast = async (alert: SlAlert): Promise<void> => {
|
||||
const closePromise = oneEvent(alert, 'sl-after-hide');
|
||||
const closeButton = getCloseButton(alert);
|
||||
await clickOnElement(closeButton!);
|
||||
await closePromise;
|
||||
await aTimeout(0);
|
||||
};
|
||||
|
||||
it('deletes the toast stack after the last alert is done', async () => {
|
||||
const container = await fixture<HTMLElement>(html`<div>
|
||||
<sl-alert data-testid="alert1" closable>alert 1</sl-alert>
|
||||
<sl-alert data-testid="alert2" closable>alert 2</sl-alert>
|
||||
</div>`);
|
||||
|
||||
const alert1 = queryByTestId<SlAlert>(container, 'alert1');
|
||||
const alert2 = queryByTestId<SlAlert>(container, 'alert2');
|
||||
|
||||
await openToast(alert1!);
|
||||
|
||||
expectToastStack();
|
||||
|
||||
await openToast(alert2!);
|
||||
|
||||
expectToastStack();
|
||||
|
||||
await closeToast(alert1!);
|
||||
|
||||
expectToastStack();
|
||||
|
||||
await closeToast(alert2!);
|
||||
|
||||
expectNoToastStack();
|
||||
});
|
||||
});
|
||||
|
||||
describe('timer controlled closing', () => {
|
||||
it('closes after a predefined amount of time', async () => {
|
||||
clock = sinon.useFakeTimers();
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert open duration="3000">I am an alert</sl-alert>`);
|
||||
|
||||
expectAlertToBeVisible(alert);
|
||||
|
||||
clock.tick(2999);
|
||||
|
||||
expectAlertToBeVisible(alert);
|
||||
|
||||
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {
|
||||
clock?.tick(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('resets the closing timer after mouse-over', async () => {
|
||||
clock = sinon.useFakeTimers();
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert open duration="3000">I am an alert</sl-alert>`);
|
||||
|
||||
expectAlertToBeVisible(alert);
|
||||
|
||||
clock.tick(1000);
|
||||
|
||||
await moveMouseOnElement(alert);
|
||||
|
||||
clock.tick(2999);
|
||||
|
||||
expectAlertToBeVisible(alert);
|
||||
|
||||
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {
|
||||
clock?.tick(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('resets the closing timer after opening', async () => {
|
||||
clock = sinon.useFakeTimers();
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert duration="3000">I am an alert</sl-alert>`);
|
||||
|
||||
expectAlertToBeInvisible(alert);
|
||||
|
||||
clock.tick(1000);
|
||||
|
||||
const afterShowPromise = oneEvent(alert, 'sl-after-show');
|
||||
alert.show();
|
||||
await afterShowPromise;
|
||||
|
||||
clock.tick(2999);
|
||||
|
||||
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {
|
||||
clock?.tick(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('alert variants', () => {
|
||||
const variants = ['primary', 'success', 'neutral', 'warning', 'danger'];
|
||||
|
||||
variants.forEach(variant => {
|
||||
it(`adapts to the variant: ${variant}`, async () => {
|
||||
const alert = await fixture<SlAlert>(html`<sl-alert variant="${variant}" open>I am an alert</sl-alert>`);
|
||||
|
||||
const alertContainer = getAlertContainer(alert);
|
||||
expect(alertContainer).to.have.class(`alert--${variant}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
import { html } from 'lit';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { animateTo, stopAnimations } from '../../internal/animate';
|
||||
import { waitForEvent } from '../../internal/event';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import { HasSlotController } from '../../internal/slot';
|
||||
import { watch } from '../../internal/watch';
|
||||
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry';
|
||||
import { LocalizeController } from '../../utilities/localize';
|
||||
import '../icon-button/icon-button';
|
||||
import { animateTo, stopAnimations } from '../../internal/animate';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry';
|
||||
import { HasSlotController } from '../../internal/slot';
|
||||
import { html } from 'lit';
|
||||
import { LocalizeController } from '../../utilities/localize';
|
||||
import { waitForEvent } from '../../internal/event';
|
||||
import { watch } from '../../internal/watch';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './alert.styles';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
const toastStack = Object.assign(document.createElement('div'), { className: 'sl-toast-stack' });
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
* @summary Alerts are used to display important messages inline or as toast notifications.
|
||||
* @documentation https://shoelace.style/components/alert
|
||||
* @status stable
|
||||
* @since 2.0
|
||||
*
|
||||
* @dependency sl-icon-button
|
||||
*
|
||||
* @slot - The alert's content.
|
||||
* @slot icon - An icon to show in the alert.
|
||||
* @slot - The alert's main content.
|
||||
* @slot icon - An icon to show in the alert. Works best with `<sl-icon>`.
|
||||
*
|
||||
* @event sl-show - Emitted when the alert opens.
|
||||
* @event sl-after-show - Emitted after the alert opens and all animations are complete.
|
||||
* @event sl-hide - Emitted when the alert closes.
|
||||
* @event sl-after-hide - Emitted after the alert closes and all animations are complete.
|
||||
*
|
||||
* @csspart base - The component's internal wrapper.
|
||||
* @csspart icon - The container that wraps the alert icon.
|
||||
* @csspart message - The alert message.
|
||||
* @csspart close-button - The close button.
|
||||
* @csspart close-button__base - The close button's `base` part.
|
||||
*
|
||||
* @cssproperty --box-shadow - The alert's box shadow.
|
||||
* @csspart base - The component's base wrapper.
|
||||
* @csspart icon - The container that wraps the optional icon.
|
||||
* @csspart message - The container that wraps the alert's main content.
|
||||
* @csspart close-button - The close button, an `<sl-icon-button>`.
|
||||
* @csspart close-button__base - The close button's exported `base` part.
|
||||
*
|
||||
* @animation alert.show - The animation to use when showing the alert.
|
||||
* @animation alert.hide - The animation to use when hiding the alert.
|
||||
@@ -48,20 +48,24 @@ export default class SlAlert extends ShoelaceElement {
|
||||
private readonly hasSlotController = new HasSlotController(this, 'icon', 'suffix');
|
||||
private readonly localize = new LocalizeController(this);
|
||||
|
||||
@query('[part="base"]') base: HTMLElement;
|
||||
@query('[part~="base"]') base: HTMLElement;
|
||||
|
||||
/** Indicates whether or not the alert is open. You can use this in lieu of the show/hide methods. */
|
||||
/**
|
||||
* Indicates whether or not the alert is open. You can toggle this attribute to show and hide the alert, or you can
|
||||
* use the `show()` and `hide()` methods and this attribute will reflect the alert's open state.
|
||||
*/
|
||||
@property({ type: Boolean, reflect: true }) open = false;
|
||||
|
||||
/** Makes the alert closable. */
|
||||
/** Enables a close button that allows the user to dismiss the alert. */
|
||||
@property({ type: Boolean, reflect: true }) closable = false;
|
||||
|
||||
/** The alert's variant. */
|
||||
/** The alert's theme variant. */
|
||||
@property({ reflect: true }) variant: 'primary' | 'success' | 'neutral' | 'warning' | 'danger' = 'primary';
|
||||
|
||||
/**
|
||||
* The length of time, in milliseconds, the alert will show before closing itself. If the user interacts with
|
||||
* the alert before it closes (e.g. moves the mouse over it), the timer will restart. Defaults to `Infinity`.
|
||||
* the alert before it closes (e.g. moves the mouse over it), the timer will restart. Defaults to `Infinity`, meaning
|
||||
* the alert will not close on its own.
|
||||
*/
|
||||
@property({ type: Number }) duration = Infinity;
|
||||
|
||||
@@ -69,6 +73,57 @@ export default class SlAlert extends ShoelaceElement {
|
||||
this.base.hidden = !this.open;
|
||||
}
|
||||
|
||||
private restartAutoHide() {
|
||||
clearTimeout(this.autoHideTimeout);
|
||||
if (this.open && this.duration < Infinity) {
|
||||
this.autoHideTimeout = window.setTimeout(() => this.hide(), this.duration);
|
||||
}
|
||||
}
|
||||
|
||||
private handleCloseClick() {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
private handleMouseMove() {
|
||||
this.restartAutoHide();
|
||||
}
|
||||
|
||||
@watch('open', { waitUntilFirstUpdate: true })
|
||||
async handleOpenChange() {
|
||||
if (this.open) {
|
||||
// Show
|
||||
this.emit('sl-show');
|
||||
|
||||
if (this.duration < Infinity) {
|
||||
this.restartAutoHide();
|
||||
}
|
||||
|
||||
await stopAnimations(this.base);
|
||||
this.base.hidden = false;
|
||||
const { keyframes, options } = getAnimation(this, 'alert.show', { dir: this.localize.dir() });
|
||||
await animateTo(this.base, keyframes, options);
|
||||
|
||||
this.emit('sl-after-show');
|
||||
} else {
|
||||
// Hide
|
||||
this.emit('sl-hide');
|
||||
|
||||
clearTimeout(this.autoHideTimeout);
|
||||
|
||||
await stopAnimations(this.base);
|
||||
const { keyframes, options } = getAnimation(this, 'alert.hide', { dir: this.localize.dir() });
|
||||
await animateTo(this.base, keyframes, options);
|
||||
this.base.hidden = true;
|
||||
|
||||
this.emit('sl-after-hide');
|
||||
}
|
||||
}
|
||||
|
||||
@watch('duration')
|
||||
handleDurationChange() {
|
||||
this.restartAutoHide();
|
||||
}
|
||||
|
||||
/** Shows the alert. */
|
||||
async show() {
|
||||
if (this.open) {
|
||||
@@ -125,57 +180,6 @@ export default class SlAlert extends ShoelaceElement {
|
||||
});
|
||||
}
|
||||
|
||||
restartAutoHide() {
|
||||
clearTimeout(this.autoHideTimeout);
|
||||
if (this.open && this.duration < Infinity) {
|
||||
this.autoHideTimeout = window.setTimeout(() => this.hide(), this.duration);
|
||||
}
|
||||
}
|
||||
|
||||
handleCloseClick() {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
handleMouseMove() {
|
||||
this.restartAutoHide();
|
||||
}
|
||||
|
||||
@watch('open', { waitUntilFirstUpdate: true })
|
||||
async handleOpenChange() {
|
||||
if (this.open) {
|
||||
// Show
|
||||
this.emit('sl-show');
|
||||
|
||||
if (this.duration < Infinity) {
|
||||
this.restartAutoHide();
|
||||
}
|
||||
|
||||
await stopAnimations(this.base);
|
||||
this.base.hidden = false;
|
||||
const { keyframes, options } = getAnimation(this, 'alert.show', { dir: this.localize.dir() });
|
||||
await animateTo(this.base, keyframes, options);
|
||||
|
||||
this.emit('sl-after-show');
|
||||
} else {
|
||||
// Hide
|
||||
this.emit('sl-hide');
|
||||
|
||||
clearTimeout(this.autoHideTimeout);
|
||||
|
||||
await stopAnimations(this.base);
|
||||
const { keyframes, options } = getAnimation(this, 'alert.hide', { dir: this.localize.dir() });
|
||||
await animateTo(this.base, keyframes, options);
|
||||
this.base.hidden = true;
|
||||
|
||||
this.emit('sl-after-hide');
|
||||
}
|
||||
}
|
||||
|
||||
@watch('duration')
|
||||
handleDurationChange() {
|
||||
this.restartAutoHide();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div
|
||||
@@ -192,18 +196,12 @@ export default class SlAlert extends ShoelaceElement {
|
||||
'alert--danger': this.variant === 'danger'
|
||||
})}
|
||||
role="alert"
|
||||
aria-live="assertive"
|
||||
aria-atomic="true"
|
||||
aria-hidden=${this.open ? 'false' : 'true'}
|
||||
@mousemove=${this.handleMouseMove}
|
||||
>
|
||||
<span part="icon" class="alert__icon">
|
||||
<slot name="icon"></slot>
|
||||
</span>
|
||||
<slot name="icon" part="icon" class="alert__icon"></slot>
|
||||
|
||||
<span part="message" class="alert__message">
|
||||
<slot></slot>
|
||||
</span>
|
||||
<slot part="message" class="alert__message" aria-live="polite"></slot>
|
||||
|
||||
${this.closable
|
||||
? html`
|
||||
@@ -211,8 +209,9 @@ export default class SlAlert extends ShoelaceElement {
|
||||
part="close-button"
|
||||
exportparts="base:close-button__base"
|
||||
class="alert__close-button"
|
||||
name="x"
|
||||
name="x-lg"
|
||||
library="system"
|
||||
label=${this.localize.term('close')}
|
||||
@click=${this.handleCloseClick}
|
||||
></sl-icon-button>
|
||||
`
|
||||
@@ -224,16 +223,16 @@ export default class SlAlert extends ShoelaceElement {
|
||||
|
||||
setDefaultAnimation('alert.show', {
|
||||
keyframes: [
|
||||
{ opacity: 0, transform: 'scale(0.8)' },
|
||||
{ opacity: 1, transform: 'scale(1)' }
|
||||
{ opacity: 0, scale: 0.8 },
|
||||
{ opacity: 1, scale: 1 }
|
||||
],
|
||||
options: { duration: 250, easing: 'ease' }
|
||||
});
|
||||
|
||||
setDefaultAnimation('alert.hide', {
|
||||
keyframes: [
|
||||
{ opacity: 1, transform: 'scale(1)' },
|
||||
{ opacity: 0, transform: 'scale(0.8)' }
|
||||
{ opacity: 1, scale: 1 },
|
||||
{ opacity: 0, scale: 0.8 }
|
||||
],
|
||||
options: { duration: 250, easing: 'ease' }
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ export default css`
|
||||
:host {
|
||||
--control-box-size: 3rem;
|
||||
--icon-size: calc(var(--control-box-size) * 0.625);
|
||||
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
@@ -43,10 +44,14 @@ export default css`
|
||||
|
||||
:host([play]:hover) .animated-image__control-box {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
:host([play]:not(:hover)) .animated-image__control-box {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
:host([play]) slot[name='play-icon'],
|
||||
:host(:not([play])) slot[name='pause-icon'] {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
import { html } from 'lit';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import { watch } from '../../internal/watch';
|
||||
import '../icon/icon';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { watch } from '../../internal/watch';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './animated-image.styles';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
* @summary A component for displaying animated GIFs and WEBPs that play and pause on interaction.
|
||||
* @documentation https://shoelace.style/components/animated-image
|
||||
* @status stable
|
||||
* @since 2.0
|
||||
*
|
||||
* @dependency sl-icon
|
||||
*
|
||||
* @event sl-load - Emitted when the image loads successfully.
|
||||
* @event sl-error - Emitted when the image fails to load.
|
||||
*
|
||||
* @slot play-icon - Optional play icon to use instead of the default. Works best with `<sl-icon>`.
|
||||
* @slot pause-icon - Optional pause icon to use instead of the default. Works best with `<sl-icon>`.
|
||||
*
|
||||
* @part - control-box - The container that surrounds the pause/play icons and provides their background.
|
||||
* @part - play-icon - The icon to use for the play button.
|
||||
* @part - pause-icon - The icon to use for the pause button.
|
||||
*
|
||||
* @cssproperty --control-box-size - The size of the icon box.
|
||||
* @cssproperty --icon-size - The size of the play/pause icons.
|
||||
@@ -26,25 +29,25 @@ import type { CSSResultGroup } from 'lit';
|
||||
export default class SlAnimatedImage extends ShoelaceElement {
|
||||
static styles: CSSResultGroup = styles;
|
||||
|
||||
@query('.animated-image__animated') animatedImage: HTMLImageElement;
|
||||
|
||||
@state() frozenFrame: string;
|
||||
@state() isLoaded = false;
|
||||
|
||||
@query('.animated-image__animated') animatedImage: HTMLImageElement;
|
||||
|
||||
/** The image's src attribute. */
|
||||
/** The path to the image to load. */
|
||||
@property() src: string;
|
||||
|
||||
/** The image's alt attribute. */
|
||||
/** A description of the image used by assistive devices. */
|
||||
@property() alt: string;
|
||||
|
||||
/** When set, the image will animate. Otherwise, it will be paused. */
|
||||
/** Plays the animation. When this attribute is remove, the animation will pause. */
|
||||
@property({ type: Boolean, reflect: true }) play: boolean;
|
||||
|
||||
handleClick() {
|
||||
private handleClick() {
|
||||
this.play = !this.play;
|
||||
}
|
||||
|
||||
handleLoad() {
|
||||
private handleLoad() {
|
||||
const canvas = document.createElement('canvas');
|
||||
const { width, height } = this.animatedImage;
|
||||
canvas.width = width;
|
||||
@@ -58,7 +61,7 @@ export default class SlAnimatedImage extends ShoelaceElement {
|
||||
}
|
||||
}
|
||||
|
||||
handleError() {
|
||||
private handleError() {
|
||||
this.emit('sl-error');
|
||||
}
|
||||
|
||||
@@ -102,9 +105,8 @@ export default class SlAnimatedImage extends ShoelaceElement {
|
||||
/>
|
||||
|
||||
<div part="control-box" class="animated-image__control-box">
|
||||
${this.play
|
||||
? html`<sl-icon part="pause-icon" name="pause-fill" library="system"></sl-icon>`
|
||||
: html`<sl-icon part="play-icon" name="play-fill" library="system"></sl-icon>`}
|
||||
<slot name="play-icon"><sl-icon name="play-fill" library="system"></sl-icon></slot>
|
||||
<slot name="pause-icon"><sl-icon name="pause-fill" library="system"></sl-icon></slot>
|
||||
</div>
|
||||
`
|
||||
: ''}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import { html } from 'lit';
|
||||
import { customElement, property, queryAsync } from 'lit/decorators.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import { watch } from '../../internal/watch';
|
||||
import styles from './animation.styles';
|
||||
import { animations } from './animations';
|
||||
import { customElement, property, queryAsync } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { watch } from '../../internal/watch';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './animation.styles';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
* @summary Animate elements declaratively with nearly 100 baked-in presets, or roll your own with custom keyframes. Powered by the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API).
|
||||
* @documentation https://shoelace.style/components/animation
|
||||
* @status stable
|
||||
* @since 2.0
|
||||
*
|
||||
* @event sl-cancel - Emitted when the animation is canceled.
|
||||
* @event sl-finish - Emitted when the animation finishes.
|
||||
* @event sl-start - Emitted when the animation starts or restarts.
|
||||
*
|
||||
* @slot - The element to animate. If multiple elements are to be animated, wrap them in a single container or use
|
||||
* multiple animation elements.
|
||||
* @slot - The element to animate. Avoid slotting in more than one element, as subsequent ones will be ignored. To
|
||||
* animate multiple elements, either wrap them in a single container or use multiple `<sl-animation>` elements.
|
||||
*/
|
||||
@customElement('sl-animation')
|
||||
export default class SlAnimation extends ShoelaceElement {
|
||||
@@ -30,15 +32,18 @@ export default class SlAnimation extends ShoelaceElement {
|
||||
@property() name = 'none';
|
||||
|
||||
/**
|
||||
* Plays the animation. When omitted, the animation will be paused. This prop will be automatically removed when the
|
||||
* animation finishes or gets canceled.
|
||||
* Plays the animation. When omitted, the animation will be paused. This attribute will be automatically removed when
|
||||
* the animation finishes or gets canceled.
|
||||
*/
|
||||
@property({ type: Boolean, reflect: true }) play = false;
|
||||
|
||||
/** The number of milliseconds to delay the start of the animation. */
|
||||
@property({ type: Number }) delay = 0;
|
||||
|
||||
/** Determines the direction of playback as well as the behavior when reaching the end of an iteration. */
|
||||
/**
|
||||
* Determines the direction of playback as well as the behavior when reaching the end of an iteration.
|
||||
* [Learn more](https://developer.mozilla.org/en-US/docs/Web/CSS/animation-direction)
|
||||
*/
|
||||
@property() direction: PlaybackDirection = 'normal';
|
||||
|
||||
/** The number of milliseconds each iteration of the animation takes to complete. */
|
||||
@@ -95,68 +100,24 @@ export default class SlAnimation extends ShoelaceElement {
|
||||
this.destroyAnimation();
|
||||
}
|
||||
|
||||
@watch('name')
|
||||
@watch('delay')
|
||||
@watch('direction')
|
||||
@watch('duration')
|
||||
@watch('easing')
|
||||
@watch('endDelay')
|
||||
@watch('fill')
|
||||
@watch('iterations')
|
||||
@watch('iterationsStart')
|
||||
@watch('keyframes')
|
||||
handleAnimationChange() {
|
||||
if (!this.hasUpdated) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.createAnimation();
|
||||
}
|
||||
|
||||
handleAnimationFinish() {
|
||||
private handleAnimationFinish() {
|
||||
this.play = false;
|
||||
this.hasStarted = false;
|
||||
this.emit('sl-finish');
|
||||
}
|
||||
|
||||
handleAnimationCancel() {
|
||||
private handleAnimationCancel() {
|
||||
this.play = false;
|
||||
this.hasStarted = false;
|
||||
this.emit('sl-cancel');
|
||||
}
|
||||
|
||||
@watch('play')
|
||||
handlePlayChange() {
|
||||
if (this.animation) {
|
||||
if (this.play && !this.hasStarted) {
|
||||
this.hasStarted = true;
|
||||
this.emit('sl-start');
|
||||
}
|
||||
|
||||
if (this.play) {
|
||||
this.animation.play();
|
||||
} else {
|
||||
this.animation.pause();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@watch('playbackRate')
|
||||
handlePlaybackRateChange() {
|
||||
if (this.animation) {
|
||||
this.animation.playbackRate = this.playbackRate;
|
||||
}
|
||||
}
|
||||
|
||||
handleSlotChange() {
|
||||
private handleSlotChange() {
|
||||
this.destroyAnimation();
|
||||
this.createAnimation();
|
||||
}
|
||||
|
||||
async createAnimation() {
|
||||
private async createAnimation() {
|
||||
const easing = animations.easings[this.easing] ?? this.easing;
|
||||
const keyframes = this.keyframes ?? (animations as unknown as Partial<Record<string, Keyframe[]>>)[this.name];
|
||||
const slot = await this.defaultSlot;
|
||||
@@ -191,7 +152,7 @@ export default class SlAnimation extends ShoelaceElement {
|
||||
return true;
|
||||
}
|
||||
|
||||
destroyAnimation() {
|
||||
private destroyAnimation() {
|
||||
if (this.animation) {
|
||||
this.animation.cancel();
|
||||
this.animation.removeEventListener('cancel', this.handleAnimationCancel);
|
||||
@@ -200,7 +161,53 @@ export default class SlAnimation extends ShoelaceElement {
|
||||
}
|
||||
}
|
||||
|
||||
/** Clears all KeyframeEffects caused by this animation and aborts its playback. */
|
||||
@watch([
|
||||
'name',
|
||||
'delay',
|
||||
'direction',
|
||||
'duration',
|
||||
'easing',
|
||||
'endDelay',
|
||||
'fill',
|
||||
'iterations',
|
||||
'iterationsStart',
|
||||
'keyframes'
|
||||
])
|
||||
handleAnimationChange() {
|
||||
if (!this.hasUpdated) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.createAnimation();
|
||||
}
|
||||
|
||||
@watch('play')
|
||||
handlePlayChange() {
|
||||
if (this.animation) {
|
||||
if (this.play && !this.hasStarted) {
|
||||
this.hasStarted = true;
|
||||
this.emit('sl-start');
|
||||
}
|
||||
|
||||
if (this.play) {
|
||||
this.animation.play();
|
||||
} else {
|
||||
this.animation.pause();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@watch('playbackRate')
|
||||
handlePlaybackRateChange() {
|
||||
if (this.animation) {
|
||||
this.animation.playbackRate = this.playbackRate;
|
||||
}
|
||||
}
|
||||
|
||||
/** Clears all keyframe effects caused by this animation and aborts its playback. */
|
||||
cancel() {
|
||||
this.animation?.cancel();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@ import { expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import sinon from 'sinon';
|
||||
import type SlAvatar from './avatar';
|
||||
|
||||
// The default avatar background just misses AA contrast, but the next step up is way too dark. Since avatars aren't
|
||||
// used to display text, we're going to relax this rule.
|
||||
const ignoredRules = ['color-contrast'];
|
||||
|
||||
describe('<sl-avatar>', () => {
|
||||
let el: SlAvatar;
|
||||
|
||||
@@ -11,11 +15,11 @@ describe('<sl-avatar>', () => {
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
|
||||
it('should default to circle styling', () => {
|
||||
const part = el.shadowRoot!.querySelector('[part="base"]')!;
|
||||
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
|
||||
expect(el.getAttribute('shape')).to.eq('circle');
|
||||
expect(part.classList.value.trim()).to.eq('avatar avatar--circle');
|
||||
});
|
||||
@@ -36,17 +40,17 @@ describe('<sl-avatar>', () => {
|
||||
* the image element to pass accessibility.
|
||||
* https://html.spec.whatwg.org/multipage/images.html#ancillary-images
|
||||
*/
|
||||
await expect(el).to.be.accessible();
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
|
||||
it('renders "image" part, with src and a role of presentation', () => {
|
||||
const part = el.shadowRoot!.querySelector('[part="image"]')!;
|
||||
const part = el.shadowRoot!.querySelector('[part~="image"]')!;
|
||||
|
||||
expect(part.getAttribute('src')).to.eq(image);
|
||||
});
|
||||
|
||||
it('renders the label attribute in the "base" part', () => {
|
||||
const part = el.shadowRoot!.querySelector('[part="base"]')!;
|
||||
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
|
||||
|
||||
expect(part.getAttribute('aria-label')).to.eq(label);
|
||||
});
|
||||
@@ -59,11 +63,11 @@ describe('<sl-avatar>', () => {
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
|
||||
it('renders "initials" part, with initials as the text node', () => {
|
||||
const part = el.shadowRoot!.querySelector<HTMLElement>('[part="initials"]')!;
|
||||
const part = el.shadowRoot!.querySelector<HTMLElement>('[part~="initials"]')!;
|
||||
|
||||
expect(part.innerText).to.eq(initials);
|
||||
});
|
||||
@@ -76,11 +80,11 @@ describe('<sl-avatar>', () => {
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
|
||||
it('appends the appropriate class on the "base" part', () => {
|
||||
const part = el.shadowRoot!.querySelector('[part="base"]')!;
|
||||
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
|
||||
|
||||
expect(el.getAttribute('shape')).to.eq(shape);
|
||||
expect(part.classList.value.trim()).to.eq(`avatar avatar--${shape}`);
|
||||
@@ -94,7 +98,7 @@ describe('<sl-avatar>', () => {
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
|
||||
it('should accept as an assigned child in the shadow root', () => {
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
import { html } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import { watch } from '../../internal/watch';
|
||||
import '../icon/icon';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { watch } from '../../internal/watch';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './avatar.styles';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
* @summary Avatars are used to represent a person or object.
|
||||
* @documentation https://shoelace.style/components/avatar
|
||||
* @status stable
|
||||
* @since 2.0
|
||||
*
|
||||
* @dependency sl-icon
|
||||
*
|
||||
* @slot icon - The default icon to use when no image or initials are present.
|
||||
* @slot icon - The default icon to use when no image or initials are present. Works best with `<sl-icon>`.
|
||||
*
|
||||
* @csspart base - The component's internal wrapper.
|
||||
* @csspart icon - The container that wraps the avatar icon.
|
||||
* @csspart initials - The container that wraps the avatar initials.
|
||||
* @csspart image - The avatar image.
|
||||
* @csspart base - The component's base wrapper.
|
||||
* @csspart icon - The container that wraps the avatar's icon.
|
||||
* @csspart initials - The container that wraps the avatar's initials.
|
||||
* @csspart image - The avatar image. Only shown when the `image` attribute is set.
|
||||
*
|
||||
* @cssproperty --size - The size of the avatar.
|
||||
*/
|
||||
@@ -37,6 +39,9 @@ export default class SlAvatar extends ShoelaceElement {
|
||||
/** Initials to use as a fallback when no image is available (1-2 characters max recommended). */
|
||||
@property() initials = '';
|
||||
|
||||
/** Indicates how the browser should load the image. */
|
||||
@property() loading: 'eager' | 'lazy' = 'eager';
|
||||
|
||||
/** The shape of the avatar. */
|
||||
@property({ reflect: true }) shape: 'circle' | 'square' | 'rounded' = 'circle';
|
||||
|
||||
@@ -62,11 +67,9 @@ export default class SlAvatar extends ShoelaceElement {
|
||||
${this.initials
|
||||
? html` <div part="initials" class="avatar__initials">${this.initials}</div> `
|
||||
: html`
|
||||
<div part="icon" class="avatar__icon" aria-hidden="true">
|
||||
<slot name="icon">
|
||||
<sl-icon name="person-fill" library="system"></sl-icon>
|
||||
</slot>
|
||||
</div>
|
||||
<slot name="icon" part="icon" class="avatar__icon" aria-hidden="true">
|
||||
<sl-icon name="person-fill" library="system"></sl-icon>
|
||||
</slot>
|
||||
`}
|
||||
${this.image && !this.hasError
|
||||
? html`
|
||||
@@ -74,6 +77,7 @@ export default class SlAvatar extends ShoelaceElement {
|
||||
part="image"
|
||||
class="avatar__image"
|
||||
src="${this.image}"
|
||||
loading="${this.loading}"
|
||||
alt=""
|
||||
@error="${() => (this.hasError = true)}"
|
||||
/>
|
||||
|
||||
@@ -12,14 +12,14 @@ export default css`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--sl-font-size-x-small);
|
||||
font-size: max(12px, 0.75em);
|
||||
font-weight: var(--sl-font-weight-semibold);
|
||||
letter-spacing: var(--sl-letter-spacing-normal);
|
||||
line-height: 1;
|
||||
border-radius: var(--sl-border-radius-small);
|
||||
border: solid 1px var(--sl-color-neutral-0);
|
||||
white-space: nowrap;
|
||||
padding: 3px 6px;
|
||||
padding: 0.35em 0.6em;
|
||||
user-select: none;
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('<sl-badge>', () => {
|
||||
it('should pass accessibility tests with a role of status on the base part.', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
|
||||
const part = el.shadowRoot!.querySelector('[part="base"]')!;
|
||||
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
|
||||
expect(part.getAttribute('role')).to.eq('status');
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('<sl-badge>', () => {
|
||||
});
|
||||
|
||||
it('should default to square styling, with the primary color', () => {
|
||||
const part = el.shadowRoot!.querySelector('[part="base"]')!;
|
||||
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
|
||||
expect(part.classList.value.trim()).to.eq('badge badge--primary');
|
||||
});
|
||||
});
|
||||
@@ -36,7 +36,7 @@ describe('<sl-badge>', () => {
|
||||
});
|
||||
|
||||
it('should append the pill class to the classlist to render a pill', () => {
|
||||
const part = el.shadowRoot!.querySelector('[part="base"]')!;
|
||||
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
|
||||
expect(part.classList.value.trim()).to.eq('badge badge--primary badge--pill');
|
||||
});
|
||||
});
|
||||
@@ -51,7 +51,7 @@ describe('<sl-badge>', () => {
|
||||
});
|
||||
|
||||
it('should append the pulse class to the classlist to render a pulse', () => {
|
||||
const part = el.shadowRoot!.querySelector('[part="base"]')!;
|
||||
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
|
||||
expect(part.classList.value.trim()).to.eq('badge badge--primary badge--pulse');
|
||||
});
|
||||
});
|
||||
@@ -67,7 +67,7 @@ describe('<sl-badge>', () => {
|
||||
});
|
||||
|
||||
it('should default to square styling, with the primary color', () => {
|
||||
const part = el.shadowRoot!.querySelector('[part="base"]')!;
|
||||
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
|
||||
expect(part.classList.value.trim()).to.eq(`badge badge--${variant}`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import { html } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './badge.styles';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
* @summary Badges are used to draw attention and display statuses or counts.
|
||||
* @documentation https://shoelace.style/components/badge
|
||||
* @status stable
|
||||
* @since 2.0
|
||||
*
|
||||
* @slot - The badge's content.
|
||||
*
|
||||
* @csspart base - The component's internal wrapper.
|
||||
* @csspart base - The component's base wrapper.
|
||||
*/
|
||||
@customElement('sl-badge')
|
||||
export default class SlBadge extends ShoelaceElement {
|
||||
static styles: CSSResultGroup = styles;
|
||||
|
||||
/** The badge's variant. */
|
||||
/** The badge's theme variant. */
|
||||
@property({ reflect: true }) variant: 'primary' | 'success' | 'neutral' | 'warning' | 'danger' = 'primary';
|
||||
|
||||
/** Draws a pill-style badge with rounded edges. */
|
||||
@@ -28,7 +30,7 @@ export default class SlBadge extends ShoelaceElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<span
|
||||
<slot
|
||||
part="base"
|
||||
class=${classMap({
|
||||
badge: true,
|
||||
@@ -41,9 +43,7 @@ export default class SlBadge extends ShoelaceElement {
|
||||
'badge--pulse': this.pulse
|
||||
})}
|
||||
role="status"
|
||||
>
|
||||
<slot></slot>
|
||||
</span>
|
||||
></slot>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@ describe('<sl-breadcrumb-item>', () => {
|
||||
});
|
||||
|
||||
it('should hide the separator from screen readers', () => {
|
||||
const separator = el.shadowRoot!.querySelector<HTMLSpanElement>('[part="separator"]');
|
||||
const separator = el.shadowRoot!.querySelector<HTMLSpanElement>('[part~="separator"]');
|
||||
expect(separator).attribute('aria-hidden', 'true');
|
||||
});
|
||||
|
||||
it('should render a HTMLButtonElement as the part "label", with a set type "button"', () => {
|
||||
const button = el.shadowRoot!.querySelector<HTMLButtonElement>('[part="label"]');
|
||||
const button = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="label"]');
|
||||
expect(button).to.exist;
|
||||
expect(button).attribute('type', 'button');
|
||||
});
|
||||
@@ -38,7 +38,7 @@ describe('<sl-breadcrumb-item>', () => {
|
||||
});
|
||||
|
||||
it('should render a HTMLAnchorElement as the part "label", with the supplied href value', () => {
|
||||
const hyperlink = el.shadowRoot!.querySelector<HTMLAnchorElement>('[part="label"]');
|
||||
const hyperlink = el.shadowRoot!.querySelector<HTMLAnchorElement>('[part~="label"]');
|
||||
expect(hyperlink).attribute('href', 'https://jsonplaceholder.typicode.com/');
|
||||
});
|
||||
});
|
||||
@@ -58,7 +58,7 @@ describe('<sl-breadcrumb-item>', () => {
|
||||
let hyperlink: HTMLAnchorElement | null;
|
||||
|
||||
before(() => {
|
||||
hyperlink = el.shadowRoot!.querySelector<HTMLAnchorElement>('[part="label"]');
|
||||
hyperlink = el.shadowRoot!.querySelector<HTMLAnchorElement>('[part~="label"]');
|
||||
});
|
||||
|
||||
it('should use the supplied href value, as the href attribute value', () => {
|
||||
@@ -124,7 +124,7 @@ describe('<sl-breadcrumb-item>', () => {
|
||||
});
|
||||
|
||||
it('should append class "breadcrumb-item--has-prefix" to "base" part', () => {
|
||||
const part = el.shadowRoot!.querySelector('[part="base"]')!;
|
||||
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
|
||||
expect(part.classList.value.trim()).to.equal('breadcrumb-item breadcrumb-item--has-prefix');
|
||||
});
|
||||
});
|
||||
@@ -151,7 +151,7 @@ describe('<sl-breadcrumb-item>', () => {
|
||||
});
|
||||
|
||||
it('should append class "breadcrumb-item--has-suffix" to "base" part', () => {
|
||||
const part = el.shadowRoot!.querySelector<HTMLElement>('[part="base"]')!;
|
||||
const part = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
expect(part.classList.value.trim()).to.equal('breadcrumb-item breadcrumb-item--has-suffix');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { html } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { HasSlotController } from '../../internal/slot';
|
||||
import { html } from 'lit';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import { HasSlotController } from '../../internal/slot';
|
||||
import styles from './breadcrumb-item.styles';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
* @summary Breadcrumb Items are used inside [breadcrumbs](/components/breadcrumb) to represent different links.
|
||||
* @documentation https://shoelace.style/components/breadcrumb-item
|
||||
* @status stable
|
||||
* @since 2.0
|
||||
*
|
||||
* @slot - The breadcrumb item's label.
|
||||
* @slot prefix - An optional prefix, usually an icon or icon button.
|
||||
@@ -17,11 +19,11 @@ import type { CSSResultGroup } from 'lit';
|
||||
* @slot separator - The separator to use for the breadcrumb item. This will only change the separator for this item. If
|
||||
* you want to change it for all items in the group, set the separator on `<sl-breadcrumb>` instead.
|
||||
*
|
||||
* @csspart base - The component's internal wrapper.
|
||||
* @csspart base - The component's base wrapper.
|
||||
* @csspart label - The breadcrumb item's label.
|
||||
* @csspart prefix - The container that wraps the prefix slot.
|
||||
* @csspart suffix - The container that wraps the suffix slot.
|
||||
* @csspart separator - The container that wraps the separator slot.
|
||||
* @csspart prefix - The container that wraps the prefix.
|
||||
* @csspart suffix - The container that wraps the suffix.
|
||||
* @csspart separator - The container that wraps the separator.
|
||||
*/
|
||||
@customElement('sl-breadcrumb-item')
|
||||
export default class SlBreadcrumbItem extends ShoelaceElement {
|
||||
@@ -53,9 +55,7 @@ export default class SlBreadcrumbItem extends ShoelaceElement {
|
||||
'breadcrumb-item--has-suffix': this.hasSlotController.test('suffix')
|
||||
})}
|
||||
>
|
||||
<span part="prefix" class="breadcrumb-item__prefix">
|
||||
<slot name="prefix"></slot>
|
||||
</span>
|
||||
<slot name="prefix" part="prefix" class="breadcrumb-item__prefix"></slot>
|
||||
|
||||
${isLink
|
||||
? html`
|
||||
@@ -75,13 +75,9 @@ export default class SlBreadcrumbItem extends ShoelaceElement {
|
||||
</button>
|
||||
`}
|
||||
|
||||
<span part="suffix" class="breadcrumb-item__suffix">
|
||||
<slot name="suffix"></slot>
|
||||
</span>
|
||||
<slot name="suffix" part="suffix" class="breadcrumb-item__suffix"></slot>
|
||||
|
||||
<span part="separator" class="breadcrumb-item__separator" aria-hidden="true">
|
||||
<slot name="separator"></slot>
|
||||
</span>
|
||||
<slot name="separator" part="separator" class="breadcrumb-item__separator" aria-hidden="true"></slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import type SlBreadcrumb from './breadcrumb';
|
||||
|
||||
// The default link color just misses AA contrast, but the next step up is way too dark. Maybe we can solve this in the
|
||||
// future with a prefers-contrast media query.
|
||||
const ignoredRules = ['color-contrast'];
|
||||
|
||||
describe('<sl-breadcrumb>', () => {
|
||||
let el: SlBreadcrumb;
|
||||
|
||||
@@ -17,7 +21,7 @@ describe('<sl-breadcrumb>', () => {
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
|
||||
it('should render sl-icon as separator', () => {
|
||||
@@ -44,7 +48,7 @@ describe('<sl-breadcrumb>', () => {
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
|
||||
it('should accept "separator" as an assigned child in the shadow root', () => {
|
||||
@@ -76,7 +80,7 @@ describe('<sl-breadcrumb>', () => {
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -96,7 +100,7 @@ describe('<sl-breadcrumb>', () => {
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,38 +1,40 @@
|
||||
import { html } from 'lit';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import { LocalizeController } from '../../utilities/localize';
|
||||
import '../icon/icon';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { LocalizeController } from '../../utilities/localize';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './breadcrumb.styles';
|
||||
import type SlBreadcrumbItem from '../breadcrumb-item/breadcrumb-item';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
import type SlBreadcrumbItem from '../breadcrumb-item/breadcrumb-item';
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
* @summary Breadcrumbs provide a group of links so users can easily navigate a website's hierarchy.
|
||||
* @documentation https://shoelace.style/components/breadcrumb
|
||||
* @status stable
|
||||
* @since 2.0
|
||||
*
|
||||
* @slot - One or more breadcrumb items to display.
|
||||
* @slot separator - The separator to use between breadcrumb items.
|
||||
* @slot separator - The separator to use between breadcrumb items. Works best with `<sl-icon>`.
|
||||
*
|
||||
* @dependency sl-icon
|
||||
*
|
||||
* @csspart base - The component's internal wrapper.
|
||||
* @csspart base - The component's base wrapper.
|
||||
*/
|
||||
@customElement('sl-breadcrumb')
|
||||
export default class SlBreadcrumb extends ShoelaceElement {
|
||||
static styles: CSSResultGroup = styles;
|
||||
|
||||
@query('slot') defaultSlot: HTMLSlotElement;
|
||||
@query('slot[name="separator"]') separatorSlot: HTMLSlotElement;
|
||||
|
||||
private readonly localize = new LocalizeController(this);
|
||||
private separatorDir = this.localize.dir();
|
||||
|
||||
@query('slot') defaultSlot: HTMLSlotElement;
|
||||
@query('slot[name="separator"]') separatorSlot: HTMLSlotElement;
|
||||
|
||||
/**
|
||||
* The label to use for the breadcrumb control. This will not be shown, but it will be announced by screen readers and
|
||||
* other assistive devices.
|
||||
* The label to use for the breadcrumb control. This will not be shown on the screen, but it will be announced by
|
||||
* screen readers and other assistive devices to provide more context for users.
|
||||
*/
|
||||
@property() label = 'Breadcrumb';
|
||||
@property() label = '';
|
||||
|
||||
// Generates a clone of the separator element to use for each breadcrumb item
|
||||
private getSeparator() {
|
||||
@@ -47,7 +49,7 @@ export default class SlBreadcrumb extends ShoelaceElement {
|
||||
return clone;
|
||||
}
|
||||
|
||||
handleSlotChange() {
|
||||
private handleSlotChange() {
|
||||
const items = [...this.defaultSlot.assignedElements({ flatten: true })].filter(
|
||||
item => item.tagName.toLowerCase() === 'sl-breadcrumb-item'
|
||||
) as SlBreadcrumbItem[];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { expect, fixture, html, elementUpdated } from '@open-wc/testing';
|
||||
import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
|
||||
import type SlButtonGroup from './button-group';
|
||||
|
||||
describe('<sl-button-group>', () => {
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { html } from 'lit';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './button-group.styles';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
* @summary Button groups can be used to group related buttons into sections.
|
||||
* @documentation https://shoelace.style/components/button-group
|
||||
* @status stable
|
||||
* @since 2.0
|
||||
*
|
||||
* @slot - One or more `<sl-button>` elements to display in the button group.
|
||||
*
|
||||
* @csspart base - The component's internal wrapper.
|
||||
* @csspart base - The component's base wrapper.
|
||||
*/
|
||||
@customElement('sl-button-group')
|
||||
export default class SlButtonGroup extends ShoelaceElement {
|
||||
@@ -20,30 +22,33 @@ export default class SlButtonGroup extends ShoelaceElement {
|
||||
|
||||
@state() disableRole = false;
|
||||
|
||||
/** A label to use for the button group's `aria-label` attribute. */
|
||||
/**
|
||||
* A label to use for the button group. This won't be displayed on the screen, but it will be announced by assistive
|
||||
* devices when interacting with the control and is strongly recommended.
|
||||
*/
|
||||
@property() label = '';
|
||||
|
||||
handleFocus(event: CustomEvent) {
|
||||
private handleFocus(event: CustomEvent) {
|
||||
const button = findButton(event.target as HTMLElement);
|
||||
button?.classList.add('sl-button-group__button--focus');
|
||||
}
|
||||
|
||||
handleBlur(event: CustomEvent) {
|
||||
private handleBlur(event: CustomEvent) {
|
||||
const button = findButton(event.target as HTMLElement);
|
||||
button?.classList.remove('sl-button-group__button--focus');
|
||||
}
|
||||
|
||||
handleMouseOver(event: CustomEvent) {
|
||||
private handleMouseOver(event: CustomEvent) {
|
||||
const button = findButton(event.target as HTMLElement);
|
||||
button?.classList.add('sl-button-group__button--hover');
|
||||
}
|
||||
|
||||
handleMouseOut(event: CustomEvent) {
|
||||
private handleMouseOut(event: CustomEvent) {
|
||||
const button = findButton(event.target as HTMLElement);
|
||||
button?.classList.remove('sl-button-group__button--hover');
|
||||
}
|
||||
|
||||
handleSlotChange() {
|
||||
private handleSlotChange() {
|
||||
const slottedElements = [...this.defaultSlot.assignedElements({ flatten: true })] as HTMLElement[];
|
||||
|
||||
slottedElements.forEach(el => {
|
||||
@@ -61,9 +66,9 @@ export default class SlButtonGroup extends ShoelaceElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
// eslint-disable-next-line lit-a11y/mouse-events-have-key-events -- focusout & focusin support bubbling whereas focus & blur do not which is necessary here
|
||||
// eslint-disable-next-line lit-a11y/mouse-events-have-key-events
|
||||
return html`
|
||||
<div
|
||||
<slot
|
||||
part="base"
|
||||
class="button-group"
|
||||
role="${this.disableRole ? 'presentation' : 'group'}"
|
||||
@@ -72,16 +77,17 @@ export default class SlButtonGroup extends ShoelaceElement {
|
||||
@focusin=${this.handleFocus}
|
||||
@mouseover=${this.handleMouseOver}
|
||||
@mouseout=${this.handleMouseOut}
|
||||
>
|
||||
<slot @slotchange=${this.handleSlotChange} role="none"></slot>
|
||||
</div>
|
||||
@slotchange=${this.handleSlotChange}
|
||||
></slot>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function findButton(el: HTMLElement) {
|
||||
const children = ['sl-button', 'sl-radio-button'];
|
||||
return children.includes(el.tagName.toLowerCase()) ? el : el.querySelector(children.join(','));
|
||||
const selector = 'sl-button, sl-radio-button';
|
||||
|
||||
// The button could be the target element or a child of it (e.g. a dropdown or tooltip anchor)
|
||||
return el.closest(selector) ?? el.querySelector(selector);
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user