Compare commits
1337 Commits
v2.0.0-bet
...
current
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e6e0368a4 | ||
|
|
7e4d4c3c98 | ||
|
|
b5ef3191b7 | ||
|
|
f30481e229 | ||
|
|
ae010c333b | ||
|
|
43d1f9ee7a | ||
|
|
ec17e8736d | ||
|
|
44b27e791e | ||
|
|
02385027db | ||
|
|
b311072d9b | ||
|
|
87ac077b0a | ||
|
|
87837df35c | ||
|
|
5d72bbd162 | ||
|
|
a4fc1c5b44 | ||
|
|
539eaded73 | ||
|
|
93b2e78092 | ||
|
|
402a00dcd3 | ||
|
|
b63368d5f6 | ||
|
|
74c6d3ee36 | ||
|
|
621aa4362b | ||
|
|
c8919ad11f | ||
|
|
fad76dd1a2 | ||
|
|
b2f6499b87 | ||
|
|
9520e850dd | ||
|
|
4ee5271a83 | ||
|
|
d8de7bcc51 | ||
|
|
7ee31be6d6 | ||
|
|
9cb5ba7ac1 | ||
|
|
c380368b61 | ||
|
|
e298f7e5f4 | ||
|
|
c743561c25 | ||
|
|
e73e32fb71 | ||
|
|
b09a48bec4 | ||
|
|
aeef986cf5 | ||
|
|
409ac96ddf | ||
|
|
6f08f50639 | ||
|
|
8fc5f598d0 | ||
|
|
1383ea3fe8 | ||
|
|
f8c37e0d14 | ||
|
|
cf543ef335 | ||
|
|
a3450a7d83 | ||
|
|
e80b2c9fb9 | ||
|
|
8d617fb98c | ||
|
|
a6e225e47c | ||
|
|
e21943f4fb | ||
|
|
c36df5ecc1 | ||
|
|
458def7830 | ||
|
|
b5d800f07a | ||
|
|
6551a6330b | ||
|
|
cb5f670909 | ||
|
|
5b6c1632bd | ||
|
|
bf15f2fb8a | ||
|
|
31ef2f7929 | ||
|
|
8aab94f184 | ||
|
|
b7acb27c98 | ||
|
|
dcbbc55f28 | ||
|
|
81dfcc2eae | ||
|
|
16f3e256b0 | ||
|
|
1bf09f20aa | ||
|
|
75b2da9eab | ||
|
|
9736f053d9 | ||
|
|
d0b710c26d | ||
|
|
5b83d4d1b0 | ||
|
|
89f0f4a02c | ||
|
|
a067ccb9e0 | ||
|
|
1ccea42cca | ||
|
|
0f90dd0f54 | ||
|
|
262cbc9a22 | ||
|
|
3a61d20d93 | ||
|
|
95f4f87eb8 | ||
|
|
5b3cc0d492 | ||
|
|
0de39a8163 | ||
|
|
879fd7a224 | ||
|
|
50af138424 | ||
|
|
9d592f4e08 | ||
|
|
5016d27af7 | ||
|
|
7218a19357 | ||
|
|
33d2d4368f | ||
|
|
cca40ca710 | ||
|
|
c6281859fd | ||
|
|
956271880d | ||
|
|
201ff4efc5 | ||
|
|
f954233bda | ||
|
|
8267968b76 | ||
|
|
1e243e4257 | ||
|
|
0b6c3a46cf | ||
|
|
a2e58b7696 | ||
|
|
119d299657 | ||
|
|
e8634e4178 | ||
|
|
2e2a683d11 | ||
|
|
414197acc9 | ||
|
|
8fd01e1eda | ||
|
|
e1ca7d1f59 | ||
|
|
f84d6939bd | ||
|
|
82446e2114 | ||
|
|
a4f0ae9088 | ||
|
|
fe3906f766 | ||
|
|
c9e644f3fc | ||
|
|
8ffbd02db7 | ||
|
|
e88d57d17d | ||
|
|
5f4de6d9f5 | ||
|
|
2cce87deeb | ||
|
|
630b5b19a0 | ||
|
|
2ce1451a9f | ||
|
|
2d1badba96 | ||
|
|
1b5db078a7 | ||
|
|
91095bd63a | ||
|
|
d9703a64fd | ||
|
|
4c22e72390 | ||
|
|
d05b8fca20 | ||
|
|
afca2ad2e0 | ||
|
|
b2aa854d98 | ||
|
|
ba642a0886 | ||
|
|
287fff7cf1 | ||
|
|
136ecae4a6 | ||
|
|
cac772d5e6 | ||
|
|
e1dedcb1b5 | ||
|
|
c4901eca68 | ||
|
|
a001c2d12b | ||
|
|
c4c622eabd | ||
|
|
1ae018bedd | ||
|
|
c37c71d8fd | ||
|
|
24929e27c1 | ||
|
|
33a8d92aec | ||
|
|
32d21fa560 | ||
|
|
347d8b7f79 | ||
|
|
8f9c15913b | ||
|
|
60d7f688eb | ||
|
|
15f914914c | ||
|
|
2914475821 | ||
|
|
8e831aa3e7 | ||
|
|
985d4585c4 | ||
|
|
05b1212657 | ||
|
|
854db13bd7 | ||
|
|
4ddf80459a | ||
|
|
89fc2ff643 | ||
|
|
d7145f1f84 | ||
|
|
441a957432 | ||
|
|
67cbb85682 | ||
|
|
0005d16a06 | ||
|
|
ca5ab03cd4 | ||
|
|
c9e30022df | ||
|
|
c167bdd80f | ||
|
|
b9f62bb1bc | ||
|
|
a01b2cf8a2 | ||
|
|
f4b2623c8f | ||
|
|
af8426579e | ||
|
|
6b9ba9becf | ||
|
|
c6cc7b6983 | ||
|
|
0e869ec18d | ||
|
|
1b347874ef | ||
|
|
ff5b1e8573 | ||
|
|
73ad76a2fa | ||
|
|
aadcb486a9 | ||
|
|
4c854d64a7 | ||
|
|
c2e02d34ad | ||
|
|
8c8977549c | ||
|
|
24ef154d42 | ||
|
|
b5a3045bae | ||
|
|
7404e496cb | ||
|
|
5ba2c7eeec | ||
|
|
514a7f3d51 | ||
|
|
15474b83b1 | ||
|
|
a5f1bc6c82 | ||
|
|
834d44e0e4 | ||
|
|
c070149ae6 | ||
|
|
b0b6ea943e | ||
|
|
65b72217ea | ||
|
|
c4c2e8e3a9 | ||
|
|
47018d61cd | ||
|
|
d18db9adfa | ||
|
|
41913c8c58 | ||
|
|
68b982a744 | ||
|
|
a582302a79 | ||
|
|
bd3b2c93ee | ||
|
|
4704d63791 | ||
|
|
415a1477bb | ||
|
|
f363d5e187 | ||
|
|
efb0ee9c48 | ||
|
|
96daee5e1a | ||
|
|
d236206cce | ||
|
|
1ef8e1cf73 | ||
|
|
dc63f858b0 | ||
|
|
b8a3952153 | ||
|
|
4b2a62f660 | ||
|
|
08c074e44b | ||
|
|
d1953b0215 | ||
|
|
b268d7dd8e | ||
|
|
90e56e2f07 | ||
|
|
45ddaa4d38 | ||
|
|
0de54b163a | ||
|
|
620b86cddb | ||
|
|
32d0ac4147 | ||
|
|
a16733eb93 | ||
|
|
b67eef484c | ||
|
|
5166964659 | ||
|
|
e72c2df6d2 | ||
|
|
cdafb3870c | ||
|
|
20abf21791 | ||
|
|
482d155af4 | ||
|
|
ece23c727e | ||
|
|
b7726cd514 | ||
|
|
c31fe1ed35 | ||
|
|
9e034810d9 | ||
|
|
217aabe55a | ||
|
|
1dea2f384e | ||
|
|
c24edec6b9 | ||
|
|
add09ca5b8 | ||
|
|
57bd3632e8 | ||
|
|
6f44b6ffa6 | ||
|
|
ca8ba2d16b | ||
|
|
c37e4ba6b5 | ||
|
|
82cc778a0f | ||
|
|
5085ef831b | ||
|
|
4721bd117b | ||
|
|
57b13d848a | ||
|
|
e01a43f01e | ||
|
|
ec92351acf | ||
|
|
8c44eb75d5 | ||
|
|
aa48566aef | ||
|
|
7cbb26cbdb | ||
|
|
b8b6175a64 | ||
|
|
de5ad1b1b9 | ||
|
|
0866785495 | ||
|
|
947b3a9ec4 | ||
|
|
6db27ca51f | ||
|
|
f966ba97d7 | ||
|
|
1af711bc89 | ||
|
|
c71da4a075 | ||
|
|
d609fa87b4 | ||
|
|
dd16b0f65f | ||
|
|
6144e5eff4 | ||
|
|
c4db99f5a3 | ||
|
|
21431b0e56 | ||
|
|
136e6bc915 | ||
|
|
05aeb78b01 | ||
|
|
22010a1f9b | ||
|
|
e0fd6b210e | ||
|
|
be1c38f0e5 | ||
|
|
b1bdedd3a3 | ||
|
|
429b47963b | ||
|
|
466c8a0883 | ||
|
|
3a212030c0 | ||
|
|
7f1ac48f5f | ||
|
|
9c3e344ae0 | ||
|
|
db147e77b0 | ||
|
|
0384b03528 | ||
|
|
fc06de7b11 | ||
|
|
5ada0a7093 | ||
|
|
f136b8eb12 | ||
|
|
56a160464f | ||
|
|
3dc9430932 | ||
|
|
62b4525321 | ||
|
|
afa69b4f9c | ||
|
|
102f46d185 | ||
|
|
9c5e184d82 | ||
|
|
385b5451c8 | ||
|
|
0e7487257b | ||
|
|
eab0e3219f | ||
|
|
cf89c901a2 | ||
|
|
902b08cc0f | ||
|
|
caf9a09efa | ||
|
|
65734dc993 | ||
|
|
0f02fffc3a | ||
|
|
931ecad8c5 | ||
|
|
c137f83df6 | ||
|
|
d3a0a38dce | ||
|
|
b76af1aa21 | ||
|
|
5cf6a37ee2 | ||
|
|
63194abf93 | ||
|
|
e196b0915a | ||
|
|
d2369d1de8 | ||
|
|
a9bbcc5556 | ||
|
|
8d9430e7a2 | ||
|
|
0411754949 | ||
|
|
91ffaa1a2d | ||
|
|
ae9972a91a | ||
|
|
478fa6f2bb | ||
|
|
6a52a04591 | ||
|
|
a8f87e0d5e | ||
|
|
cbc96fdf5c | ||
|
|
b4d24dd9af | ||
|
|
4b66cc2acb | ||
|
|
3766d5ce27 | ||
|
|
4b7d686754 | ||
|
|
b948a07a4d | ||
|
|
6d3505aefa | ||
|
|
b22650ff51 | ||
|
|
23a7f65b49 | ||
|
|
f4fba8eab4 | ||
|
|
1734bf54a7 | ||
|
|
88efec7815 | ||
|
|
e335189bb8 | ||
|
|
d03ca4ab95 | ||
|
|
257407758f | ||
|
|
2443c046aa | ||
|
|
d710eb3947 | ||
|
|
7b2f6f230d | ||
|
|
07cb6070cc | ||
|
|
bd7dc2a7be | ||
|
|
db931c12be | ||
|
|
765b311a08 | ||
|
|
ce198d9c0b | ||
|
|
8f5893931b | ||
|
|
221be48589 | ||
|
|
234ff2619d | ||
|
|
b37be46ba3 | ||
|
|
6e2ea508db | ||
|
|
0e6e2abd28 | ||
|
|
db1bdfbf65 | ||
|
|
7bf0f647b3 | ||
|
|
44628889fe | ||
|
|
df25f8617b | ||
|
|
ad2099a27f | ||
|
|
708127f96d | ||
|
|
9deb51e95a | ||
|
|
67852ea657 | ||
|
|
7240f4f8f4 | ||
|
|
17ee89a5e8 | ||
|
|
f2177dccaf | ||
|
|
6aaf17b81a | ||
|
|
d113d13792 | ||
|
|
ab9cb5f185 | ||
|
|
76fd7aa28d | ||
|
|
8f17bf4e9d | ||
|
|
0f0f71af9b | ||
|
|
e624701022 | ||
|
|
4cedfc3201 | ||
|
|
d88d9fc81a | ||
|
|
051baa4ff5 | ||
|
|
57c3d7009b | ||
|
|
a27fd4d2e9 | ||
|
|
79ac425e2b | ||
|
|
857f318f9c | ||
|
|
c0966bf767 | ||
|
|
86cecc9e30 | ||
|
|
ec036d8e61 | ||
|
|
77b25f4581 | ||
|
|
3168a1acf8 | ||
|
|
a8d59b3329 | ||
|
|
17160e3bed | ||
|
|
5990fbd000 | ||
|
|
954d78dcd1 | ||
|
|
3ea31389dd | ||
|
|
d79799043a | ||
|
|
9f8ce58288 | ||
|
|
2371c5490f | ||
|
|
77abd42d66 | ||
|
|
218e78e947 | ||
|
|
8a1efac9b8 | ||
|
|
f9ae8327f6 | ||
|
|
7f3076d195 | ||
|
|
1fa79e64ae | ||
|
|
dde1010465 | ||
|
|
3a3a7347bc | ||
|
|
77c9750206 | ||
|
|
3d2e618be8 | ||
|
|
79feaae7fc | ||
|
|
b0f7dfb86b | ||
|
|
e1979b8f38 | ||
|
|
7e9ae32b9b | ||
|
|
480a1df246 | ||
|
|
ff798adb49 | ||
|
|
70a64262e9 | ||
|
|
5f65896150 | ||
|
|
c69db4919b | ||
|
|
a526e8a956 | ||
|
|
4970ba065e | ||
|
|
0292ed30c5 | ||
|
|
b64b1c2536 | ||
|
|
8f9eb012ba | ||
|
|
c8fd9f19d2 | ||
|
|
603aa93322 | ||
|
|
74203de094 | ||
|
|
4fa4682a45 | ||
|
|
34e0fb2fc1 | ||
|
|
50972f2b38 | ||
|
|
652ce6c9f1 | ||
|
|
8412b150b2 | ||
|
|
22b8ef4edf | ||
|
|
0865dede6f | ||
|
|
d638d811ad | ||
|
|
bc58472b7b | ||
|
|
226c856b1e | ||
|
|
a127b8722e | ||
|
|
9c573fb454 | ||
|
|
a346d18930 | ||
|
|
a32488baeb | ||
|
|
a4131caeda | ||
|
|
6c62a4f4c0 | ||
|
|
5b12de1edf | ||
|
|
f79a670ca3 | ||
|
|
fe2e2d6df5 | ||
|
|
e1ec60af62 | ||
|
|
dcbcc4c050 | ||
|
|
0eb3375bb9 | ||
|
|
c26a8810c8 | ||
|
|
872227e345 | ||
|
|
f22c529eab | ||
|
|
3430b33c3e | ||
|
|
1bc2a6ef76 | ||
|
|
5a94f5bf5b | ||
|
|
4277377189 | ||
|
|
d818980dea | ||
|
|
636f61006f | ||
|
|
d93e698baf | ||
|
|
f8d8291caa | ||
|
|
21bef1c2ea | ||
|
|
f0efb9253c | ||
|
|
cfd28f2608 | ||
|
|
a3844fe074 | ||
|
|
65e90f12f4 | ||
|
|
4335289d6a | ||
|
|
86cc721e03 | ||
|
|
4a28825ea7 | ||
|
|
19cf823da5 | ||
|
|
737b55d78d | ||
|
|
8493131db5 | ||
|
|
0d86c2af37 | ||
|
|
d6a7820a52 | ||
|
|
39ca1208f5 | ||
|
|
610a06bcb9 | ||
|
|
b8584c0581 | ||
|
|
ab19afeb66 | ||
|
|
41b5cb367f | ||
|
|
e65b09fdec | ||
|
|
15a4049a01 | ||
|
|
ce708fbba8 | ||
|
|
75bd7784fb | ||
|
|
6e092ccf7a | ||
|
|
b7b73ea3a9 | ||
|
|
9dab91e0d1 | ||
|
|
a3a802a37b | ||
|
|
358ad7bb30 | ||
|
|
0a555c53c7 | ||
|
|
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 | ||
|
|
6e2856db5f | ||
|
|
6d1eb71d8e | ||
|
|
a334e53a5d | ||
|
|
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 | ||
|
|
48ccc95dd9 | ||
|
|
c6a6a77bbd | ||
|
|
e632b51eb8 | ||
|
|
c8e633c4a1 | ||
|
|
724f4a59db | ||
|
|
041364fb7d | ||
|
|
fbcb4d8dbd | ||
|
|
27a6b4a8c9 | ||
|
|
0d50749ec8 | ||
|
|
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 | ||
|
|
316d8b52b6 | ||
|
|
59cd70ae6f | ||
|
|
6d9c8561fb | ||
|
|
edb8a92838 | ||
|
|
da3ffe9a60 | ||
|
|
e2b791dee8 | ||
|
|
9178be576b | ||
|
|
752f5cff55 | ||
|
|
3675787ddd | ||
|
|
6284ed0347 | ||
|
|
1ff05c4b3d | ||
|
|
066abe4e52 | ||
|
|
e9134ddcf6 | ||
|
|
0237851aa1 | ||
|
|
b7b2d9265a | ||
|
|
1841251fdf | ||
|
|
b797803a2b | ||
|
|
b1e48406f3 | ||
|
|
bcb4f2267b | ||
|
|
c951661b58 | ||
|
|
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 | ||
|
|
03fe71353a | ||
|
|
8915282082 | ||
|
|
28003ac430 | ||
|
|
f2dcad82a9 | ||
|
|
69ff4f0bbc | ||
|
|
a1c93fd30f | ||
|
|
bdf8c4e669 | ||
|
|
854e576c2c | ||
|
|
cf32064e8c | ||
|
|
1f91d4cb79 | ||
|
|
a7fdb23577 | ||
|
|
39d1bbed2c | ||
|
|
e2e152c373 | ||
|
|
aae74dbaf7 | ||
|
|
6c716db037 | ||
|
|
7c0c0fa244 | ||
|
|
ffd32e52ef | ||
|
|
27868b56e8 | ||
|
|
346f13e5a5 | ||
|
|
69547d4800 | ||
|
|
9fc4185f87 | ||
|
|
08b5da7c56 | ||
|
|
05e026876a | ||
|
|
8d0d0ac4e0 | ||
|
|
972d629883 | ||
|
|
ce12020fca | ||
|
|
29b149cd26 | ||
|
|
7de37b9315 | ||
|
|
237cda3b63 | ||
|
|
51c4abb72b | ||
|
|
3a6890af81 | ||
|
|
7cc2c89f92 | ||
|
|
bb55c93b1a | ||
|
|
a3d00a92a0 | ||
|
|
5eccce625a | ||
|
|
0c3a25d2ad | ||
|
|
d225aaa2ff | ||
|
|
c6b7c24ed7 | ||
|
|
827c36bb1d | ||
|
|
d9f48a5f2a | ||
|
|
ccbdc6c014 | ||
|
|
b6edba912b | ||
|
|
4cc5baaa0b | ||
|
|
c2bbb0e8a4 | ||
|
|
352cade421 | ||
|
|
708977acf7 | ||
|
|
bab5749788 | ||
|
|
c6d0b27f0d | ||
|
|
ee31f3f682 | ||
|
|
105bb08ee1 | ||
|
|
ca64d26d77 | ||
|
|
23b2f9335b | ||
|
|
dfdd7c75c2 | ||
|
|
156a2812ae | ||
|
|
e42beab336 | ||
|
|
098db9c3fa | ||
|
|
81d393fbc1 | ||
|
|
2696b1a9ec | ||
|
|
322f23ba56 | ||
|
|
09224041b8 | ||
|
|
326816e7b7 | ||
|
|
4117b6d219 | ||
|
|
af9905acff | ||
|
|
e0727cc72c | ||
|
|
624297f624 | ||
|
|
1996037acc | ||
|
|
8fa665d3e7 | ||
|
|
f0a3972ef6 | ||
|
|
c8f42c5bde | ||
|
|
7a6144d8c4 | ||
|
|
ea71155b8f | ||
|
|
20e9e3c320 | ||
|
|
bcf2139fe7 | ||
|
|
48f864475e | ||
|
|
32f24a881e | ||
|
|
0163edd8a3 | ||
|
|
81620f0199 | ||
|
|
d850b0f507 | ||
|
|
19b2cde0d9 | ||
|
|
87e252940d | ||
|
|
c6d30ae1b5 | ||
|
|
23bb45b1b6 | ||
|
|
70f59aa4ba | ||
|
|
237230961a | ||
|
|
b669ab7b74 | ||
|
|
040e8ce5e1 | ||
|
|
a74de59687 | ||
|
|
186f57cdde | ||
|
|
c94db27f07 | ||
|
|
578a58042b | ||
|
|
b3b3956ff5 | ||
|
|
ce2abb97e0 | ||
|
|
6fc71601dd | ||
|
|
fd3da7e773 | ||
|
|
70700b1ab5 | ||
|
|
eb9107bf2b | ||
|
|
d0820178c9 | ||
|
|
a8e8325ea7 | ||
|
|
4b9e35313d | ||
|
|
dc1394483a | ||
|
|
4b32b9461c | ||
|
|
308c8228aa | ||
|
|
d74d2dec2e | ||
|
|
c9f3497a8a | ||
|
|
8e57d0075c | ||
|
|
73c8eba269 | ||
|
|
1c0ffa7a24 | ||
|
|
014354efde | ||
|
|
13ec55ba07 | ||
|
|
d6508a1262 | ||
|
|
9ebb5c8ec7 | ||
|
|
2093568981 | ||
|
|
bd5ca9034c | ||
|
|
7ae454f8ca | ||
|
|
b9fcd09209 | ||
|
|
cf2915a591 | ||
|
|
9e625752be | ||
|
|
147d1f048b | ||
|
|
801c4f70ec | ||
|
|
bcc9081247 | ||
|
|
3919ea97e9 | ||
|
|
876078b725 | ||
|
|
c765a1df9b | ||
|
|
a1bc784bc7 | ||
|
|
d14b9e12ed | ||
|
|
ab0fdd66b4 | ||
|
|
bf730f5bd2 | ||
|
|
8c667791fa | ||
|
|
d07a8cfaea | ||
|
|
23deda2253 | ||
|
|
1c6cf769d4 | ||
|
|
2add23d5d2 | ||
|
|
26693b2256 | ||
|
|
f31d13c424 | ||
|
|
59182db564 | ||
|
|
1bae224f80 | ||
|
|
9b3240a14f | ||
|
|
860224c894 | ||
|
|
a8b2eb2bb0 | ||
|
|
cea69beca9 | ||
|
|
4b7da8f510 | ||
|
|
6d31b1d63d | ||
|
|
7b55b38aa4 | ||
|
|
bf1121d126 | ||
|
|
fb2d419ab0 | ||
|
|
7179b60499 | ||
|
|
ad00d8840e | ||
|
|
3d9fd3b889 | ||
|
|
90f4d77ed6 | ||
|
|
36d1e7c52e | ||
|
|
2a1895a125 | ||
|
|
d5a352465e | ||
|
|
088ab99d02 | ||
|
|
42ed7f5cbb | ||
|
|
66e8259421 | ||
|
|
1f34b63a2e | ||
|
|
cf3b4fa501 | ||
|
|
73c190217f | ||
|
|
b2986e89b0 | ||
|
|
2bf68e8a32 | ||
|
|
f392ab4270 | ||
|
|
eefb8f6a6b | ||
|
|
8b2ddc984b | ||
|
|
f5bcd03c3f | ||
|
|
92a73f21eb | ||
|
|
4dbc3d2c8f | ||
|
|
90aac9abe9 | ||
|
|
589021c40f | ||
|
|
1cc65b145a | ||
|
|
f6fbde3c96 | ||
|
|
caf25773f0 | ||
|
|
b904bb0736 | ||
|
|
28ecc90e7f | ||
|
|
72fb9c7293 | ||
|
|
d69712530c | ||
|
|
71a39600f5 | ||
|
|
0291d7c28d | ||
|
|
ed2417d974 | ||
|
|
85fd8a5204 | ||
|
|
6446bb1013 | ||
|
|
2f8852245e | ||
|
|
401b46bad8 | ||
|
|
ae3efaf8ae | ||
|
|
98746c86e4 | ||
|
|
268aef1711 | ||
|
|
d765cef376 | ||
|
|
23ed3d5647 | ||
|
|
1a7fbbfab4 | ||
|
|
47388d4a3f | ||
|
|
ce09869ea2 | ||
|
|
33587f51d3 | ||
|
|
ed76d8aecc | ||
|
|
33d2ec0597 | ||
|
|
b4ae6ed1aa | ||
|
|
b6839254d4 | ||
|
|
8ee811af58 | ||
|
|
f34ffdac55 | ||
|
|
d7e7ff6101 | ||
|
|
1c36a7bcd4 | ||
|
|
7a77be017e | ||
|
|
5b5c6710fe | ||
|
|
04f7d2e182 | ||
|
|
36ee3c8a70 | ||
|
|
98eec84422 | ||
|
|
bc08a4c005 | ||
|
|
41580992f6 | ||
|
|
9b7ce98ec0 | ||
|
|
864d567572 | ||
|
|
cf360b3b3f | ||
|
|
673dc29dfe | ||
|
|
849b643cfc | ||
|
|
c39c4a9e9f | ||
|
|
92c36c65cb | ||
|
|
b6f25e09d2 | ||
|
|
71119c963d | ||
|
|
ecf5ab5aad | ||
|
|
7e581e6ad7 | ||
|
|
36fa84e4b0 | ||
|
|
3fb4cba856 | ||
|
|
00f98cc505 | ||
|
|
d9f719196a | ||
|
|
4b0c7245bd | ||
|
|
ced3a2a45a | ||
|
|
e6838e0a1f | ||
|
|
9e637ce0db | ||
|
|
acbee743a0 | ||
|
|
fa179fa30b | ||
|
|
2376f75f1d | ||
|
|
ca95822bba | ||
|
|
6fbcd2158a | ||
|
|
389b78f748 | ||
|
|
99368b9fdd | ||
|
|
a5a4621e25 | ||
|
|
fe95ebaa95 | ||
|
|
01c7ca27fe | ||
|
|
96ab3146be | ||
|
|
3c920cfed2 | ||
|
|
db7caf1997 | ||
|
|
808815bdab | ||
|
|
8a8fd7f5a9 | ||
|
|
3b3cb6d61d | ||
|
|
30a45f1d14 | ||
|
|
d10f628f1d | ||
|
|
615da18dc9 | ||
|
|
be11c13f67 | ||
|
|
cb7f0aa41e | ||
|
|
b2cf3a5505 | ||
|
|
0d19c46d18 | ||
|
|
c05832db67 | ||
|
|
e76dbef5f5 | ||
|
|
25c00c80b7 | ||
|
|
012206e4d8 | ||
|
|
de9da437f1 | ||
|
|
153fe15ed3 | ||
|
|
b58374aff1 | ||
|
|
31ae084538 | ||
|
|
f980126e81 | ||
|
|
cb1ada1bd7 | ||
|
|
200d340123 | ||
|
|
5c2f4dd84e | ||
|
|
953d175b44 | ||
|
|
f52a463728 | ||
|
|
5f25049abc | ||
|
|
8edaf67197 | ||
|
|
41c1979283 | ||
|
|
9840891cdc | ||
|
|
7d22e18bfb | ||
|
|
07af6f2741 | ||
|
|
60d9819088 | ||
|
|
9549539f5d | ||
|
|
1c23daa3b1 | ||
|
|
4a6c37ae0c | ||
|
|
f1bfd58dd2 | ||
|
|
4446814114 | ||
|
|
1ae6236e90 | ||
|
|
624a8bbe71 | ||
|
|
ca876b291a | ||
|
|
c850a7eae1 | ||
|
|
5613a3cef3 | ||
|
|
d3b161fc25 | ||
|
|
7659e45cc1 | ||
|
|
8a28d66393 | ||
|
|
c8d92e41b2 | ||
|
|
9b39c90849 | ||
|
|
5c8a34696e | ||
|
|
3e5da7c25a | ||
|
|
b1e6770712 | ||
|
|
fd49fd6456 | ||
|
|
d0ff2fef35 | ||
|
|
4c3313e275 | ||
|
|
4e0bc36b02 | ||
|
|
01eb84e3a6 | ||
|
|
70c97e2ae4 | ||
|
|
c165c8e71f | ||
|
|
6dd9773f2c | ||
|
|
15dbb0a634 | ||
|
|
ce09ac2a92 | ||
|
|
425f936254 | ||
|
|
7a9e4b0e8f | ||
|
|
48f10011e1 | ||
|
|
f6d3f799dd | ||
|
|
2157f4a385 | ||
|
|
0f76d05546 | ||
|
|
293f49e178 | ||
|
|
4d2de2dd57 | ||
|
|
46dc965cd0 | ||
|
|
707aeb6d65 | ||
|
|
96c63c60a2 | ||
|
|
9539123fc3 | ||
|
|
ea07346ae6 | ||
|
|
af1e440103 | ||
|
|
0cea7d23f0 | ||
|
|
ea8c88a31a | ||
|
|
b4e5544ff8 | ||
|
|
04d534cd30 | ||
|
|
7cb247976f | ||
|
|
3cbf9e14e6 | ||
|
|
3bd6516440 | ||
|
|
e3f691fbda | ||
|
|
ae76bea220 | ||
|
|
ca81a507b6 | ||
|
|
3f2382cfdc | ||
|
|
c1ccae315f | ||
|
|
92cb4e3d29 | ||
|
|
9dd8c45c57 | ||
|
|
fa84a84a40 | ||
|
|
9e747e7c2e | ||
|
|
79306e0618 | ||
|
|
139073dc3e | ||
|
|
18d441ef2e | ||
|
|
e07058ef5a | ||
|
|
f02941445b | ||
|
|
37b172dbfd | ||
|
|
4b2fc37015 | ||
|
|
ae219c83fb | ||
|
|
e60c5c4546 | ||
|
|
f57adb33eb | ||
|
|
25821c1294 | ||
|
|
8904e9d709 | ||
|
|
35885ef59f | ||
|
|
c19eb5847a | ||
|
|
c591f1d23e | ||
|
|
fd1f76169a | ||
|
|
8814746738 | ||
|
|
7bb6c4f0c1 | ||
|
|
6c14282223 | ||
|
|
7333760ada | ||
|
|
cb0b5feef8 | ||
|
|
01369464fa | ||
|
|
c2ceaa9a3f | ||
|
|
36716508c3 | ||
|
|
d683a76a49 | ||
|
|
4a0f6ef8af | ||
|
|
92b735b7fa | ||
|
|
240db01e75 | ||
|
|
04bacccfea | ||
|
|
25b6e8c2d7 | ||
|
|
1e2ceb8252 | ||
|
|
329a5aec32 | ||
|
|
c86ae623cb | ||
|
|
ce8dbc4302 | ||
|
|
72972c76af | ||
|
|
d55813fffb | ||
|
|
1f92e96079 | ||
|
|
2cb3c1fc9f | ||
|
|
caaf2b0d1e | ||
|
|
a635e4cd30 | ||
|
|
aa336fb525 | ||
|
|
c0dc08116c | ||
|
|
a5b3334222 | ||
|
|
8fd2491b9c | ||
|
|
deea93acad | ||
|
|
ba92feee4a | ||
|
|
e6458c26d6 | ||
|
|
4befbfafc5 | ||
|
|
fc938ea3eb | ||
|
|
5e444a4f7c | ||
|
|
1250627862 | ||
|
|
a588bdc7b3 | ||
|
|
44a4d13bad | ||
|
|
7467806969 | ||
|
|
9f844a8b91 | ||
|
|
83435a47de | ||
|
|
33612590ed | ||
|
|
7475e9576c | ||
|
|
56d114f13d | ||
|
|
50f702e9f1 | ||
|
|
43a361a52d | ||
|
|
85976a1f35 | ||
|
|
167cc6862a | ||
|
|
5ca57e4110 | ||
|
|
988d09ed2a | ||
|
|
bafa5fad54 | ||
|
|
a2e816253f | ||
|
|
68603f9aed | ||
|
|
46ac480713 | ||
|
|
35cad794e9 | ||
|
|
ee1a0c2c59 | ||
|
|
6f2ded4ce8 | ||
|
|
756b86a416 | ||
|
|
4b22fd2095 | ||
|
|
368854ba41 | ||
|
|
af4d25ee37 | ||
|
|
cb460ee7ba | ||
|
|
300cbd090f | ||
|
|
e32c15204c | ||
|
|
b8b68af316 | ||
|
|
0a5fb5e9e7 | ||
|
|
99f475b56f | ||
|
|
0b1ff75f1b | ||
|
|
ca653cd245 | ||
|
|
84563bdcd4 | ||
|
|
49215503a6 | ||
|
|
9a8aafc189 | ||
|
|
347808e86c | ||
|
|
f34960d82a | ||
|
|
c6165ee502 | ||
|
|
f676949460 | ||
|
|
73cfaee5ec | ||
|
|
a6983d2d99 | ||
|
|
43e4a5b250 | ||
|
|
7b2c027c26 | ||
|
|
e2069889b4 | ||
|
|
499b3f1ff4 | ||
|
|
b4713f9bc6 | ||
|
|
5132ee3559 | ||
|
|
2a702d1cb5 | ||
|
|
23a4859e0e | ||
|
|
ae94aecdd7 | ||
|
|
a6edf34a92 | ||
|
|
c26b1335f5 | ||
|
|
fdeb7689d7 | ||
|
|
2d5e765193 | ||
|
|
29d82736a7 | ||
|
|
e493c65b12 | ||
|
|
ada6f533b7 | ||
|
|
5ea578b8c8 | ||
|
|
67cb6abe56 | ||
|
|
3ff6a02391 | ||
|
|
dde83e7f67 | ||
|
|
fe527ff5dd | ||
|
|
4f99bbace9 | ||
|
|
ad0ac34f9d | ||
|
|
ba3306b497 | ||
|
|
7ff8b34e80 | ||
|
|
fc4b1464b9 | ||
|
|
485347e20b | ||
|
|
34676105a1 | ||
|
|
339eacb01f | ||
|
|
27a047976b | ||
|
|
3289129782 | ||
|
|
afa715c860 | ||
|
|
ae0eddfb25 | ||
|
|
13b2f8018d | ||
|
|
eb66ce2d4b | ||
|
|
ad16b0b5a6 | ||
|
|
c81f519b7c | ||
|
|
6450c0bee6 | ||
|
|
87d1db760f | ||
|
|
1c903f4d26 | ||
|
|
b84a8bc76a | ||
|
|
e77f059685 | ||
|
|
4e108d434a | ||
|
|
0f626bebbf | ||
|
|
cff57b6562 | ||
|
|
5cb9212fa4 | ||
|
|
c2910d742a | ||
|
|
e8174f7462 | ||
|
|
f245d97fc0 | ||
|
|
469c03f5e7 | ||
|
|
3144c45688 | ||
|
|
99e746ba81 | ||
|
|
7527b9f9b1 | ||
|
|
5e6add724d | ||
|
|
a5cd9a4968 | ||
|
|
c5fe481c33 | ||
|
|
38b0ace0ca | ||
|
|
1819f38ccb | ||
|
|
4834ecbddb | ||
|
|
3aa5fdba55 | ||
|
|
8ae987ea69 | ||
|
|
d77f543c8f | ||
|
|
517415f743 | ||
|
|
b9770e7e73 | ||
|
|
93cb8a2411 | ||
|
|
4866d2d190 | ||
|
|
80a9d05ff3 | ||
|
|
1db7aa3f26 | ||
|
|
ff1e11022d | ||
|
|
b55bf31fdc | ||
|
|
75c557ec63 | ||
|
|
2116ba19f6 | ||
|
|
8b9375ea68 | ||
|
|
9a024c6146 | ||
|
|
642de684e8 | ||
|
|
eb18d759f1 | ||
|
|
3fa41ea8d9 | ||
|
|
1147d6ba4a | ||
|
|
b0bebcd162 | ||
|
|
12dedf2047 | ||
|
|
9af8184221 | ||
|
|
62eeb06abe | ||
|
|
e60b5f670a | ||
|
|
6b2e64ef96 | ||
|
|
d6bfc773de | ||
|
|
a1e4b50b29 | ||
|
|
38c8762f64 | ||
|
|
ddd5b581fa | ||
|
|
e2afe4b787 | ||
|
|
b24ff33fe0 | ||
|
|
0922ee202a | ||
|
|
946fcf2b25 | ||
|
|
a517a8038f | ||
|
|
faca17ff78 | ||
|
|
d4e6126ec3 | ||
|
|
5c0da06d66 | ||
|
|
0d3bcb0b2c | ||
|
|
e0ab8fc8e2 | ||
|
|
011f12a7e4 | ||
|
|
f7f146df04 | ||
|
|
803a6d8a25 | ||
|
|
fa907a7f2b | ||
|
|
033a3a9db9 | ||
|
|
4a0dbde6b7 | ||
|
|
e41e08f2af | ||
|
|
c738f715a3 | ||
|
|
0d2572d37d | ||
|
|
a0df846493 | ||
|
|
01a6d60890 | ||
|
|
8721999a22 | ||
|
|
2b505466ce | ||
|
|
17627d0775 | ||
|
|
0f87fb86a9 |
@@ -1,4 +1,4 @@
|
||||
# http://editorconfig.org
|
||||
# https://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
@@ -7,3 +7,4 @@ examples
|
||||
node_modules
|
||||
src/react
|
||||
scripts
|
||||
|
||||
|
||||
129
.eslintrc.cjs
@@ -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'
|
||||
},
|
||||
@@ -64,26 +72,16 @@ module.exports = {
|
||||
}
|
||||
],
|
||||
'@typescript-eslint/consistent-type-imports': 'warn',
|
||||
// These are commented out for now as we may want to add them to improve function boundary safety
|
||||
// "@typescript-eslint/explicit-function-return-type": [
|
||||
// "error",
|
||||
// {
|
||||
// allowTypedFunctionExpressions: true,
|
||||
// },
|
||||
// ],
|
||||
// "@typescript-eslint/explicit-member-accessibility": "warn",
|
||||
// "@typescript-eslint/explicit-module-boundary-types": "error",
|
||||
'@typescript-eslint/no-base-to-string': 'error',
|
||||
'@typescript-eslint/no-confusing-non-null-assertion': 'error',
|
||||
'@typescript-eslint/no-invalid-void-type': 'error',
|
||||
'@typescript-eslint/no-require-imports': 'error',
|
||||
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 'warn',
|
||||
'@typescript-eslint/no-unnecessary-condition': 'warn',
|
||||
'@typescript-eslint/no-unnecessary-condition': 'off',
|
||||
'@typescript-eslint/no-unnecessary-qualifier': 'warn',
|
||||
'@typescript-eslint/non-nullable-type-assertion-style': 'warn',
|
||||
'@typescript-eslint/prefer-for-of': 'warn',
|
||||
'@typescript-eslint/prefer-optional-chain': 'warn',
|
||||
'@typescript-eslint/prefer-readonly': 'warn',
|
||||
'@typescript-eslint/prefer-ts-expect-error': 'warn',
|
||||
'@typescript-eslint/prefer-return-this-type': 'error',
|
||||
'@typescript-eslint/prefer-string-starts-ends-with': 'warn',
|
||||
@@ -93,43 +91,23 @@ module.exports = {
|
||||
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
|
||||
'@typescript-eslint/member-delimiter-style': 'warn',
|
||||
'@typescript-eslint/method-signature-style': 'warn',
|
||||
'@typescript-eslint/naming-convention': [
|
||||
'warn',
|
||||
{
|
||||
selector: 'default',
|
||||
format: ['camelCase']
|
||||
},
|
||||
{
|
||||
selector: ['function', 'enumMember', 'property'],
|
||||
format: ['camelCase', 'PascalCase']
|
||||
},
|
||||
{
|
||||
selector: 'variable',
|
||||
modifiers: ['const'],
|
||||
format: ['camelCase', 'PascalCase', 'UPPER_CASE']
|
||||
},
|
||||
{
|
||||
selector: 'typeLike',
|
||||
format: ['PascalCase']
|
||||
},
|
||||
{
|
||||
selector: 'typeProperty',
|
||||
format: ['camelCase', 'PascalCase', 'UPPER_CASE']
|
||||
},
|
||||
{
|
||||
selector: 'objectLiteralProperty',
|
||||
format: null
|
||||
}
|
||||
],
|
||||
'@typescript-eslint/no-extraneous-class': 'error',
|
||||
'@typescript-eslint/no-parameter-properties': 'error',
|
||||
'@typescript-eslint/no-redundant-type-constituents': 'off',
|
||||
'@typescript-eslint/parameter-properties': 'error',
|
||||
'@typescript-eslint/strict-boolean-expressions': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.cjs'],
|
||||
env: {
|
||||
node: true
|
||||
}
|
||||
},
|
||||
{
|
||||
extends: ['plugin:chai-expect/recommended', 'plugin:chai-friendly/recommended'],
|
||||
files: ['*.test.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unsafe-call': 'off',
|
||||
'@typescript-eslint/no-unused-expressions': 'off'
|
||||
}
|
||||
}
|
||||
@@ -137,10 +115,12 @@ module.exports = {
|
||||
rules: {
|
||||
'no-template-curly-in-string': 'error',
|
||||
'array-callback-return': 'error',
|
||||
'comma-dangle': 'off',
|
||||
'consistent-return': 'error',
|
||||
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',
|
||||
@@ -169,7 +149,7 @@ module.exports = {
|
||||
'no-useless-concat': 'error',
|
||||
'no-useless-return': 'warn',
|
||||
'prefer-promise-reject-errors': 'error',
|
||||
radix: 'error',
|
||||
radix: 'off',
|
||||
'require-await': 'error',
|
||||
'wrap-iife': ['warn', 'inside'],
|
||||
'no-shadow': 'error',
|
||||
@@ -185,37 +165,15 @@ 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'],
|
||||
'func-style': ['warn', 'declaration'],
|
||||
'one-var': ['warn', 'never'],
|
||||
'operator-assignment': 'warn',
|
||||
'prefer-arrow-callback': 'warn',
|
||||
'no-restricted-syntax': [
|
||||
'warn',
|
||||
{
|
||||
selector: "CallExpression[callee.name='String']",
|
||||
message: "Don't use the String function. Use .toString() instead."
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.name='Number']",
|
||||
message: "Don't use the Number function. Use parseInt or parseFloat instead."
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.name='Boolean']",
|
||||
message: "Don't use the Boolean function. Use a strict comparison instead."
|
||||
}
|
||||
],
|
||||
'no-restricted-imports': [
|
||||
'warn',
|
||||
{
|
||||
patterns: [
|
||||
{
|
||||
group: ['../*'],
|
||||
message: 'Usage of relative parent imports is not allowed.'
|
||||
}
|
||||
],
|
||||
paths: [
|
||||
{
|
||||
name: '.',
|
||||
@@ -228,27 +186,24 @@ module.exports = {
|
||||
]
|
||||
}
|
||||
],
|
||||
'import/no-duplicates': 'warn',
|
||||
'import/order': [
|
||||
'warn',
|
||||
'import/extensions': [
|
||||
'error',
|
||||
'always',
|
||||
{
|
||||
groups: ['builtin', 'external', ['parent', 'sibling', 'internal', 'index']],
|
||||
pathGroups: [
|
||||
{
|
||||
pattern: '~/**',
|
||||
group: 'internal'
|
||||
},
|
||||
{
|
||||
pattern: 'dist/**',
|
||||
group: 'external'
|
||||
}
|
||||
],
|
||||
alphabetize: {
|
||||
order: 'asc',
|
||||
caseInsensitive: true
|
||||
},
|
||||
'newlines-between': 'never',
|
||||
warnOnUnassignedImports: true
|
||||
ignorePackages: true,
|
||||
pattern: {
|
||||
js: 'always',
|
||||
ts: 'never'
|
||||
}
|
||||
}
|
||||
],
|
||||
'import/no-duplicates': 'warn',
|
||||
'sort-imports-es6-autofix/sort-imports-es6': [
|
||||
2,
|
||||
{
|
||||
ignoreCase: true,
|
||||
ignoreMemberSort: false,
|
||||
memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single']
|
||||
}
|
||||
],
|
||||
'wc/guard-super-call': 'off'
|
||||
|
||||
7
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -3,8 +3,7 @@ name: Bug Report
|
||||
about: Create a bug report to help us fix a demonstrable problem with code in the library.
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: claviska
|
||||
|
||||
assignees:
|
||||
---
|
||||
|
||||
### Describe the bug
|
||||
@@ -29,8 +28,8 @@ If applicable, add screenshots to help explain the bug.
|
||||
|
||||
### Browser / OS
|
||||
- OS: [e.g. Mac, Windows]
|
||||
- Browser [e.g. Chrome, Firefox, Safari]
|
||||
- Browser version [e.g. 22]
|
||||
- Browser: [e.g. Chrome, Firefox, Safari]
|
||||
- Browser version: [e.g. 22]
|
||||
|
||||
### Additional information
|
||||
Provide any additional information about the bug here.
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -3,8 +3,6 @@ name: Feature Request
|
||||
about: Suggest an idea for this project.
|
||||
title: ''
|
||||
labels: feature
|
||||
assignees: claviska
|
||||
|
||||
---
|
||||
|
||||
### What issue are you having?
|
||||
|
||||
2
.github/SECURITY.md
vendored
@@ -4,4 +4,4 @@ We take security issues in Shoelace very seriously and appreciate your efforts t
|
||||
|
||||
To report a security issue, email [cory@abeautifulsite.net](mailto:cory@abeautifulsite.net) and include "SHOELACE SECURITY" in the subject line.
|
||||
|
||||
Well respond as soon as possible and keep you updated throughout the process.
|
||||
We'll respond as soon as possible and keep you updated throughout the process.
|
||||
|
||||
6
.github/workflows/node.js.yml
vendored
@@ -15,13 +15,13 @@ 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:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
|
||||
9
.gitignore
vendored
@@ -1,8 +1,9 @@
|
||||
.DS_Store
|
||||
_site
|
||||
.cache
|
||||
docs/dist
|
||||
docs/search.json
|
||||
.DS_Store
|
||||
dist
|
||||
examples
|
||||
docs/assets/images/sprite.svg
|
||||
node_modules
|
||||
src/react
|
||||
cdn
|
||||
web-types.json
|
||||
@@ -1,13 +1,14 @@
|
||||
*.hbs
|
||||
*.md
|
||||
.cache
|
||||
.github
|
||||
cspell.json
|
||||
dist
|
||||
docs/*.md
|
||||
docs/search.json
|
||||
src/components/icon/icons
|
||||
src/react/index.ts
|
||||
node_modules
|
||||
package.json
|
||||
package-lock.json
|
||||
tsconfig.json
|
||||
cdn
|
||||
_site
|
||||
|
||||
5
.vscode/settings.json
vendored
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,9 +51,7 @@ Once you've cloned the repo, run the following command.
|
||||
npm start
|
||||
```
|
||||
|
||||
This will spin up the Shoelace dev server. After the initial build, a browser will open automatically. There is currently no hot module reloading (HMR), as browser's don't provide a way to reregister custom elements, but most changes to the source will reload the browser automatically.
|
||||
|
||||
The documentation is powered by Docsify, which uses raw markdown files to generate pages. As such, no static files are built for the docs.
|
||||
This will spin up the dev server. After the initial build, a browser will open automatically. There is currently no hot module reloading (HMR), as browser's don't provide a way to reregister custom elements, but most changes to the source will reload the browser automatically.
|
||||
|
||||
### Building
|
||||
|
||||
|
||||
61
cspell.json
@@ -1,25 +1,39 @@
|
||||
{
|
||||
"version": "0.2",
|
||||
"words": [
|
||||
"activedescendant",
|
||||
"allowfullscreen",
|
||||
"animationend",
|
||||
"Animista",
|
||||
"apos",
|
||||
"atrule",
|
||||
"autocorrect",
|
||||
"autofix",
|
||||
"autoload",
|
||||
"autoloader",
|
||||
"autoloading",
|
||||
"autoplay",
|
||||
"bezier",
|
||||
"boxicons",
|
||||
"CACHEABLE",
|
||||
"callout",
|
||||
"callouts",
|
||||
"cdndir",
|
||||
"chatbubble",
|
||||
"checkmark",
|
||||
"claviska",
|
||||
"Clippy",
|
||||
"codebases",
|
||||
"codepen",
|
||||
"colocated",
|
||||
"colour",
|
||||
"combobox",
|
||||
"Commonmark",
|
||||
"Composability",
|
||||
"Consolas",
|
||||
"contenteditable",
|
||||
"copydir",
|
||||
"Cotte",
|
||||
"coverpage",
|
||||
"crossorigin",
|
||||
"crutchcorn",
|
||||
@@ -28,16 +42,28 @@
|
||||
"datetime",
|
||||
"describedby",
|
||||
"Docsify",
|
||||
"dogfood",
|
||||
"dropdowns",
|
||||
"easings",
|
||||
"endraw",
|
||||
"enterkeyhint",
|
||||
"eqeqeq",
|
||||
"erroneou",
|
||||
"errormessage",
|
||||
"esbuild",
|
||||
"exportmaps",
|
||||
"exportparts",
|
||||
"fieldsets",
|
||||
"formaction",
|
||||
"formdata",
|
||||
"formenctype",
|
||||
"formmethod",
|
||||
"formnovalidate",
|
||||
"formtarget",
|
||||
"FOUC",
|
||||
"FOUCE",
|
||||
"fullscreen",
|
||||
"gestern",
|
||||
"giga",
|
||||
"globby",
|
||||
"Grayscale",
|
||||
@@ -46,34 +72,48 @@
|
||||
"hexa",
|
||||
"Iconoir",
|
||||
"Iframes",
|
||||
"iife",
|
||||
"inputmode",
|
||||
"ionicon",
|
||||
"ionicons",
|
||||
"jsDelivr",
|
||||
"jsfiddle",
|
||||
"jsonata",
|
||||
"keydown",
|
||||
"keyframes",
|
||||
"Kool",
|
||||
"labelledby",
|
||||
"Laravel",
|
||||
"LaViska",
|
||||
"linkify",
|
||||
"listbox",
|
||||
"listitem",
|
||||
"litelement",
|
||||
"lowercasing",
|
||||
"Lucide",
|
||||
"maxlength",
|
||||
"Menlo",
|
||||
"menuitemcheckbox",
|
||||
"menuitemradio",
|
||||
"middlewares",
|
||||
"minlength",
|
||||
"monospace",
|
||||
"mousedown",
|
||||
"mousemove",
|
||||
"mouseup",
|
||||
"multiselectable",
|
||||
"nextjs",
|
||||
"nocheck",
|
||||
"noopener",
|
||||
"noreferrer",
|
||||
"novalidate",
|
||||
"npmdir",
|
||||
"Numberish",
|
||||
"outdir",
|
||||
"ParamagicDev",
|
||||
"peta",
|
||||
"petabit",
|
||||
"popperjs",
|
||||
"prismjs",
|
||||
"progressbar",
|
||||
"radiogroup",
|
||||
"Railsbyte",
|
||||
@@ -81,30 +121,46 @@
|
||||
"reregister",
|
||||
"resizer",
|
||||
"resizers",
|
||||
"retargeted",
|
||||
"RETRYABLE",
|
||||
"rgba",
|
||||
"roadmap",
|
||||
"Roboto",
|
||||
"roledescription",
|
||||
"Sapan",
|
||||
"saturationl",
|
||||
"Schilp",
|
||||
"scrollbars",
|
||||
"scrollend",
|
||||
"scroller",
|
||||
"Segoe",
|
||||
"semibold",
|
||||
"sitedir",
|
||||
"slotchange",
|
||||
"smartquotes",
|
||||
"spacebar",
|
||||
"stylesheet",
|
||||
"Tabbable",
|
||||
"tabindex",
|
||||
"tabler",
|
||||
"tablist",
|
||||
"tabpanel",
|
||||
"templating",
|
||||
"tera",
|
||||
"testid",
|
||||
"textareas",
|
||||
"textfield",
|
||||
"tinycolor",
|
||||
"transitionend",
|
||||
"treeitem",
|
||||
"treeshaking",
|
||||
"Triaging",
|
||||
"ttsc",
|
||||
"turbolinks",
|
||||
"typeof",
|
||||
"unbundles",
|
||||
"unbundling",
|
||||
"unicons",
|
||||
"unsanitized",
|
||||
"unsupportive",
|
||||
"valpha",
|
||||
"valuenow",
|
||||
@@ -116,6 +172,7 @@
|
||||
"ignorePaths": [
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
"docs/assets/examples/include.html",
|
||||
".vscode/**",
|
||||
"src/translations/!(en).ts",
|
||||
"**/*.min.js"
|
||||
|
||||
@@ -1,14 +1,34 @@
|
||||
import fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { customElementJetBrainsPlugin } from 'custom-element-jet-brains-integration';
|
||||
import { customElementVsCodePlugin } from 'custom-element-vs-code-integration';
|
||||
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'],
|
||||
globs: ['src/components/**/*.component.ts'],
|
||||
exclude: ['**/*.styles.ts', '**/*.test.ts'],
|
||||
plugins: [
|
||||
// Append package data
|
||||
@@ -18,7 +38,34 @@ export default {
|
||||
customElementsManifest.package = { name, description, version, author, homepage, license };
|
||||
}
|
||||
},
|
||||
// Infer tag names because we no longer use @customElement decorators.
|
||||
{
|
||||
name: 'shoelace-infer-tag-names',
|
||||
analyzePhase({ ts, node, moduleDoc }) {
|
||||
switch (node.kind) {
|
||||
case ts.SyntaxKind.ClassDeclaration: {
|
||||
const className = node.name.getText();
|
||||
const classDoc = moduleDoc?.declarations?.find(declaration => declaration.name === className);
|
||||
|
||||
const importPath = moduleDoc.path;
|
||||
|
||||
// This is kind of a best guess at components. "thing.component.ts"
|
||||
if (!importPath.endsWith('.component.ts')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tagNameWithoutPrefix = path.basename(importPath, '.component.ts');
|
||||
const tagName = 'sl-' + tagNameWithoutPrefix;
|
||||
|
||||
classDoc.tagNameWithoutPrefix = tagNameWithoutPrefix;
|
||||
classDoc.tagName = tagName;
|
||||
|
||||
// This used to be set to true by @customElement
|
||||
classDoc.customElement = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// Parse custom jsDoc tags
|
||||
{
|
||||
name: 'shoelace-custom-tags',
|
||||
@@ -27,7 +74,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 => {
|
||||
@@ -40,6 +87,9 @@ export default {
|
||||
});
|
||||
});
|
||||
|
||||
// This is what allows us to map JSDOC comments to ReactWrappers.
|
||||
classDoc['jsDoc'] = node.jsDoc?.map(jsDoc => jsDoc.getFullText()).join('\n');
|
||||
|
||||
const parsed = parse(`${customComments}\n */`);
|
||||
parsed[0].tags?.forEach(t => {
|
||||
switch (t.tag) {
|
||||
@@ -63,8 +113,10 @@ export default {
|
||||
break;
|
||||
|
||||
// Value-only metadata tags
|
||||
case 'documentation':
|
||||
case 'since':
|
||||
case 'status':
|
||||
case 'title':
|
||||
classDoc[t.tag] = t.name;
|
||||
break;
|
||||
|
||||
@@ -85,7 +137,6 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'shoelace-react-event-names',
|
||||
analyzePhase({ ts, node, moduleDoc }) {
|
||||
@@ -97,11 +148,68 @@ export default {
|
||||
if (classDoc?.events) {
|
||||
classDoc.events.forEach(event => {
|
||||
event.reactName = `on${pascalCase(event.name)}`;
|
||||
event.eventName = `${pascalCase(event.name)}Event`;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
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: /\.component.(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
|
||||
customElementVsCodePlugin({
|
||||
outdir,
|
||||
cssFileName: null,
|
||||
referencesTemplate: (_, tag) => [
|
||||
{
|
||||
name: 'Documentation',
|
||||
url: `https://shoelace.style/components/${tag.replace('sl-', '')}`
|
||||
}
|
||||
]
|
||||
}),
|
||||
customElementJetBrainsPlugin({
|
||||
excludeCss: true,
|
||||
referencesTemplate: (_, tag) => {
|
||||
return {
|
||||
name: 'Documentation',
|
||||
url: `https://shoelace.style/components/${tag.replace('sl-', '')}`
|
||||
};
|
||||
}
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# Not Found
|
||||
|
||||
<img class="not-found-image" src="/assets/images/undraw-not-found.svg" alt="Cute monsters hiding behind a tree">
|
||||
|
||||
Sorry, I couldn't find that page. Have you tried pressing <kbd>/</kbd> to search?
|
||||
349
docs/_includes/component.njk
Normal file
@@ -0,0 +1,349 @@
|
||||
{% extends "default.njk" %}
|
||||
|
||||
{# Find the component based on the `tag` front matter #}
|
||||
{% set component = getComponent('sl-' + page.fileSlug) %}
|
||||
|
||||
{% block content %}
|
||||
{# Determine the badge variant #}
|
||||
{% if component.status == 'stable' %}
|
||||
{% set badgeVariant = 'primary' %}
|
||||
{% elseif component.status == 'experimental' %}
|
||||
{% set badgeVariant = 'warning' %}
|
||||
{% elseif component.status == 'planned' %}
|
||||
{% set badgeVariant = 'neutral' %}
|
||||
{% elseif component.status == 'deprecated' %}
|
||||
{% set badgeVariant = 'danger' %}
|
||||
{% else %}
|
||||
{% set badgeVariant = 'neutral' %}
|
||||
{% endif %}
|
||||
|
||||
{# Header #}
|
||||
<header class="component-header">
|
||||
<h1>{{ component.name | classNameToComponentName }}</h1>
|
||||
|
||||
<div class="component-header__tag">
|
||||
<code><{{ component.tagName }}> | {{ component.name }}</code>
|
||||
</div>
|
||||
|
||||
<div class="component-header__info">
|
||||
<sl-badge variant="neutral" pill>
|
||||
Since {{component.since or '?' }}
|
||||
</sl-badge>
|
||||
<sl-badge variant="{{ badgeVariant }}" pill style="text-transform: capitalize;">
|
||||
{{ component.status }}
|
||||
</sl-badge>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<p class="component-summary">
|
||||
{% if component.summary %}
|
||||
{{ component.summary | markdownInline | safe }}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{# Markdown content #}
|
||||
{{ content | safe }}
|
||||
|
||||
{# Importing #}
|
||||
<h2>Importing</h2>
|
||||
<p>
|
||||
If you're using the autoloader or the traditional loader, you can ignore this section. Otherwise, feel free to use
|
||||
any of the following snippets to <a href="/getting-started/installation#cherry-picking">cherry pick</a> this component.
|
||||
</p>
|
||||
|
||||
<sl-tab-group>
|
||||
<sl-tab slot="nav" panel="script">Script</sl-tab>
|
||||
<sl-tab slot="nav" panel="import">Import</sl-tab>
|
||||
<sl-tab slot="nav" panel="bundler">Bundler</sl-tab>
|
||||
<sl-tab slot="nav" panel="react">React</sl-tab>
|
||||
|
||||
<sl-tab-panel name="script">
|
||||
<p>
|
||||
To import this component from <a href="https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace">the CDN</a>
|
||||
using a script tag:
|
||||
</p>
|
||||
<pre><code class="language-html"><script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@{{ meta.version }}/{{ meta.cdndir }}/{{ component.path }}"></script></code></pre>
|
||||
</sl-tab-panel>
|
||||
|
||||
<sl-tab-panel name="import">
|
||||
<p>
|
||||
To import this component from <a href="https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace">the CDN</a>
|
||||
using a JavaScript import:
|
||||
</p>
|
||||
<pre><code class="language-js">import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@{{ meta.version }}/{{ meta.cdndir }}/{{ component.path }}';</code></pre>
|
||||
</sl-tab-panel>
|
||||
|
||||
<sl-tab-panel name="bundler">
|
||||
<p>
|
||||
To import this component using <a href="{{ rootUrl('/getting-started/installation#bundling') }}">a bundler</a>:
|
||||
</p>
|
||||
<pre><code class="language-js">import '@shoelace-style/shoelace/{{ meta.npmdir }}/{{ component.path }}';</code></pre>
|
||||
</sl-tab-panel>
|
||||
|
||||
<sl-tab-panel name="react">
|
||||
<p>
|
||||
To import this component as a <a href="/frameworks/react">React component</a>:
|
||||
</p>
|
||||
<pre><code class="language-js">import {{ component.name }} from '@shoelace-style/shoelace/{{ meta.npmdir }}/react/{{ component.tagNameWithoutPrefix }}';</code></pre>
|
||||
</sl-tab-panel>
|
||||
</sl-tab-group>
|
||||
|
||||
{# Slots #}
|
||||
{% if component.slots.length %}
|
||||
<h2>Slots</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-name">Name</th>
|
||||
<th class="table-description">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for slot in component.slots %}
|
||||
<tr>
|
||||
<td class="nowrap">
|
||||
{% if slot.name %}
|
||||
<code>{{ slot.name }}</code>
|
||||
{% else %}
|
||||
(default)
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ slot.description | markdownInline | safe }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#slots') }}">using slots</a>.</em></p>
|
||||
{% endif %}
|
||||
|
||||
{# Properties #}
|
||||
{% if component.properties.length %}
|
||||
<h2>Properties</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-name">Name</th>
|
||||
<th class="table-description">Description</th>
|
||||
<th class="table-reflects">Reflects</th>
|
||||
<th class="table-type">Type</th>
|
||||
<th class="table-default">Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for prop in component.properties %}
|
||||
<tr>
|
||||
<td>
|
||||
<code class="nowrap">{{ prop.name }}</code>
|
||||
{% if prop.attribute | length > 0 %}
|
||||
{% if prop.attribute != prop.name %}
|
||||
<br>
|
||||
<sl-tooltip content="This attribute is different from its property">
|
||||
<small>
|
||||
<code class="nowrap">
|
||||
{{ prop.attribute }}
|
||||
</code>
|
||||
</small>
|
||||
</sl-tooltip>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ prop.description | markdownInline | safe }}
|
||||
</td>
|
||||
<td style="text-align: center;">
|
||||
{% if prop.reflects %}
|
||||
<sl-icon label="yes" name="check-lg"></sl-icon>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if prop.type.text %}
|
||||
<code>{{ prop.type.text | markdownInline | safe }}</code>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if prop.default %}
|
||||
<code>{{ prop.default | markdownInline | safe }}</code>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<td class="nowrap"><code>updateComplete</code></td>
|
||||
<td>
|
||||
A read-only promise that resolves when the component has
|
||||
<a href="/getting-started/usage?#component-rendering-and-updating">finished updating</a>.
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#attributes-and-properties') }}">attributes and properties</a>.</em></p>
|
||||
{% endif %}
|
||||
|
||||
{# Events #}
|
||||
{% if component.events.length %}
|
||||
<h2>Events</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-name" data-flavor="html">Name</th>
|
||||
<th class="table-name" data-flavor="react">React Event</th>
|
||||
<th class="table-description">Description</th>
|
||||
<th class="table-event-detail">Event Detail</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for event in component.events %}
|
||||
<tr>
|
||||
<td data-flavor="html"><code class="nowrap">{{ event.name }}</code></td>
|
||||
<td data-flavor="react"><code class="nowrap">{{ event.reactName }}</code></td>
|
||||
<td>{{ event.description | markdownInline | safe }}</td>
|
||||
<td>
|
||||
{% if event.type.text %}
|
||||
<code>{{ event.type.text }}</code>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#events') }}">events</a>.</em></p>
|
||||
{% endif %}
|
||||
|
||||
{# Methods #}
|
||||
{% if component.methods.length %}
|
||||
<h2>Methods</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-name">Name</th>
|
||||
<th class="table-description">Description</th>
|
||||
<th class="table-arguments">Arguments</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for method in component.methods %}
|
||||
<tr>
|
||||
<td class="nowrap"><code>{{ method.name }}()</code></td>
|
||||
<td>{{ method.description | markdownInline | safe }}</td>
|
||||
<td>
|
||||
{% if method.parameters.length %}
|
||||
<code>
|
||||
{% for param in method.parameters %}
|
||||
{{ param.name }}: {{ param.type.text }}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
</code>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#methods') }}">methods</a>.</em></p>
|
||||
{% endif %}
|
||||
|
||||
{# Custom Properties #}
|
||||
{% if component.cssProperties.length %}
|
||||
<h2>Custom Properties</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-name">Name</th>
|
||||
<th class="table-description">Description</th>
|
||||
<th class="table-default">Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for cssProperty in component.cssProperties %}
|
||||
<tr>
|
||||
<td class="nowrap"><code>{{ cssProperty.name }}</code></td>
|
||||
<td>{{ cssProperty.description | markdownInline | safe }}</td>
|
||||
<td>{{ cssProperty.default }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#custom-properties') }}">customizing CSS custom properties</a>.</em></p>
|
||||
{% endif %}
|
||||
|
||||
{# CSS Parts #}
|
||||
{% if component.cssParts.length %}
|
||||
<h2>Parts</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-name">Name</th>
|
||||
<th class="table-description">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for cssPart in component.cssParts %}
|
||||
<tr>
|
||||
<td class="nowrap"><code>{{ cssPart.name }}</code></td>
|
||||
<td>{{ cssPart.description | markdownInline | safe }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/customizing/#css-parts') }}">customizing CSS parts</a>.</em></p>
|
||||
{% endif %}
|
||||
|
||||
{# Animations #}
|
||||
{% if component.animations.length %}
|
||||
<h2>Animations</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-name">Name</th>
|
||||
<th class="table-description">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for animation in component.animations %}
|
||||
<tr>
|
||||
<td class="nowrap"><code>{{ animation.name }}</code></td>
|
||||
<td>{{ animation.description | markdownInline | safe }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/customizing#animations') }}">customizing animations</a>.</em></p>
|
||||
{% endif %}
|
||||
|
||||
{# Dependencies #}
|
||||
{% if component.dependencies.length %}
|
||||
<h2>Dependencies</h2>
|
||||
|
||||
<p>This component automatically imports the following dependencies.</p>
|
||||
|
||||
<ul>
|
||||
{% for dependency in component.dependencies %}
|
||||
<li><code><{{ dependency }}></code></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
150
docs/_includes/default.njk
Normal file
@@ -0,0 +1,150 @@
|
||||
<!DOCTYPE html>
|
||||
<html
|
||||
lang="en"
|
||||
data-layout="{{ layout }}"
|
||||
data-shoelace-version="{{ meta.version }}"
|
||||
>
|
||||
<head>
|
||||
{# Metadata #}
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="{{ meta.description }}" />
|
||||
<title>{{ meta.title }}</title>
|
||||
|
||||
{# Opt out of Turbo caching #}
|
||||
<meta name="turbo-cache-control">
|
||||
|
||||
{# Stylesheets #}
|
||||
<link rel="stylesheet" href="{{ assetUrl('styles/docs.css') }}" />
|
||||
<link rel="stylesheet" href="{{ assetUrl('styles/code-previews.css') }}" />
|
||||
<link rel="stylesheet" href="{{ assetUrl('styles/search.css') }}" />
|
||||
|
||||
{# Favicons #}
|
||||
<link rel="icon" href="{{ assetUrl('images/logo.svg') }}" type="image/x-icon" />
|
||||
|
||||
{# Twitter Cards #}
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:creator" content="shoelace_style" />
|
||||
<meta name="twitter:image" content="{{ assetUrl(meta.image, true) }}" />
|
||||
|
||||
{# OpenGraph #}
|
||||
<meta property="og:url" content="{{ rootUrl(page.url, true) }}" />
|
||||
<meta property="og:title" content="{{ meta.title }}" />
|
||||
<meta property="og:description" content="{{ meta.description }}" />
|
||||
<meta property="og:image" content="{{ assetUrl(meta.image, true) }}" />
|
||||
|
||||
{# Shoelace #}
|
||||
<link rel="stylesheet" href="/dist/themes/light.css" />
|
||||
<link rel="stylesheet" href="/dist/themes/dark.css" />
|
||||
<script type="module" src="/dist/shoelace-autoloader.js"></script>
|
||||
|
||||
{# Set the initial theme and menu states here to prevent flashing #}
|
||||
<script>
|
||||
(() => {
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const theme = localStorage.getItem('theme') || 'auto';
|
||||
document.documentElement.classList.toggle('sl-theme-dark', theme === 'dark' || (theme === 'auto' && prefersDark));
|
||||
})();
|
||||
</script>
|
||||
|
||||
{# Turbo + Scroll positioning #}
|
||||
<script src="{{ assetUrl('scripts/turbo.js') }}" type="module"></script>
|
||||
<script src="{{ assetUrl('scripts/docs.js') }}" defer></script>
|
||||
<script src="{{ assetUrl('scripts/code-previews.js') }}" defer></script>
|
||||
<script src="{{ assetUrl('scripts/lunr.js') }}" defer></script>
|
||||
<script src="{{ assetUrl('scripts/search.js') }}" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<a id="skip-to-main" class="visually-hidden" href="#main-content" data-smooth-link="false">
|
||||
Skip to main content
|
||||
</a>
|
||||
|
||||
{# Menu toggle #}
|
||||
<button id="menu-toggle" type="button" aria-label="Menu">
|
||||
<svg width="148" height="148" viewBox="0 0 148 148" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="currentColor" stroke-width="18" fill="none" fill-rule="evenodd" stroke-linecap="round">
|
||||
<path d="M9.5 125.5h129M9.5 74.5h129M9.5 23.5h129"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{# Icon toolbar #}
|
||||
<div id="icon-toolbar">
|
||||
{# GitHub #}
|
||||
<a href="https://github.com/shoelace-style/shoelace" title="View Shoelace on GitHub">
|
||||
<sl-icon name="github"></sl-icon>
|
||||
</a>
|
||||
|
||||
{# Twitter #}
|
||||
<a href="https://twitter.com/shoelace_style" title="Follow Shoelace on Twitter">
|
||||
<sl-icon name="twitter"></sl-icon>
|
||||
</a>
|
||||
|
||||
{# Theme selector #}
|
||||
<sl-dropdown id="theme-selector" placement="bottom-end" distance="3">
|
||||
<sl-button slot="trigger" size="small" variant="text" caret title="Press \ to toggle">
|
||||
<sl-icon class="only-light" name="sun-fill"></sl-icon>
|
||||
<sl-icon class="only-dark" name="moon-fill"></sl-icon>
|
||||
</sl-button>
|
||||
<sl-menu>
|
||||
<sl-menu-item type="checkbox" value="light">Light</sl-menu-item>
|
||||
<sl-menu-item type="checkbox" value="dark">Dark</sl-menu-item>
|
||||
<sl-divider></sl-divider>
|
||||
<sl-menu-item type="checkbox" value="auto">System</sl-menu-item>
|
||||
</sl-menu>
|
||||
</sl-dropdown>
|
||||
</div>
|
||||
|
||||
<aside id="sidebar" data-preserve-scroll>
|
||||
<header>
|
||||
<a href="/">
|
||||
<img src="{{ assetUrl('images/wordmark.svg') }}" alt="Shoelace" />
|
||||
</a>
|
||||
<div class="sidebar-version">
|
||||
{{ meta.version }}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="sidebar-buttons">
|
||||
<sl-button size="small" class="repo-button repo-button--github" href="https://github.com/shoelace-style/shoelace" target="_blank">
|
||||
<sl-icon slot="prefix" name="github"></sl-icon> Code
|
||||
</sl-button>
|
||||
<sl-button size="small" class="repo-button repo-button--star" href="https://github.com/shoelace-style/shoelace/stargazers" target="_blank">
|
||||
<sl-icon slot="prefix" name="star-fill"></sl-icon> Star
|
||||
</sl-button>
|
||||
<sl-button size="small" class="repo-button repo-button--twitter" href="https://twitter.com/shoelace_style" target="_blank">
|
||||
<sl-icon slot="prefix" name="twitter"></sl-icon> Follow
|
||||
</sl-button>
|
||||
</div>
|
||||
|
||||
<button class="search-box" type="button" title="Press / to search" aria-label="Search" data-plugin="search">
|
||||
<sl-icon name="search"></sl-icon>
|
||||
<span>Search</span>
|
||||
</button>
|
||||
|
||||
<nav>
|
||||
{% include 'sidebar.njk' %}
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
{# Content #}
|
||||
<main>
|
||||
<a id="main-content"></a>
|
||||
<article id="content" class="content{% if toc %} content--with-toc{% endif %}">
|
||||
{% if toc %}
|
||||
<div class="content__toc">
|
||||
<ul>
|
||||
<li class="top"><a href="#">{{ meta.title }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="content__body">
|
||||
{% block content %}
|
||||
{{ content | safe }}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
65
docs/_includes/sidebar.njk
Normal file
@@ -0,0 +1,65 @@
|
||||
<ul>
|
||||
<li>
|
||||
<h2>Getting Started</h2>
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/getting-started/installation">Installation</a></li>
|
||||
<li><a href="/getting-started/usage">Usage</a></li>
|
||||
<li><a href="/getting-started/themes">Themes</a></li>
|
||||
<li><a href="/getting-started/customizing">Customizing</a></li>
|
||||
<li><a href="/getting-started/form-controls">Form Controls</a></li>
|
||||
<li><a href="/getting-started/localization">Localization</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h2>Frameworks</h2>
|
||||
<ul>
|
||||
<li><a href="/frameworks/react">React</a></li>
|
||||
<li><a href="/frameworks/vue">Vue</a></li>
|
||||
<li><a href="/frameworks/angular">Angular</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h2>Resources</h2>
|
||||
<ul>
|
||||
<li><a href="/resources/community">Community</a></li>
|
||||
<li><a href="https://github.com/shoelace-style/shoelace/discussions">Help & Support</a></li>
|
||||
<li><a href="/resources/accessibility">Accessibility</a></li>
|
||||
<li><a href="/resources/contributing">Contributing</a></li>
|
||||
<li><a href="/resources/changelog">Changelog</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h2>Components</h2>
|
||||
<ul>
|
||||
{% for component in meta.components %}
|
||||
<li>
|
||||
<a href="/components/{{ component.tagName | removeSlPrefix }}">
|
||||
{{ component.name | classNameToComponentName }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h2>Design Tokens</h2>
|
||||
<ul>
|
||||
<li><a href="/tokens/typography">Typography</a></li>
|
||||
<li><a href="/tokens/color">Color</a></li>
|
||||
<li><a href="/tokens/spacing">Spacing</a></li>
|
||||
<li><a href="/tokens/elevation">Elevation</a></li>
|
||||
<li><a href="/tokens/border-radius">Border Radius</a></li>
|
||||
<li><a href="/tokens/transition">Transition</a></li>
|
||||
<li><a href="/tokens/z-index">Z-index</a></li>
|
||||
<li><a href="/tokens/more">More Tokens</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h2>Tutorials</h2>
|
||||
<ul>
|
||||
<li><a href="/tutorials/integrating-with-laravel">Integrating with Laravel</a></li>
|
||||
<li><a href="/tutorials/integrating-with-nextjs">Integrating with NextJS</a></li>
|
||||
<li><a href="/tutorials/integrating-with-rails">Integrating with Rails</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -1,89 +0,0 @@
|
||||
- Getting Started
|
||||
- [Overview](/)
|
||||
- [Installation](/getting-started/installation)
|
||||
- [Usage](/getting-started/usage)
|
||||
- [Themes](/getting-started/themes)
|
||||
- [Customizing](/getting-started/customizing)
|
||||
- [Form Controls](/getting-started/form-controls)
|
||||
- [Localization](/getting-started/localization)
|
||||
|
||||
- Frameworks
|
||||
- [React](/frameworks/react)
|
||||
- [Vue](/frameworks/vue)
|
||||
- [Angular](/frameworks/angular)
|
||||
|
||||
- Resources
|
||||
- [Community](/resources/community)
|
||||
- [Accessibility](/resources/accessibility)
|
||||
- [Contributing](/resources/contributing)
|
||||
- [Changelog](/resources/changelog)
|
||||
|
||||
- Components
|
||||
- [Alert](/components/alert)
|
||||
- [Avatar](/components/avatar)
|
||||
- [Badge](/components/badge)
|
||||
- [Breadcrumb](/components/breadcrumb)
|
||||
- [Breadcrumb Item](/components/breadcrumb-item)
|
||||
- [Button](/components/button)
|
||||
- [Button Group](/components/button-group)
|
||||
- [Card](/components/card)
|
||||
- [Checkbox](/components/checkbox)
|
||||
- [Color Picker](/components/color-picker)
|
||||
- [Details](/components/details)
|
||||
- [Dialog](/components/dialog)
|
||||
- [Divider](/components/divider)
|
||||
- [Drawer](/components/drawer)
|
||||
- [Dropdown](/components/dropdown)
|
||||
- [Icon](/components/icon)
|
||||
- [Icon Button](/components/icon-button)
|
||||
- [Image Comparer](/components/image-comparer)
|
||||
- [Input](/components/input)
|
||||
- [Menu](/components/menu)
|
||||
- [Menu Item](/components/menu-item)
|
||||
- [Menu Label](/components/menu-label)
|
||||
- [Progress Bar](/components/progress-bar)
|
||||
- [Progress Ring](/components/progress-ring)
|
||||
- [QR Code](/components/qr-code)
|
||||
- [Radio](/components/radio)
|
||||
- [Radio Group](/components/radio-group)
|
||||
- [Range](/components/range)
|
||||
- [Rating](/components/rating)
|
||||
- [Select](/components/select)
|
||||
- [Skeleton](/components/skeleton)
|
||||
- [Spinner](/components/spinner)
|
||||
- [Split Panel](/components/split-panel)
|
||||
- [Switch](/components/switch)
|
||||
- [Tab Group](/components/tab-group)
|
||||
- [Tab](/components/tab)
|
||||
- [Tab Panel](/components/tab-panel)
|
||||
- [Tag](/components/tag)
|
||||
- [Textarea](/components/textarea)
|
||||
- [Tooltip](/components/tooltip)
|
||||
<!--plop:component-->
|
||||
|
||||
- Utilities
|
||||
- [Animated Image](/components/animated-image)
|
||||
- [Animation](/components/animation)
|
||||
- [Format Bytes](/components/format-bytes)
|
||||
- [Format Date](/components/format-date)
|
||||
- [Format Number](/components/format-number)
|
||||
- [Include](/components/include)
|
||||
- [Mutation Observer](/components/mutation-observer)
|
||||
- [Relative Time](/components/relative-time)
|
||||
- [Resize Observer](/components/resize-observer)
|
||||
- [Responsive Media](/components/responsive-media)
|
||||
- [Visually Hidden](/components/visually-hidden)
|
||||
|
||||
- Design Tokens
|
||||
- [Typography](/tokens/typography)
|
||||
- [Color](/tokens/color)
|
||||
- [Spacing](/tokens/spacing)
|
||||
- [Elevation](/tokens/elevation)
|
||||
- [Border Radius](/tokens/border-radius)
|
||||
- [Transition](/tokens/transition)
|
||||
- [Z-index](/tokens/z-index)
|
||||
|
||||
- Tutorials
|
||||
- [Integrating with Laravel](/tutorials/integrating-with-laravel)
|
||||
- [Integrating with NextJS](/tutorials/integrating-with-nextjs)
|
||||
- [Integrating with Rails](/tutorials/integrating-with-rails)
|
||||
35
docs/_utilities/active-links.cjs
Normal file
@@ -0,0 +1,35 @@
|
||||
function normalizePathname(pathname) {
|
||||
// Remove /index.html
|
||||
if (pathname.endsWith('/index.html')) {
|
||||
pathname = pathname.replace(/\/index\.html/, '');
|
||||
}
|
||||
|
||||
// Remove trailing slashes
|
||||
return pathname.replace(/\/$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a class name to links that are currently active.
|
||||
*/
|
||||
module.exports = function (doc, options) {
|
||||
options = {
|
||||
className: 'active-link', // the class to add to active links
|
||||
pathname: undefined, // the current pathname to compare
|
||||
within: 'body', // element containing the target links
|
||||
...options
|
||||
};
|
||||
|
||||
const within = doc.querySelector(options.within);
|
||||
|
||||
if (!within) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
within.querySelectorAll('a').forEach(link => {
|
||||
if (normalizePathname(options.pathname) === normalizePathname(link.pathname)) {
|
||||
link.classList.add(options.className);
|
||||
}
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
64
docs/_utilities/anchor-headings.cjs
Normal file
@@ -0,0 +1,64 @@
|
||||
const { createSlug } = require('./strings.cjs');
|
||||
|
||||
/**
|
||||
* Turns headings into clickable, deep linkable anchors. The provided doc should be a document object provided by JSDOM.
|
||||
* The same document will be returned with the appropriate DOM manipulations.
|
||||
*/
|
||||
module.exports = function (doc, options) {
|
||||
options = {
|
||||
levels: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], // the headings to convert
|
||||
className: 'anchor-heading', // the class name to add
|
||||
within: 'body', // the element containing the target headings
|
||||
...options
|
||||
};
|
||||
|
||||
const within = doc.querySelector(options.within);
|
||||
|
||||
if (!within) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
within.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(heading => {
|
||||
const hasAnchor = heading.querySelector('a');
|
||||
const anchor = doc.createElement('a');
|
||||
let id = heading.textContent ?? '';
|
||||
let suffix = 0;
|
||||
|
||||
// Skip heading levels we don't care about
|
||||
if (!options.levels?.includes(heading.tagName.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert dots to underscores
|
||||
id = id.replace(/\./g, '_');
|
||||
|
||||
// Turn it into a slug
|
||||
id = createSlug(id);
|
||||
|
||||
// Make sure it starts with a letter
|
||||
if (!/^[a-z]/i.test(id)) {
|
||||
id = `id_${id}`;
|
||||
}
|
||||
|
||||
// Make sure the id is unique
|
||||
const originalId = id;
|
||||
while (doc.getElementById(id) !== null) {
|
||||
id = `${originalId}-${++suffix}`;
|
||||
}
|
||||
|
||||
if (hasAnchor || !id) return;
|
||||
|
||||
heading.setAttribute('id', id);
|
||||
anchor.setAttribute('href', `#${encodeURIComponent(id)}`);
|
||||
anchor.setAttribute('aria-label', `Direct link to "${heading.textContent}"`);
|
||||
|
||||
if (options.className) {
|
||||
heading.classList.add(options.className);
|
||||
}
|
||||
|
||||
// Append the anchor
|
||||
heading.append(anchor);
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
71
docs/_utilities/cem.cjs
Normal file
@@ -0,0 +1,71 @@
|
||||
const customElementsManifest = require('../../dist/custom-elements.json');
|
||||
|
||||
//
|
||||
// Export it here so we can import it elsewhere and use the same version
|
||||
//
|
||||
module.exports.customElementsManifest = customElementsManifest;
|
||||
|
||||
//
|
||||
// Gets all components from custom-elements.json and returns them in a more documentation-friendly format.
|
||||
//
|
||||
module.exports.getAllComponents = function () {
|
||||
const allComponents = [];
|
||||
|
||||
customElementsManifest.modules?.forEach(module => {
|
||||
module.declarations?.forEach(declaration => {
|
||||
if (declaration.customElement) {
|
||||
// Generate the dist path based on the src path and attach it to the component
|
||||
declaration.path = module.path.replace(/^src\//, 'dist/').replace(/\.ts$/, '.js');
|
||||
|
||||
// Remove members that are private or don't have a description
|
||||
const members = declaration.members?.filter(member => member.description && member.privacy !== 'private');
|
||||
const methods = members?.filter(prop => prop.kind === 'method' && prop.privacy !== 'private');
|
||||
const properties = members?.filter(prop => {
|
||||
// Look for a corresponding attribute
|
||||
const attribute = declaration.attributes?.find(attr => attr.fieldName === prop.name);
|
||||
if (attribute) {
|
||||
prop.attribute = attribute.name || attribute.fieldName;
|
||||
}
|
||||
|
||||
return prop.kind === 'field' && prop.privacy !== 'private';
|
||||
});
|
||||
allComponents.push({
|
||||
...declaration,
|
||||
methods,
|
||||
properties
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Build dependency graphs
|
||||
allComponents.forEach(component => {
|
||||
const dependencies = [];
|
||||
|
||||
// Recursively fetch sub-dependencies
|
||||
function getDependencies(tag) {
|
||||
const cmp = allComponents.find(c => c.tagName === tag);
|
||||
if (!cmp || !Array.isArray(component.dependencies)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cmp.dependencies?.forEach(dependentTag => {
|
||||
if (!dependencies.includes(dependentTag)) {
|
||||
dependencies.push(dependentTag);
|
||||
}
|
||||
getDependencies(dependentTag);
|
||||
});
|
||||
}
|
||||
|
||||
getDependencies(component.tagName);
|
||||
|
||||
component.dependencies = dependencies.sort();
|
||||
});
|
||||
|
||||
// Sort by name
|
||||
return allComponents.sort((a, b) => {
|
||||
if (a.name < b.name) return -1;
|
||||
if (a.name > b.name) return 1;
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
138
docs/_utilities/code-previews.cjs
Normal file
@@ -0,0 +1,138 @@
|
||||
let count = 1;
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns code fields with the :preview suffix into interactive code previews.
|
||||
*/
|
||||
module.exports = function (doc, options) {
|
||||
options = {
|
||||
within: 'body', // the element containing the code fields to convert
|
||||
...options
|
||||
};
|
||||
|
||||
const within = doc.querySelector(options.within);
|
||||
if (!within) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
within.querySelectorAll('[class*=":preview"]').forEach(code => {
|
||||
const pre = code.closest('pre');
|
||||
if (!pre) {
|
||||
return;
|
||||
}
|
||||
const adjacentPre = pre.nextElementSibling?.tagName.toLowerCase() === 'pre' ? pre.nextElementSibling : null;
|
||||
const reactCode = adjacentPre?.querySelector('code[class$="react"]');
|
||||
const sourceGroupId = `code-preview-source-group-${count}`;
|
||||
const isExpanded = code.getAttribute('class').includes(':expanded');
|
||||
const noCodePen = code.getAttribute('class').includes(':no-codepen');
|
||||
|
||||
count++;
|
||||
|
||||
const htmlButton = `
|
||||
<button type="button"
|
||||
title="Show HTML code"
|
||||
class="code-preview__button code-preview__button--html"
|
||||
>
|
||||
HTML
|
||||
</button>
|
||||
`;
|
||||
|
||||
const reactButton = `
|
||||
<button type="button" title="Show React code" class="code-preview__button code-preview__button--react">
|
||||
React
|
||||
</button>
|
||||
`;
|
||||
|
||||
const codePenButton = `
|
||||
<button type="button" class="code-preview__button code-preview__button--codepen" title="Edit on CodePen">
|
||||
<svg
|
||||
width="138"
|
||||
height="26"
|
||||
viewBox="0 0 138 26"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.3"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M80 6h-9v14h9 M114 6h-9 v14h9 M111 13h-6 M77 13h-6 M122 20V6l11 14V6 M22 16.7L33 24l11-7.3V9.3L33 2L22 9.3V16.7z M44 16.7L33 9.3l-11 7.4 M22 9.3l11 7.3 l11-7.3 M33 2v7.3 M33 16.7V24 M88 14h6c2.2 0 4-1.8 4-4s-1.8-4-4-4h-6v14 M15 8c-1.3-1.3-3-2-5-2c-4 0-7 3-7 7s3 7 7 7 c2 0 3.7-0.8 5-2 M64 13c0 4-3 7-7 7h-5V6h5C61 6 64 9 64 13z" />
|
||||
</svg>
|
||||
</button>
|
||||
`;
|
||||
|
||||
const codePreview = `
|
||||
<div class="code-preview ${isExpanded ? 'code-preview--expanded' : ''}">
|
||||
<div class="code-preview__preview">
|
||||
${code.textContent}
|
||||
<div class="code-preview__resizer">
|
||||
<sl-icon name="grip-vertical"></sl-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="code-preview__source-group" id="${sourceGroupId}">
|
||||
<div class="code-preview__source code-preview__source--html" ${reactCode ? 'data-flavor="html"' : ''}>
|
||||
<pre><code class="language-html">${escapeHtml(code.textContent)}</code></pre>
|
||||
</div>
|
||||
|
||||
${
|
||||
reactCode
|
||||
? `
|
||||
<div class="code-preview__source code-preview__source--react" data-flavor="react">
|
||||
<pre><code class="language-jsx">${escapeHtml(reactCode.textContent)}</code></pre>
|
||||
</div>
|
||||
`
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="code-preview__buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="code-preview__button code-preview__toggle"
|
||||
aria-expanded="${isExpanded ? 'true' : 'false'}"
|
||||
aria-controls="${sourceGroupId}"
|
||||
>
|
||||
Source
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
${reactCode ? ` ${htmlButton} ${reactButton} ` : ''}
|
||||
|
||||
${noCodePen ? '' : codePenButton}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
pre.insertAdjacentHTML('afterend', codePreview);
|
||||
pre.remove();
|
||||
|
||||
if (adjacentPre) {
|
||||
adjacentPre.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Wrap code preview scripts in anonymous functions so they don't run in the global scope
|
||||
doc.querySelectorAll('.code-preview__preview script').forEach(script => {
|
||||
if (script.type === 'module') {
|
||||
// Modules are already scoped
|
||||
script.textContent = script.innerHTML;
|
||||
} else {
|
||||
// Wrap non-modules in an anonymous function so they don't run in the global scope
|
||||
script.textContent = `(() => { ${script.innerHTML} })();`;
|
||||
}
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
23
docs/_utilities/copy-code-buttons.cjs
Normal file
@@ -0,0 +1,23 @@
|
||||
let codeBlockId = 0;
|
||||
|
||||
/**
|
||||
* Adds copy code buttons to code fields. The provided doc should be a document object provided by JSDOM. The same
|
||||
* document will be returned with the appropriate DOM manipulations.
|
||||
*/
|
||||
module.exports = function (doc) {
|
||||
doc.querySelectorAll('pre > code').forEach(code => {
|
||||
const pre = code.closest('pre');
|
||||
const button = doc.createElement('sl-copy-button');
|
||||
|
||||
if (!code.id) {
|
||||
code.id = `code-block-${++codeBlockId}`;
|
||||
}
|
||||
|
||||
button.classList.add('copy-code-button');
|
||||
button.setAttribute('from', code.id);
|
||||
|
||||
pre.append(button);
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
41
docs/_utilities/external-links.cjs
Normal file
@@ -0,0 +1,41 @@
|
||||
const { isExternalLink } = require('./strings.cjs');
|
||||
|
||||
/**
|
||||
* Transforms external links to make them safer and optionally add a target. The provided doc should be a document
|
||||
* object provided by JSDOM. The same document will be returned with the appropriate DOM manipulations.
|
||||
*/
|
||||
module.exports = function (doc, options) {
|
||||
options = {
|
||||
className: 'external-link', // the class name to add to links
|
||||
noopener: true, // sets rel="noopener"
|
||||
noreferrer: true, // sets rel="noreferrer"
|
||||
ignore: () => false, // callback function to filter links that should be ignored
|
||||
within: 'body', // element that contains the target links
|
||||
target: '', // sets the target attribute
|
||||
...options
|
||||
};
|
||||
|
||||
const within = doc.querySelector(options.within);
|
||||
|
||||
if (within) {
|
||||
within.querySelectorAll('a').forEach(link => {
|
||||
if (isExternalLink(link) && !options.ignore(link)) {
|
||||
link.classList.add(options.className);
|
||||
|
||||
const rel = [];
|
||||
if (options.noopener) rel.push('noopener');
|
||||
if (options.noreferrer) rel.push('noreferrer');
|
||||
|
||||
if (rel.length) {
|
||||
link.setAttribute('rel', rel.join(' '));
|
||||
}
|
||||
|
||||
if (options.target) {
|
||||
link.setAttribute('target', options.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return doc;
|
||||
};
|
||||
63
docs/_utilities/highlight-code.cjs
Normal file
@@ -0,0 +1,63 @@
|
||||
const Prism = require('prismjs');
|
||||
const PrismLoader = require('prismjs/components/index.js');
|
||||
|
||||
PrismLoader('diff');
|
||||
PrismLoader.silent = true;
|
||||
|
||||
/** Highlights a code string. */
|
||||
function highlight(code, language) {
|
||||
const alias = language.replace(/^diff-/, '');
|
||||
const isDiff = /^diff-/i.test(language);
|
||||
|
||||
// Auto-load the target language
|
||||
if (!Prism.languages[alias]) {
|
||||
PrismLoader(alias);
|
||||
|
||||
if (!Prism.languages[alias]) {
|
||||
throw new Error(`Unsupported language for code highlighting: "${language}"`);
|
||||
}
|
||||
}
|
||||
|
||||
// Register diff-* languages to use the diff grammar
|
||||
if (isDiff) {
|
||||
Prism.languages[language] = Prism.languages.diff;
|
||||
}
|
||||
|
||||
return Prism.highlight(code, Prism.languages[language], language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights all code fields that have a language parameter. If the language has a colon in its name, the first chunk
|
||||
* will be the language used and additional chunks will be applied as classes to the `<pre>`. For example, a code field
|
||||
* tagged with "html:preview" will be rendered as `<pre class="language-html preview">`.
|
||||
*
|
||||
* The provided doc should be a document object provided by JSDOM. The same document will be returned with the
|
||||
* appropriate DOM manipulations.
|
||||
*/
|
||||
module.exports = function (doc) {
|
||||
doc.querySelectorAll('pre > code[class]').forEach(code => {
|
||||
// Look for class="language-*" and split colons into separate classes
|
||||
code.classList.forEach(className => {
|
||||
if (className.startsWith('language-')) {
|
||||
//
|
||||
// We use certain suffixes to indicate code previews, expanded states, etc. The class might look something like
|
||||
// this:
|
||||
//
|
||||
// class="language-html:preview:expanded"
|
||||
//
|
||||
// The language will always come first, so we need to drop the "language-" prefix and everything after the first
|
||||
// color to get the highlighter language.
|
||||
//
|
||||
const language = className.replace(/^language-/, '').split(':')[0];
|
||||
|
||||
try {
|
||||
code.innerHTML = highlight(code.textContent ?? '', language);
|
||||
} catch (err) {
|
||||
// Language not found, skip it
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
67
docs/_utilities/markdown.cjs
Normal file
@@ -0,0 +1,67 @@
|
||||
const MarkdownIt = require('markdown-it');
|
||||
const markdownItContainer = require('markdown-it-container');
|
||||
const markdownItIns = require('markdown-it-ins');
|
||||
const markdownItKbd = require('markdown-it-kbd');
|
||||
const markdownItMark = require('markdown-it-mark');
|
||||
const markdownItReplaceIt = require('markdown-it-replace-it');
|
||||
|
||||
const markdown = MarkdownIt({
|
||||
html: true,
|
||||
xhtmlOut: false,
|
||||
breaks: false,
|
||||
langPrefix: 'language-',
|
||||
linkify: false,
|
||||
typographer: false
|
||||
});
|
||||
|
||||
// Third-party plugins
|
||||
markdown.use(markdownItContainer);
|
||||
markdown.use(markdownItIns);
|
||||
markdown.use(markdownItKbd);
|
||||
markdown.use(markdownItMark);
|
||||
markdown.use(markdownItReplaceIt);
|
||||
|
||||
// Callouts
|
||||
['tip', 'warning', 'danger'].forEach(type => {
|
||||
markdown.use(markdownItContainer, type, {
|
||||
render: function (tokens, idx) {
|
||||
if (tokens[idx].nesting === 1) {
|
||||
return `<div role="alert" class="callout callout--${type}">`;
|
||||
}
|
||||
return '</div>\n';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Asides
|
||||
markdown.use(markdownItContainer, 'aside', {
|
||||
render: function (tokens, idx) {
|
||||
if (tokens[idx].nesting === 1) {
|
||||
return `<aside>`;
|
||||
}
|
||||
return '</aside>\n';
|
||||
}
|
||||
});
|
||||
|
||||
// Details
|
||||
markdown.use(markdownItContainer, 'details', {
|
||||
validate: params => params.trim().match(/^details\s+(.*)$/),
|
||||
render: (tokens, idx) => {
|
||||
const m = tokens[idx].info.trim().match(/^details\s+(.*)$/);
|
||||
if (tokens[idx].nesting === 1) {
|
||||
return `<details>\n<summary><span>${markdown.utils.escapeHtml(m[1])}</span></summary>\n`;
|
||||
}
|
||||
return '</details>\n';
|
||||
}
|
||||
});
|
||||
|
||||
// Replace [#1234] with a link to GitHub issues
|
||||
markdownItReplaceIt.replacements.push({
|
||||
name: 'github-issues',
|
||||
re: /\[#([0-9]+)\]/gs,
|
||||
sub: '<a href="https://github.com/shoelace-style/shoelace/issues/$1">#$1</a>',
|
||||
html: true,
|
||||
default: true
|
||||
});
|
||||
|
||||
module.exports = markdown;
|
||||
26
docs/_utilities/prettier.cjs
Normal file
@@ -0,0 +1,26 @@
|
||||
const { format } = require('prettier');
|
||||
|
||||
/** Formats markup using prettier. */
|
||||
module.exports = function (content, options) {
|
||||
options = {
|
||||
arrowParens: 'avoid',
|
||||
bracketSpacing: true,
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
insertPragma: false,
|
||||
bracketSameLine: false,
|
||||
jsxSingleQuote: false,
|
||||
parser: 'html',
|
||||
printWidth: 120,
|
||||
proseWrap: 'preserve',
|
||||
quoteProps: 'as-needed',
|
||||
requirePragma: false,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
tabWidth: 2,
|
||||
trailingComma: 'none',
|
||||
useTabs: false,
|
||||
...options
|
||||
};
|
||||
|
||||
return format(content, options);
|
||||
};
|
||||
19
docs/_utilities/replacer.cjs
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @typedef {object} Replacement
|
||||
* @property {string | RegExp} pattern
|
||||
* @property {string} replacement
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Array<Replacement>} Replacements
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Document} content
|
||||
* @param {Replacements} replacements
|
||||
*/
|
||||
module.exports = function (content, replacements) {
|
||||
replacements.forEach(replacement => {
|
||||
content.body.innerHTML = content.body.innerHTML.replaceAll(replacement.pattern, replacement.replacement);
|
||||
});
|
||||
};
|
||||
21
docs/_utilities/scrolling-tables.cjs
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Turns headings into clickable, deep linkable anchors. The provided doc should be a document object provided by JSDOM.
|
||||
* The same document will be returned with the appropriate DOM manipulations.
|
||||
*/
|
||||
module.exports = function (doc, options) {
|
||||
const tables = [...doc.querySelectorAll('table')];
|
||||
|
||||
options = {
|
||||
className: 'table-scroll', // the class name to add to the table's container
|
||||
...options
|
||||
};
|
||||
|
||||
tables.forEach(table => {
|
||||
const div = doc.createElement('div');
|
||||
div.classList.add(options.className);
|
||||
table.insertAdjacentElement('beforebegin', div);
|
||||
div.append(table);
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
16
docs/_utilities/strings.cjs
Normal file
@@ -0,0 +1,16 @@
|
||||
const slugify = require('slugify');
|
||||
|
||||
/** Creates a slug from an arbitrary string of text. */
|
||||
module.exports.createSlug = function (text) {
|
||||
return slugify(String(text), {
|
||||
remove: /[^\w|\s]/g,
|
||||
lower: true
|
||||
});
|
||||
};
|
||||
|
||||
/** Determines whether or not a link is external. */
|
||||
module.exports.isExternalLink = function (link) {
|
||||
// We use the "internal" hostname when initializing JSDOM so we know that those are local links
|
||||
if (!link.hostname || link.hostname === 'internal') return false;
|
||||
return true;
|
||||
};
|
||||
42
docs/_utilities/table-of-contents.cjs
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Generates an in-page table of contents based on headings.
|
||||
*/
|
||||
module.exports = function (doc, options) {
|
||||
options = {
|
||||
levels: ['h2'], // headings to include (they must have an id)
|
||||
container: 'nav', // the container to append links to
|
||||
listItem: true, // if true, links will be wrapped in <li>
|
||||
within: 'body', // the element containing the headings to summarize
|
||||
...options
|
||||
};
|
||||
|
||||
const container = doc.querySelector(options.container);
|
||||
const within = doc.querySelector(options.within);
|
||||
const headingSelector = options.levels.map(h => `${h}[id]`).join(', ');
|
||||
|
||||
if (!container || !within) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
within.querySelectorAll(headingSelector).forEach(heading => {
|
||||
const listItem = doc.createElement('li');
|
||||
const link = doc.createElement('a');
|
||||
const level = heading.tagName.slice(1);
|
||||
|
||||
link.href = `#${heading.id}`;
|
||||
link.textContent = heading.textContent;
|
||||
|
||||
if (options.listItem) {
|
||||
// List item + link
|
||||
listItem.setAttribute('data-level', level);
|
||||
listItem.append(link);
|
||||
container.append(listItem);
|
||||
} else {
|
||||
// Link only
|
||||
link.setAttribute('data-level', level);
|
||||
container.append(link);
|
||||
}
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
23
docs/_utilities/typography.cjs
Normal file
@@ -0,0 +1,23 @@
|
||||
const smartquotes = require('smartquotes');
|
||||
|
||||
smartquotes.replacements.push([/---/g, '\u2014']); // em dash
|
||||
smartquotes.replacements.push([/--/g, '\u2013']); // en dash
|
||||
smartquotes.replacements.push([/\.\.\./g, '\u2026']); // ellipsis
|
||||
smartquotes.replacements.push([/\(c\)/gi, '\u00A9']); // copyright
|
||||
smartquotes.replacements.push([/\(r\)/gi, '\u00AE']); // registered trademark
|
||||
smartquotes.replacements.push([/\?!/g, '\u2048']); // ?!
|
||||
smartquotes.replacements.push([/!!/g, '\u203C']); // !!
|
||||
smartquotes.replacements.push([/\?\?/g, '\u2047']); // ??
|
||||
smartquotes.replacements.push([/([0-9]\s?)-(\s?[0-9])/g, '$1\u2013$2']); // number ranges use en dash
|
||||
|
||||
/**
|
||||
* Improves typography by adding smart quotes and similar corrections within the specified element(s).
|
||||
*
|
||||
* The provided doc should be a document object provided by JSDOM. The same document will be returned with the
|
||||
* appropriate DOM manipulations.
|
||||
*/
|
||||
module.exports = function (doc, selector = 'body') {
|
||||
const elements = [...doc.querySelectorAll(selector)];
|
||||
elements.forEach(el => smartquotes.element(el));
|
||||
return doc;
|
||||
};
|
||||
BIN
docs/assets/examples/carousel/field.jpg
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
docs/assets/examples/carousel/mountains.jpg
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
docs/assets/examples/carousel/sunset.jpg
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
docs/assets/examples/carousel/valley.jpg
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
docs/assets/examples/carousel/waterfall.jpg
Normal file
|
After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 861 KiB |
11
docs/assets/images/awesome.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="733" xmlns="http://www.w3.org/2000/svg" height="733">
|
||||
<circle cy="366.5" cx="366.5" r="366.5"/>
|
||||
<circle cy="366.5" cx="366.5" r="336.5" fill="#fede58"/>
|
||||
<path d="M325 665c-121-21-194-115-212-233v-8l-25-1-1-18h481c6 13 10 27 13 41 13 94-38 146-114 193-45 23-93 29-142 26z"/>
|
||||
<path d="M372 647c52-6 98-28 138-62 28-25 46-56 51-87 4-20 1-57-5-70l-423-1c-2 56 39 118 74 157 31 34 72 54 116 63 11 2 38 2 49 0z" fill="#871945"/>
|
||||
<path d="M76 342c-13-26-13-57-9-85 6-27 18-52 35-68 21-20 50-23 77-18 15 4 28 12 39 23 18 17 30 40 36 67 4 20 4 41 0 60l-6 21z"/>
|
||||
<path d="M234 323c5-6 6-40 2-58-3-16-4-16-10-10-14 14-38 14-52 0-15-18-12-41 6-55 3-3 5-5 5-6-1-4-22-8-34-7-42 4-57.6 40-66.2 77-3 17-1 53 4 59H234z" fill="#fff"/>
|
||||
<path d="M378 343c-2-3-6-20-7-29-5-28-1-57 11-83 15-30 41-52 72-60 29-7 57 0 82 15 26 17 45 49 50 82 2 12 2 33 0 45-1 10-5 26-8 30z"/>
|
||||
<path d="M565 324c4-5 5-34 4-50-2-14-6-24-8-24-1 0-3 2-6 5-17 17-47 13-58-9-7-16-4-31 8-43 4-4 7-8 7-9 0 0-4-2-8-3-51-17-105 20-115 80-3 15 0 43 3 53z" fill="#fff"/>
|
||||
<path d="M504 590s-46 40-105 53c-66 15-114-7-114-7s14-76 93-95c76-18 126 49 126 49z" fill="#f9bedd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
@@ -41,7 +41,7 @@
|
||||
<ellipse id="Oval" fill="#2F2E41" fill-rule="nonzero" cx="103.34581" cy="426.732" rx="10.90314" ry="4.08868"></ellipse>
|
||||
<circle id="Oval" fill="#FFFFFF" fill-rule="nonzero" cx="86.99113" cy="358.86008" r="14.71922"></circle>
|
||||
<circle id="Oval" fill="#3F3D56" fill-rule="nonzero" cx="86.99113" cy="358.86008" r="4.90642"></circle>
|
||||
<path d="M44.12401,329.71183 C40.64653,314.13804 51.76268,298.4014 68.95262,294.56302 C86.14256,290.72464 102.89683,300.23813 106.37431,315.81192 C109.85179,331.38571 98.45939,337.12961 81.26945,340.96792 C64.07951,344.80623 47.60154,345.28568 44.12401,329.71183 Z" id="Path" fill="#E6E6E6" fill-rule="nonzero"></path>
|
||||
<path d="M44.12401,329.71183 C40.64653,314.13804 51.76268,298.4014 68.95262,294.56302 C86.14256,290.72464 102.89683,300.23813 106.37431,315.81192 C109.85179,331.38571 98.45939,337.12961 81.26945,340.96792 C64.07951,344.80623 47.60154,345.28568 44.12401,329.71183 Z" id="Path" fill="#12a5e9" fill-rule="nonzero"></path>
|
||||
<ellipse id="Oval" fill="#2F2E41" fill-rule="nonzero" transform="translate(110.988725, 213.490755) rotate(-69.082170) translate(-110.988725, -213.490755) " cx="110.988725" cy="213.490755" rx="21.53369" ry="6.76007"></ellipse>
|
||||
<circle id="Oval" fill="#2F2E41" fill-rule="nonzero" transform="translate(71.181568, 246.012788) rotate(-80.782520) translate(-71.181568, -246.012788) " cx="71.1815681" cy="246.012788" r="43.06735"></circle>
|
||||
<rect id="Rectangle" fill="#2F2E41" fill-rule="nonzero" x="51.55595" y="279.81244" width="13.08374" height="23.44171"></rect>
|
||||
@@ -67,13 +67,13 @@
|
||||
<circle id="Oval" fill="#3F3D56" fill-rule="nonzero" cx="725.31949" cy="403.22896" r="4.90642"></circle>
|
||||
<ellipse id="Oval" fill="#2F2E41" fill-rule="nonzero" transform="translate(771.918005, 384.148727) rotate(-53.549900) translate(-771.918005, -384.148727) " cx="771.918005" cy="384.148727" rx="21.53368" ry="6.76007"></ellipse>
|
||||
<path d="M705.39698,436.33866 C705.39698,432.86466 714.34198,426.00466 724.26778,426.00466 C734.19358,426.00466 743.50006,435.78516 743.50006,439.25914 C743.50006,442.73312 734.19351,438.58514 724.26778,438.58514 C714.34205,438.58514 705.39698,439.81268 705.39698,436.33866 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero" transform="translate(724.448520, 433.325266) rotate(-180.000000) translate(-724.448520, -433.325266) "></path>
|
||||
<path d="M847.1767,378.54172 L611.43117,345.86064 C607.604361,345.32541 604.932725,341.793834 605.45868,337.96574 L631.96057,146.79396 C632.495641,142.967058 636.027329,140.295337 639.85547,140.82147 L875.60098,173.50256 C879.427885,174.037626 882.099609,177.569318 881.57347,181.39746 L855.0716,372.56924 C854.536375,376.396051 851.004794,379.067687 847.1767,378.54172 L847.1767,378.54172 Z" id="Path" fill="#11a5e9" fill-rule="nonzero"></path>
|
||||
<path d="M847.1767,378.54172 L611.43117,345.86064 C607.604361,345.32541 604.932725,341.793834 605.45868,337.96574 L631.96057,146.79396 C632.495641,142.967058 636.027329,140.295337 639.85547,140.82147 L875.60098,173.50256 C879.427885,174.037626 882.099609,177.569318 881.57347,181.39746 L855.0716,372.56924 C854.536375,376.396051 851.004794,379.067687 847.1767,378.54172 L847.1767,378.54172 Z" id="Path" fill="#9358ff" fill-rule="nonzero"></path>
|
||||
<path d="M762.72231,318.87957 L642.36784,302.19498 C642.216871,302.176045 642.067969,302.14324 641.92302,302.09698 L712.51355,211.39072 C713.394132,210.238632 714.82651,209.649483 716.262854,209.8486 C717.699198,210.047717 718.917295,211.004295 719.45127,212.35248 L748.48059,283.8148 L749.87186,287.23459 L762.72231,318.87957 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
<polygon id="Path" fill="#000000" fill-rule="nonzero" opacity="0.2" points="762.722 318.879 721.63 313.183 745.864 286.679 747.609 284.77 748.481 283.815 749.872 287.235"></polygon>
|
||||
<path d="M829.73481,328.16942 L725.63807,313.73863 L749.87186,287.23463 L751.61612,285.32515 L783.19533,250.78503 C784.29885,249.735613 785.796059,249.204111 787.314261,249.322828 C788.832463,249.441546 790.228856,250.199316 791.15584,251.40751 C791.271244,251.575507 791.375823,251.750689 791.46894,251.93199 L829.73481,328.16942 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
<circle id="Oval" fill="#FFFFFF" fill-rule="nonzero" cx="764.18602" cy="224.18353" r="18"></circle>
|
||||
<rect id="Rectangle" fill="#3F3D56" fill-rule="nonzero" transform="translate(653.262165, 167.923576) rotate(7.892770) translate(-653.262165, -167.923576) " x="642.262129" y="156.923541" width="22.0000711" height="22.0000711"></rect>
|
||||
<path d="M768.18655,374.08068 C771.66403,358.50689 760.54788,342.77022 743.35794,338.93184 C726.168,335.09346 709.41373,344.60692 705.93625,360.18071 C702.45877,375.7545 713.85117,381.49837 731.04111,385.33671 C748.23105,389.17505 764.70908,389.6545 768.18655,374.08068 Z" id="Path" fill="#F2F2F2" fill-rule="nonzero"></path>
|
||||
<rect id="Rectangle" fill="#FFFFFF" fill-rule="nonzero" transform="translate(653.262165, 167.923576) rotate(7.892770) translate(-653.262165, -167.923576) " x="642.262129" y="156.923541" width="22.0000711" height="22.0000711"></rect>
|
||||
<path d="M768.18655,374.08068 C771.66403,358.50689 760.54788,342.77022 743.35794,338.93184 C726.168,335.09346 709.41373,344.60692 705.93625,360.18071 C702.45877,375.7545 713.85117,381.49837 731.04111,385.33671 C748.23105,389.17505 764.70908,389.6545 768.18655,374.08068 Z" id="Path" fill="#12a5e9" fill-rule="nonzero"></path>
|
||||
<ellipse id="Oval" fill="#2F2E41" fill-rule="nonzero" transform="translate(735.010556, 473.249892) rotate(-4.181640) translate(-735.010556, -473.249892) " cx="735.010556" cy="473.249892" rx="10.90314" ry="4.08868"></ellipse>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
32
docs/assets/images/undraw-taken.svg
Normal file
@@ -0,0 +1,32 @@
|
||||
<svg width="673" height="739" viewBox="0 0 673 739" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill-rule="nonzero" fill="none">
|
||||
<path d="M467 149.805c-46.62-7.44-99.71-11.41-155-11.41-50.6 0-99.35 3.32-142.98 9.58.01-.67.02-1.34.05-2.01C171.475 65.082 237.996.903 318.914 1.398c80.917.494 146.649 65.48 148.066 146.387.01.68.02 1.35.02 2.02Z" fill="#0BA5E9"/>
|
||||
<path d="M337.55 1.342a149.047 149.047 0 0 0-168.93 143.229c-.031.67-.04 1.34-.05 2.01 12.961-1.86 26.384-3.454 40.164-4.784 3.478-71.745 57.64-130.8 128.816-140.455Z" opacity=".1" fill="#FFF"/>
|
||||
<path d="M532.18 161.625a600.121 600.121 0 0 0-65.2-13.84 943.364 943.364 0 0 0-108.74-10.45 1133.608 1133.608 0 0 0-83.01-.34 973.29 973.29 0 0 0-106.16 8.97 624.292 624.292 0 0 0-77.25 15.66C32.61 177.995 0 199.935 0 223.395s32.61 45.4 91.82 61.77c41.64 11.52 92.98 19.37 148.92 22.97 23.09 1.5 46.96 2.26 71.26 2.26 24.38 0 48.33-.77 71.49-2.27 50.91-3.29 98.01-10.1 137.43-20 .21-.06.41-.11.62-.16 2.66-.66 5.28-1.35 7.87-2.04.93-.26 1.85-.51 2.77-.76a.978.978 0 0 1 .16-.05c.88-.24 1.75-.49 2.62-.73 1.74-.5 3.46-.99 5.15-1.5.08-.02.15-.04.22-.06 1.47-.44 2.91-.88 4.34-1.32 1.17-.37 2.33-.73 3.48-1.1.84-.27 1.67-.54 2.49-.81.6-.2 1.19-.39 1.77-.59.79-.26 1.58-.53 2.36-.8.33-.11.66-.22.98-.34.75-.25 1.48-.51 2.21-.77.79-.28 1.58-.57 2.36-.85.65-.23 1.3-.47 1.94-.71.54-.21 1.07-.41 1.61-.61 1.47-.55 2.91-1.12 4.33-1.68.71-.29 1.42-.57 2.12-.86.69-.28 1.39-.57 2.07-.86 1.12-.47 2.22-.94 3.3-1.41.52-.24 1.05-.47 1.56-.69.39-.18.77-.35 1.16-.53.28-.12.56-.25.83-.38 1.01-.46 2.01-.93 2.99-1.4 3.76-1.8 7.27-3.64 10.53-5.52 20.45-11.71 31.24-24.7 31.24-38.2 0-23.46-32.61-45.4-91.82-61.77Zm-.54 121.62c-41.69 11.53-93.17 19.38-149.26 22.95-22.81 1.45-46.39 2.2-70.38 2.2-23.91 0-47.41-.74-70.15-2.19-56.18-3.56-107.74-11.41-149.49-22.96C34.09 267.125 2 245.875 2 223.395c0-1.986.252-3.965.74-5.89 5.1-20.28 36.47-39.26 89.62-53.96a623.806 623.806 0 0 1 76.66-15.57 976.027 976.027 0 0 1 106.8-9c11.92-.39 23.98-.583 36.18-.58 15.41 0 30.65.31 45.63.91a941.367 941.367 0 0 1 109.37 10.5 598.858 598.858 0 0 1 64.64 13.74c53.14 14.7 84.5 33.67 89.61 53.94.496 1.93.75 3.916.75 5.91 0 22.48-32.09 43.73-90.36 59.85Z" fill="#3F3D56"/>
|
||||
<path d="M623.43 224.305c0 13.36-11.01 26-30.67 37.29-3.27 1.88-6.79 3.72-10.53 5.52-.98.47-1.98.94-2.99 1.4-.27.13-.55.26-.83.38-.39.18-.77.35-1.16.53-.51.22-1.04.45-1.56.69-1.08.47-2.18.94-3.3 1.41-.68.29-1.38.58-2.07.86-.7.29-1.41.57-2.12.86-1.42.56-2.86 1.13-4.33 1.68-.54.2-1.07.4-1.61.61-.64.24-1.29.48-1.94.71-.78.28-1.57.57-2.36.85-.73.26-1.46.52-2.21.77-.32.12-.65.23-.98.34-.78.27-1.57.54-2.36.8-.58.2-1.17.39-1.77.59-.82.27-1.65.54-2.49.81-1.15.37-2.31.73-3.48 1.1-1.43.44-2.87.88-4.34 1.32-.07.02-.14.04-.22.06-1.69.51-3.41 1-5.15 1.5-.87.24-1.74.49-2.62.73a.978.978 0 0 0-.16.05c-.92.25-1.84.5-2.77.76-2.58.68-5.21 1.37-7.87 2.04-.21.05-.41.1-.62.16-38.35 9.58-85.4 16.56-137.47 19.93-22.81 1.47-46.59 2.25-71.02 2.25-24.65 0-48.63-.79-71.62-2.29-137.24-8.95-239.38-43.03-239.38-83.71.01-2.475.388-4.936 1.12-7.3.06.17.12.33.19.5 14.27 37.48 115.54 67.77 246.94 75.16 20.13 1.14 40.98 1.73 62.32 1.73 21.43 0 42.36-.6 62.57-1.74 131.29-7.42 232.46-37.72 246.68-75.17.24-.6.45-1.2.63-1.8a25.304 25.304 0 0 1 1.55 8.62ZM91.67 213.54c-16.643 0-34.331-3.58-34.331-10.216 0-6.636 17.688-10.217 34.33-10.217 16.643 0 34.331 3.58 34.331 10.217 0 6.636-17.688 10.217-34.33 10.217Zm0-18.433c-19.054 0-32.331 4.33-32.331 8.217 0 3.886 13.277 8.217 32.33 8.217 19.053 0 32.331-4.33 32.331-8.217 0-3.886-13.278-8.217-32.33-8.217Z" fill="#3F3D56"/>
|
||||
<path d="M162.67 260.54c-16.643 0-34.331-3.58-34.331-10.216 0-6.636 17.688-10.217 34.33-10.217 16.643 0 34.331 3.58 34.331 10.217 0 6.636-17.688 10.217-34.33 10.217Zm0-18.433c-19.054 0-32.331 4.33-32.331 8.217 0 3.886 13.277 8.217 32.33 8.217 19.053 0 32.331-4.33 32.331-8.217 0-3.886-13.278-8.217-32.33-8.217ZM531.67 213.54c-16.643 0-34.331-3.58-34.331-10.216 0-6.636 17.688-10.217 34.33-10.217 16.643 0 34.331 3.58 34.331 10.217 0 6.636-17.688 10.217-34.33 10.217Zm0-18.433c-19.054 0-32.331 4.33-32.331 8.217 0 3.886 13.277 8.217 32.33 8.217 19.053 0 32.331-4.33 32.331-8.217 0-3.886-13.278-8.217-32.33-8.217ZM460.67 260.54c-16.643 0-34.331-3.58-34.331-10.216 0-6.636 17.688-10.217 34.33-10.217 16.643 0 34.331 3.58 34.331 10.217 0 6.636-17.688 10.217-34.33 10.217Zm0-18.433c-19.054 0-32.331 4.33-32.331 8.217 0 3.886 13.277 8.217 32.33 8.217 19.053 0 32.331-4.33 32.331-8.217 0-3.886-13.278-8.217-32.33-8.217ZM311.67 282.54c-16.643 0-34.331-3.58-34.331-10.216 0-6.636 17.688-10.217 34.33-10.217 16.643 0 34.331 3.58 34.331 10.217 0 6.636-17.688 10.217-34.33 10.217Zm0-18.433c-19.054 0-32.331 4.33-32.331 8.217 0 3.886 13.277 8.217 32.33 8.217 19.053 0 32.331-4.33 32.331-8.217 0-3.886-13.278-8.217-32.33-8.217Z" fill="#3F3D56"/>
|
||||
<circle fill="#2F2E41" cx="336.978" cy="450.705" r="42.012"/>
|
||||
<path fill="#2F2E41" d="m300.555 488.547 20.447-10.24 5.715 11.413-20.447 10.24z"/>
|
||||
<ellipse fill="#2F2E41" transform="rotate(-56.601 300.086 492.946)" cx="300.086" cy="492.946" rx="3.989" ry="10.636"/>
|
||||
<path fill="#2F2E41" d="m347.239 489.72 5.715-11.412 20.447 10.24-5.715 11.412z"/>
|
||||
<ellipse fill="#2F2E41" transform="rotate(-33.399 373.87 492.946)" cx="373.87" cy="492.946" rx="10.636" ry="3.989"/>
|
||||
<circle fill="#FFF" cx="334.037" cy="440.429" r="14.359"/>
|
||||
<ellipse fill="#3F3D56" transform="rotate(-45 334.135 434.282)" cx="334.135" cy="434.282" rx="4.766" ry="4.8"/>
|
||||
<path d="M370.12 405c.632-15.553-12.773-28.727-29.941-29.425-17.168-.697-31.597 11.346-32.229 26.9-.632 15.553 11.302 19.087 28.47 19.785 17.167.697 33.068-1.706 33.7-17.26Z" fill="#0BA5E9"/>
|
||||
<ellipse fill="#2F2E41" transform="rotate(-40.645 380.654 456.766)" cx="380.654" cy="456.766" rx="6.594" ry="21.006"/>
|
||||
<ellipse fill="#2F2E41" transform="rotate(-49.355 293.42 456.766)" cx="293.419" cy="456.766" rx="21.006" ry="6.594"/>
|
||||
<path d="M348.517 467.262a9.572 9.572 0 1 1-18.836 3.428l-.003-.018c-.942-5.202 3.08-7.043 8.282-7.985 5.203-.942 9.615-.628 10.557 4.575Z" fill="#FFF"/>
|
||||
<path d="M266 495.395a2 2 0 0 1-2-2v-118a2 2 0 0 1 4 0v118a2 2 0 0 1-2 2ZM236 601.395a2 2 0 0 1-2-2v-86a2 2 0 0 1 4 0v86a2 2 0 0 1-2 2ZM313 530.395a2 2 0 0 1-2-2v-118a2 2 0 0 1 4 0v118a2 2 0 0 1-2 2ZM284 615.395a2 2 0 0 1-2-2v-48a2 2 0 0 1 4 0v48a2 2 0 0 1-2 2ZM325 369.395a2 2 0 0 1-2-2v-48a2 2 0 0 1 4 0v48a2 2 0 0 1-2 2ZM225 390.395a2 2 0 0 1-2-2v-48a2 2 0 0 1 4 0v48a2 2 0 0 1-2 2ZM399 395.395a2 2 0 0 1-2-2v-48a2 2 0 0 1 4 0v48a2 2 0 0 1-2 2ZM395 545.395a2 2 0 0 1-2-2v-58a2 2 0 0 1 4 0v58a2 2 0 0 1-2 2ZM355 596.395a2 2 0 0 1-2-2v-86a2 2 0 0 1 4 0v86a2 2 0 0 1-2 2ZM363 449.395a2 2 0 0 1-2-2v-118a2 2 0 0 1 4 0v118a2 2 0 0 1-2 2Z" fill="#CCC"/>
|
||||
<ellipse fill="#2F2E41" transform="rotate(-39.938 594.37 683.981)" cx="594.369" cy="683.981" rx="6.76" ry="21.534"/>
|
||||
<circle fill="#2F2E41" transform="rotate(-71.565 548.562 676.503)" cx="548.562" cy="676.503" r="43.067"/>
|
||||
<path fill="#2F2E41" d="M553.707 710.303h13.084v23.442h-13.084zM527.54 710.303h13.084v23.442H527.54z"/>
|
||||
<ellipse fill="#2F2E41" cx="555.888" cy="734.017" rx="10.903" ry="4.089"/>
|
||||
<ellipse fill="#2F2E41" cx="529.72" cy="733.472" rx="10.903" ry="4.089"/>
|
||||
<path d="M535.04 622.366c3.845-15.487 20.82-24.6 37.914-20.356 17.094 4.245 27.834 20.24 23.989 35.727-3.846 15.487-16.604 15.537-33.698 11.293-17.094-4.245-32.051-11.177-28.206-26.664Z" fill="#0BA5E9"/>
|
||||
<ellipse fill="#2F2E41" transform="rotate(-64.626 500.054 656.52)" cx="500.054" cy="656.52" rx="6.76" ry="21.534"/>
|
||||
<circle fill="#FFF" cx="542.124" cy="667.416" r="14.359"/>
|
||||
<circle fill="#3F3D56" cx="536.222" cy="662.269" r="4.786"/>
|
||||
<circle fill="#FFF" cx="542" cy="697.395" r="6"/>
|
||||
<path d="M671.531 738.395h-236a1 1 0 1 1 0-2h236a1 1 0 0 1 0 2Z" fill="#3F3D56"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
@@ -1,365 +0,0 @@
|
||||
/* global Prism */
|
||||
|
||||
(() => {
|
||||
const reactVersion = '17.0.2';
|
||||
let flavor = getFlavor();
|
||||
let count = 1;
|
||||
|
||||
// Sync flavor UI on page load
|
||||
setFlavor(getFlavor());
|
||||
|
||||
if (!window.$docsify) {
|
||||
throw new Error('Docsify must be loaded before installing this plugin.');
|
||||
}
|
||||
|
||||
function convertModuleLinks(html) {
|
||||
const version = sessionStorage.getItem('sl-version');
|
||||
|
||||
html = html
|
||||
.replace(/@shoelace-style\/shoelace/g, `https://cdn.skypack.dev/@shoelace-style/shoelace@${version}`)
|
||||
.replace(/from 'react'/g, `from 'https://cdn.skypack.dev/react@${reactVersion}'`)
|
||||
.replace(/from "react"/g, `from "https://cdn.skypack.dev/react@${reactVersion}"`);
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function getAdjacentExample(name, pre) {
|
||||
let currentPre = pre.nextElementSibling;
|
||||
|
||||
while (currentPre?.tagName.toLowerCase() === 'pre') {
|
||||
if (currentPre?.getAttribute('data-lang').split(' ').includes(name)) {
|
||||
return currentPre;
|
||||
}
|
||||
|
||||
currentPre = currentPre.nextElementSibling;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function runScript(script) {
|
||||
const newScript = document.createElement('script');
|
||||
|
||||
if (script.type === 'module') {
|
||||
newScript.type = 'module';
|
||||
newScript.textContent = script.innerHTML;
|
||||
} else {
|
||||
newScript.appendChild(document.createTextNode(`(() => { ${script.innerHTML} })();`));
|
||||
}
|
||||
|
||||
script.parentNode.replaceChild(newScript, script);
|
||||
}
|
||||
|
||||
function getFlavor() {
|
||||
return localStorage.getItem('flavor') || 'html';
|
||||
}
|
||||
|
||||
function setFlavor(newFlavor) {
|
||||
flavor = ['html', 'react'].includes(newFlavor) ? newFlavor : 'html';
|
||||
localStorage.setItem('flavor', flavor);
|
||||
|
||||
// Set the flavor class on the body
|
||||
document.body.classList.toggle('flavor-html', flavor === 'html');
|
||||
document.body.classList.toggle('flavor-react', flavor === 'react');
|
||||
}
|
||||
|
||||
window.$docsify.plugins.push(hook => {
|
||||
// Convert code blocks to previews
|
||||
hook.afterEach((html, next) => {
|
||||
const domParser = new DOMParser();
|
||||
const doc = domParser.parseFromString(html, 'text/html');
|
||||
|
||||
const htmlButton = `
|
||||
<button
|
||||
type="button"
|
||||
title="Show HTML code"
|
||||
class="code-block__button code-block__button--html ${flavor === 'html' ? 'code-block__button--selected' : ''}"
|
||||
>
|
||||
HTML
|
||||
</button>
|
||||
`;
|
||||
|
||||
const reactButton = `
|
||||
<button
|
||||
type="button"
|
||||
title="Show React code"
|
||||
class="code-block__button code-block__button--react ${
|
||||
flavor === 'react' ? 'code-block__button--selected' : ''
|
||||
}"
|
||||
>
|
||||
React
|
||||
</button>
|
||||
`;
|
||||
|
||||
const codePenButton = `
|
||||
<button type="button" class="code-block__button code-block__button--codepen" title="Edit on CodePen">
|
||||
<svg
|
||||
width="138"
|
||||
height="26"
|
||||
viewBox="0 0 138 26"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.3"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M80 6h-9v14h9 M114 6h-9 v14h9 M111 13h-6 M77 13h-6 M122 20V6l11 14V6 M22 16.7L33 24l11-7.3V9.3L33 2L22 9.3V16.7z M44 16.7L33 9.3l-11 7.4 M22 9.3l11 7.3 l11-7.3 M33 2v7.3 M33 16.7V24 M88 14h6c2.2 0 4-1.8 4-4s-1.8-4-4-4h-6v14 M15 8c-1.3-1.3-3-2-5-2c-4 0-7 3-7 7s3 7 7 7 c2 0 3.7-0.8 5-2 M64 13c0 4-3 7-7 7h-5V6h5C61 6 64 9 64 13z" />
|
||||
</svg>
|
||||
</button>
|
||||
`;
|
||||
|
||||
[...doc.querySelectorAll('code[class^="lang-"]')].forEach(code => {
|
||||
if (code.classList.contains('preview')) {
|
||||
const isExpanded = code.classList.contains('expanded');
|
||||
const pre = code.closest('pre');
|
||||
const sourceGroupId = `code-block-source-group-${count}`;
|
||||
const toggleId = `code-block-toggle-${count}`;
|
||||
const reactPre = getAdjacentExample('react', pre);
|
||||
const hasReact = reactPre !== null;
|
||||
|
||||
pre.setAttribute('data-lang', pre.getAttribute('data-lang').replace(/ preview$/, ''));
|
||||
pre.setAttribute('aria-labelledby', toggleId);
|
||||
|
||||
const codeBlock = `
|
||||
<div class="code-block ${isExpanded ? 'code-block--expanded' : ''}">
|
||||
<div class="code-block__preview">
|
||||
${code.textContent}
|
||||
<div class="code-block__resizer">
|
||||
<sl-icon name="grip-vertical"></sl-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="code-block__source-group" id="${sourceGroupId}">
|
||||
<div class="code-block__source code-block__source--html" ${hasReact ? 'data-flavor="html"' : ''}>
|
||||
${pre.outerHTML}
|
||||
</div>
|
||||
|
||||
${
|
||||
hasReact
|
||||
? `
|
||||
<div class="code-block__source code-block__source--react" data-flavor="react">
|
||||
${reactPre.outerHTML}
|
||||
</div>
|
||||
`
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="code-block__buttons">
|
||||
${hasReact ? ` ${htmlButton} ${reactButton} ` : ''}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="code-block__button code-block__toggle"
|
||||
aria-expanded="${isExpanded ? 'true' : 'false'}"
|
||||
aria-controls="${sourceGroupId}"
|
||||
>
|
||||
Source
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
${!code.classList.contains('no-codepen') ? codePenButton : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
pre.replaceWith(domParser.parseFromString(codeBlock, 'text/html').body);
|
||||
reactPre?.remove();
|
||||
|
||||
count++;
|
||||
}
|
||||
});
|
||||
|
||||
// Force the highlighter to run again so JSX fields get highlighted properly
|
||||
requestAnimationFrame(() => Prism.highlightAll());
|
||||
|
||||
next(doc.body.innerHTML);
|
||||
});
|
||||
|
||||
// After the page is done loading, force scripts in previews to execute
|
||||
hook.doneEach(() => {
|
||||
[...document.querySelectorAll('.code-block__preview script')].map(script => runScript(script));
|
||||
});
|
||||
|
||||
// Horizontal resizing
|
||||
hook.doneEach(() => {
|
||||
[...document.querySelectorAll('.code-block__preview')].forEach(preview => {
|
||||
const resizer = preview.querySelector('.code-block__resizer');
|
||||
let startX;
|
||||
let startWidth;
|
||||
|
||||
function dragStart(event) {
|
||||
startX = event.changedTouches ? event.changedTouches[0].pageX : event.clientX;
|
||||
startWidth = parseInt(document.defaultView.getComputedStyle(preview).width, 10);
|
||||
preview.classList.add('code-block__preview--dragging');
|
||||
event.preventDefault();
|
||||
document.documentElement.addEventListener('mousemove', dragMove, false);
|
||||
document.documentElement.addEventListener('touchmove', dragMove, false);
|
||||
document.documentElement.addEventListener('mouseup', dragStop, false);
|
||||
document.documentElement.addEventListener('touchend', dragStop, false);
|
||||
}
|
||||
|
||||
function dragMove(event) {
|
||||
setWidth(startWidth + (event.changedTouches ? event.changedTouches[0].pageX : event.pageX) - startX);
|
||||
}
|
||||
|
||||
function dragStop() {
|
||||
preview.classList.remove('code-block__preview--dragging');
|
||||
document.documentElement.removeEventListener('mousemove', dragMove, false);
|
||||
document.documentElement.removeEventListener('touchmove', dragMove, false);
|
||||
document.documentElement.removeEventListener('mouseup', dragStop, false);
|
||||
document.documentElement.removeEventListener('touchend', dragStop, false);
|
||||
}
|
||||
|
||||
function setWidth(width) {
|
||||
preview.style.width = `${width}px`;
|
||||
}
|
||||
|
||||
resizer.addEventListener('mousedown', dragStart);
|
||||
resizer.addEventListener('touchstart', dragStart);
|
||||
}, false);
|
||||
});
|
||||
});
|
||||
|
||||
// Toggle source mode
|
||||
document.addEventListener('click', event => {
|
||||
const button = event.target.closest('button');
|
||||
|
||||
if (button?.classList.contains('code-block__button--html')) {
|
||||
setFlavor('html');
|
||||
} else if (button?.classList.contains('code-block__button--react')) {
|
||||
setFlavor('react');
|
||||
} 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');
|
||||
});
|
||||
});
|
||||
|
||||
// Expand and collapse code blocks
|
||||
document.addEventListener('click', event => {
|
||||
const toggle = event.target.closest('.code-block__toggle');
|
||||
if (toggle) {
|
||||
const codeBlock = event.target.closest('.code-block');
|
||||
codeBlock.classList.toggle('code-block--expanded');
|
||||
event.target.setAttribute('aria-expanded', codeBlock.classList.contains('code-block--expanded'));
|
||||
}
|
||||
});
|
||||
|
||||
// Show pulse when copying
|
||||
document.addEventListener('click', event => {
|
||||
const button = event.target.closest('.docsify-copy-code-button');
|
||||
if (button) {
|
||||
button.classList.remove('copied');
|
||||
requestAnimationFrame(() => {
|
||||
button.addEventListener('animationend', () => button.classList.remove('copied'), { once: true });
|
||||
button.classList.add('copied');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Open in CodePen
|
||||
document.addEventListener('click', event => {
|
||||
const button = event.target.closest('button');
|
||||
const version = sessionStorage.getItem('sl-version');
|
||||
|
||||
if (button?.classList.contains('code-block__button--codepen')) {
|
||||
const codeBlock = button.closest('.code-block');
|
||||
const htmlExample = codeBlock.querySelector('.code-block__source--html > pre > code')?.textContent;
|
||||
const reactExample = codeBlock.querySelector('.code-block__source--react > pre > code')?.textContent;
|
||||
const isReact = flavor === 'react' && typeof reactExample === 'string';
|
||||
const theme = localStorage.getItem('theme');
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const isDark = theme === 'dark' || (theme === 'auto' && prefersDark);
|
||||
const editors = isReact ? '0010' : '1000';
|
||||
let htmlTemplate = '';
|
||||
let jsTemplate = '';
|
||||
let cssTemplate = '';
|
||||
|
||||
const form = document.createElement('form');
|
||||
form.action = 'https://codepen.io/pen/define';
|
||||
form.method = 'POST';
|
||||
form.target = '_blank';
|
||||
|
||||
// HTML templates
|
||||
if (!isReact) {
|
||||
htmlTemplate =
|
||||
`<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${version}/dist/shoelace.js"></script>\n` +
|
||||
`\n${htmlExample}`;
|
||||
jsTemplate = '';
|
||||
}
|
||||
|
||||
// React templates
|
||||
if (isReact) {
|
||||
htmlTemplate = '<div id="root"></div>';
|
||||
jsTemplate =
|
||||
`import React from 'https://cdn.skypack.dev/react@${reactVersion}';\n` +
|
||||
`import ReactDOM from 'https://cdn.skypack.dev/react-dom@${reactVersion}';\n` +
|
||||
`import { setBasePath } from 'https://cdn.skypack.dev/@shoelace-style/shoelace@${version}/dist/utilities/base-path';\n` +
|
||||
`\n` +
|
||||
`// Set the base path for Shoelace assets\n` +
|
||||
`setBasePath('https://cdn.skypack.dev/@shoelace-style/shoelace@${version}/dist/')\n` +
|
||||
`\n${convertModuleLinks(reactExample)}\n` +
|
||||
`\n` +
|
||||
`ReactDOM.render(<App />, document.getElementById('root'));`;
|
||||
}
|
||||
|
||||
// CSS templates
|
||||
cssTemplate =
|
||||
`@import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${version}/dist/themes/${
|
||||
isDark ? 'dark' : 'light'
|
||||
}.css';\n` +
|
||||
'\n' +
|
||||
'body {\n' +
|
||||
' font: 16px sans-serif;\n' +
|
||||
' background-color: var(--sl-color-neutral-0);\n' +
|
||||
' color: var(--sl-color-neutral-900);\n' +
|
||||
' padding: 1rem;\n' +
|
||||
'}';
|
||||
|
||||
// Docs: https://blog.codepen.io/documentation/prefill/
|
||||
const data = {
|
||||
title: '',
|
||||
description: '',
|
||||
tags: ['shoelace', 'web components'],
|
||||
editors,
|
||||
head: `<meta name="viewport" content="width=device-width">`,
|
||||
html_classes: `sl-theme-${isDark ? 'dark' : 'light'}`,
|
||||
css_external: ``,
|
||||
js_external: ``,
|
||||
js_module: true,
|
||||
js_pre_processor: isReact ? 'babel' : 'none',
|
||||
html: htmlTemplate,
|
||||
css: cssTemplate,
|
||||
js: jsTemplate
|
||||
};
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'data';
|
||||
input.value = JSON.stringify(data);
|
||||
form.append(input);
|
||||
|
||||
document.body.append(form);
|
||||
form.submit();
|
||||
form.remove();
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -1,549 +0,0 @@
|
||||
(() => {
|
||||
const isDev = location.hostname === 'localhost';
|
||||
const isNext = location.hostname === 'next.shoelace.style';
|
||||
const customElements = fetch('/dist/custom-elements.json')
|
||||
.then(res => res.json())
|
||||
.catch(err => console.error(err));
|
||||
|
||||
function createPropsTable(props) {
|
||||
const table = document.createElement('table');
|
||||
table.innerHTML = `
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Reflects</th>
|
||||
<th>Type</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${props
|
||||
.map(prop => {
|
||||
const hasAttribute = !!prop.attribute;
|
||||
const isAttributeDifferent = prop.attribute !== prop.name;
|
||||
let attributeInfo = '';
|
||||
|
||||
if (!hasAttribute) {
|
||||
attributeInfo = `<br><small>(property only)</small>`;
|
||||
} else if (isAttributeDifferent) {
|
||||
attributeInfo = `
|
||||
<br>
|
||||
<sl-tooltip content="This attribute is different from its property">
|
||||
<small>
|
||||
<code class="nowrap">
|
||||
${escapeHtml(prop.attribute)}
|
||||
</code>
|
||||
</small>
|
||||
</sl-tooltip>`;
|
||||
}
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>
|
||||
<code class="nowrap">${escapeHtml(prop.name)}</code>
|
||||
${attributeInfo}
|
||||
</td>
|
||||
<td>
|
||||
${escapeHtml(prop.description)}
|
||||
</td>
|
||||
<td style="text-align: center;">${
|
||||
prop.reflects ? '<sl-icon label="yes" name="check"></sl-icon>' : ''
|
||||
}</td>
|
||||
<td>${prop.type?.text ? `<code>${escapeHtml(prop.type?.text || '')}</code>` : '-'}</td>
|
||||
<td>${prop.default ? `<code>${escapeHtml(prop.default)}</code>` : '-'}</td>
|
||||
</tr>
|
||||
`;
|
||||
})
|
||||
.join('')}
|
||||
</tbody>
|
||||
`;
|
||||
|
||||
return table.outerHTML;
|
||||
}
|
||||
|
||||
function createEventsTable(events) {
|
||||
const table = document.createElement('table');
|
||||
table.innerHTML = `
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-flavor="html">Name</th>
|
||||
<th data-flavor="react">React Event</th>
|
||||
<th>Description</th>
|
||||
<th>Event Detail</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${events
|
||||
.map(
|
||||
event => `
|
||||
<tr>
|
||||
<td data-flavor="html"><code class="nowrap">${escapeHtml(event.name)}</code></td>
|
||||
<td data-flavor="react"><code class="nowrap">${escapeHtml(event.reactName)}</code></td>
|
||||
<td>${escapeHtml(event.description)}</td>
|
||||
<td>${event.type?.text ? `<code>${escapeHtml(event.type?.text)}` : '-'}</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join('')}
|
||||
</tbody>
|
||||
`;
|
||||
|
||||
return table.outerHTML;
|
||||
}
|
||||
|
||||
function createMethodsTable(methods) {
|
||||
const table = document.createElement('table');
|
||||
table.innerHTML = `
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Arguments</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${methods
|
||||
.map(
|
||||
method => `
|
||||
<tr>
|
||||
<td class="nowrap"><code>${escapeHtml(method.name)}</code></td>
|
||||
<td>${escapeHtml(method.description)}</td>
|
||||
<td>
|
||||
${
|
||||
method.parameters?.length
|
||||
? `
|
||||
<code>${escapeHtml(
|
||||
method.parameters.map(param => `${param.name}: ${param.type.text}`).join(', ')
|
||||
)}</code>
|
||||
`
|
||||
: '-'
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join('')}
|
||||
</tbody>
|
||||
`;
|
||||
|
||||
return table.outerHTML;
|
||||
}
|
||||
|
||||
function createSlotsTable(slots) {
|
||||
const table = document.createElement('table');
|
||||
table.innerHTML = `
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${slots
|
||||
.map(
|
||||
slot => `
|
||||
<tr>
|
||||
<td class="nowrap">${slot.name ? `<code>${escapeHtml(slot.name)}</code>` : '(default)'}</td>
|
||||
<td>${escapeHtml(slot.description)}</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join('')}
|
||||
</tbody>
|
||||
`;
|
||||
|
||||
return table.outerHTML;
|
||||
}
|
||||
|
||||
function createCustomPropertiesTable(styles) {
|
||||
const table = document.createElement('table');
|
||||
table.innerHTML = `
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${styles
|
||||
.map(
|
||||
style => `
|
||||
<tr>
|
||||
<td class="nowrap"><code>${escapeHtml(style.name)}</code></td>
|
||||
<td>${escapeHtml(style.description)}</td>
|
||||
<td>${style.default ? `<code>${escapeHtml(style.default)}</code>` : ''}</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join('')}
|
||||
</tbody>
|
||||
`;
|
||||
|
||||
return table.outerHTML;
|
||||
}
|
||||
|
||||
function createPartsTable(parts) {
|
||||
const table = document.createElement('table');
|
||||
table.innerHTML = `
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${parts
|
||||
.map(
|
||||
part => `
|
||||
<tr>
|
||||
<td class="nowrap"><code>${escapeHtml(part.name)}</code></td>
|
||||
<td>${escapeHtml(part.description)}</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join('')}
|
||||
</tbody>
|
||||
`;
|
||||
|
||||
return table.outerHTML;
|
||||
}
|
||||
|
||||
function createAnimationsTable(animations) {
|
||||
const table = document.createElement('table');
|
||||
table.innerHTML = `
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${animations
|
||||
.map(
|
||||
animation => `
|
||||
<tr>
|
||||
<td class="nowrap"><code>${escapeHtml(animation.name)}</code></td>
|
||||
<td>${escapeHtml(animation.description)}</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join('')}
|
||||
</tbody>
|
||||
`;
|
||||
|
||||
return table.outerHTML;
|
||||
}
|
||||
|
||||
function createDependenciesList(targetComponent, allComponents) {
|
||||
const ul = document.createElement('ul');
|
||||
const dependencies = [];
|
||||
|
||||
// Recursively fetch sub-dependencies
|
||||
function getDependencies(tag) {
|
||||
const component = allComponents.find(c => c.tagName === tag);
|
||||
if (!component || !Array.isArray(component.dependencies)) {
|
||||
return;
|
||||
}
|
||||
|
||||
component.dependencies?.forEach(dependentTag => {
|
||||
if (!dependencies.includes(dependentTag)) {
|
||||
dependencies.push(dependentTag);
|
||||
}
|
||||
getDependencies(dependentTag);
|
||||
});
|
||||
}
|
||||
|
||||
getDependencies(targetComponent);
|
||||
dependencies.sort().forEach(tag => {
|
||||
const li = document.createElement('li');
|
||||
li.innerHTML = `<code><${tag}></code>`;
|
||||
ul.appendChild(li);
|
||||
});
|
||||
|
||||
return ul.outerHTML;
|
||||
}
|
||||
|
||||
function escapeHtml(html) {
|
||||
if (!html) {
|
||||
return '';
|
||||
}
|
||||
return html
|
||||
.toString()
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/`(.*?)`/g, '<code>$1</code>');
|
||||
}
|
||||
|
||||
function getAllComponents(metadata) {
|
||||
const allComponents = [];
|
||||
metadata.modules?.forEach(module => {
|
||||
module.declarations?.forEach(declaration => {
|
||||
if (declaration.customElement) {
|
||||
// Generate the dist path based on the src path and attach it to the component
|
||||
declaration.path = module.path.replace(/^src\//, 'dist/').replace(/\.ts$/, '.js');
|
||||
|
||||
allComponents.push(declaration);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return allComponents;
|
||||
}
|
||||
|
||||
function getComponent(metadata, tagName) {
|
||||
return getAllComponents(metadata).find(component => component.tagName === tagName);
|
||||
}
|
||||
|
||||
if (!window.$docsify) {
|
||||
throw new Error('Docsify must be loaded before installing this plugin.');
|
||||
}
|
||||
|
||||
window.$docsify.plugins.push(hook => {
|
||||
hook.mounted(async () => {
|
||||
const metadata = await customElements;
|
||||
const target = document.querySelector('.app-name');
|
||||
|
||||
// Add version
|
||||
const version = document.createElement('div');
|
||||
version.classList.add('sidebar-version');
|
||||
version.textContent = isDev ? 'Development' : isNext ? 'Next' : metadata.package.version;
|
||||
target.appendChild(version);
|
||||
|
||||
// Store version for reuse
|
||||
sessionStorage.setItem('sl-version', metadata.package.version);
|
||||
|
||||
// Add repo buttons
|
||||
const buttons = document.createElement('div');
|
||||
buttons.classList.add('sidebar-buttons');
|
||||
buttons.innerHTML = `
|
||||
<sl-button size="small" class="repo-button repo-button--sponsor" href="https://github.com/sponsors/claviska" target="_blank">
|
||||
<sl-icon slot="prefix" name="heart"></sl-icon> Sponsor
|
||||
</sl-button>
|
||||
<sl-button size="small" class="repo-button repo-button--github" href="https://github.com/shoelace-style/shoelace/stargazers" target="_blank">
|
||||
<sl-icon slot="prefix" name="github"></sl-icon> <span class="github-star-count">Star</span>
|
||||
</sl-button>
|
||||
<sl-button size="small" class="repo-button repo-button--twitter" href="https://twitter.com/shoelace_style" target="_blank">
|
||||
<sl-icon slot="prefix" name="twitter"></sl-icon> Follow
|
||||
</sl-button>
|
||||
`;
|
||||
target.appendChild(buttons);
|
||||
});
|
||||
|
||||
hook.beforeEach(async (content, next) => {
|
||||
const metadata = await customElements;
|
||||
|
||||
// Replace %VERSION% placeholders
|
||||
content = content.replace(/%VERSION%/g, metadata.package.version);
|
||||
|
||||
// Handle [component-header] tags
|
||||
content = content.replace(/\[component-header:([a-z-]+)\]/g, (match, tag) => {
|
||||
const component = getComponent(metadata, tag);
|
||||
let result = '';
|
||||
|
||||
if (!component) {
|
||||
console.error(`Component not found in metadata: ${tag}`);
|
||||
return next(content);
|
||||
}
|
||||
|
||||
let badgeType = 'neutral';
|
||||
if (component.status === 'stable') {
|
||||
badgeType = 'primary';
|
||||
}
|
||||
if (component.status === 'experimental') {
|
||||
badgeType = 'warning';
|
||||
}
|
||||
if (component.status === 'planned') {
|
||||
badgeType = 'neutral';
|
||||
}
|
||||
if (component.status === 'deprecated') {
|
||||
badgeType = 'danger';
|
||||
}
|
||||
|
||||
result += `
|
||||
<div class="component-header">
|
||||
<div class="component-header__tag">
|
||||
<code><${component.tagName}> | ${component.name}</code>
|
||||
</div>
|
||||
|
||||
<div class="component-header__info">
|
||||
<sl-badge variant="neutral" pill>
|
||||
Since ${component.since || '?'}
|
||||
</sl-badge>
|
||||
|
||||
<sl-badge variant="${badgeType}" pill style="text-transform: capitalize;">
|
||||
${component.status}
|
||||
</sl-badge>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return result.replace(/^ +| +$/gm, '');
|
||||
});
|
||||
|
||||
// Handle [component-metadata] tags
|
||||
content = content.replace(/\[component-metadata:([a-z-]+)\]/g, (match, tag) => {
|
||||
const component = getComponent(metadata, tag);
|
||||
let result = '';
|
||||
|
||||
if (!component) {
|
||||
console.error(`Component not found in metadata: ${tag}`);
|
||||
return next(content);
|
||||
}
|
||||
|
||||
// Remove members that are private or don't have a description
|
||||
const members = component.members?.filter(member => member.description && member.privacy !== 'private');
|
||||
const methods = members?.filter(prop => prop.kind === 'method' && prop.privacy !== 'private');
|
||||
const props = members?.filter(prop => {
|
||||
// Look for a corresponding attribute
|
||||
const attribute = component.attributes?.find(attr => attr.fieldName === prop.name);
|
||||
if (attribute) {
|
||||
prop.attribute = attribute.name || attribute.fieldName;
|
||||
}
|
||||
|
||||
return prop.kind === 'field' && prop.privacy !== 'private';
|
||||
});
|
||||
|
||||
if (component.path) {
|
||||
/* prettier-ignore */
|
||||
result += `
|
||||
## Importing
|
||||
|
||||
<sl-tab-group>
|
||||
<sl-tab slot="nav" panel="script">Script</sl-tab>
|
||||
<sl-tab slot="nav" panel="import">Import</sl-tab>
|
||||
<sl-tab slot="nav" panel="bundler">Bundler</sl-tab>
|
||||
<sl-tab slot="nav" panel="react">React</sl-tab>
|
||||
|
||||
<sl-tab-panel name="script">\n
|
||||
To import this component from [the CDN](https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace) using a script tag:
|
||||
|
||||
\`\`\`html
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${metadata.package.version}/${component.path}"></script>
|
||||
\`\`\`
|
||||
</sl-tab-panel>
|
||||
|
||||
<sl-tab-panel name="import">\n
|
||||
To import this component from [the CDN](https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace) using a JavaScript import:
|
||||
|
||||
\`\`\`js
|
||||
import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${metadata.package.version}/${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}';
|
||||
\`\`\`
|
||||
</sl-tab-panel>
|
||||
|
||||
<sl-tab-panel name="react">\n
|
||||
To import this component as a [React component](/frameworks/react):
|
||||
\`\`\`js
|
||||
import { ${component.name} } from '@shoelace-style/shoelace/dist/react';
|
||||
\`\`\`
|
||||
</sl-tab-panel>
|
||||
</sl-tab-group>
|
||||
`;
|
||||
}
|
||||
|
||||
if (props?.length) {
|
||||
result += `
|
||||
## Properties
|
||||
${createPropsTable(props)}
|
||||
`;
|
||||
}
|
||||
|
||||
if (component.events?.length) {
|
||||
result += `
|
||||
## Events
|
||||
${createEventsTable(component.events)}
|
||||
`;
|
||||
}
|
||||
|
||||
if (methods?.length) {
|
||||
result += `
|
||||
## Methods
|
||||
|
||||
<p data-flavor="html">
|
||||
Methods can be called by obtaining a reference to the element and calling
|
||||
<code>el.methodName()</code>.
|
||||
</p>
|
||||
|
||||
|
||||
<p data-flavor="react">
|
||||
Methods can be called by obtaining a <code>ref</code> to the element and calling
|
||||
<code>ref.current.methodName()</code>.
|
||||
</p>
|
||||
|
||||
${createMethodsTable(methods)}
|
||||
`;
|
||||
}
|
||||
|
||||
if (component.slots?.length) {
|
||||
result += `
|
||||
## Slots
|
||||
${createSlotsTable(component.slots)}
|
||||
`;
|
||||
}
|
||||
|
||||
if (component.cssProperties?.length) {
|
||||
result += `
|
||||
## CSS Custom Properties
|
||||
${createCustomPropertiesTable(component.cssProperties)}
|
||||
`;
|
||||
}
|
||||
|
||||
if (component.cssParts?.length) {
|
||||
result += `
|
||||
## CSS Parts
|
||||
${createPartsTable(component.cssParts)}
|
||||
`;
|
||||
}
|
||||
|
||||
if (component.animations?.length) {
|
||||
result += `
|
||||
## Animations
|
||||
${createAnimationsTable(component.animations)}
|
||||
|
||||
Learn how to [customize animations](/getting-started/customizing#animations).
|
||||
`;
|
||||
}
|
||||
|
||||
if (component.dependencies?.length) {
|
||||
result += `
|
||||
## Dependencies
|
||||
|
||||
This component imports the following dependencies.
|
||||
|
||||
${createDependenciesList(component.tagName, getAllComponents(metadata))}
|
||||
`;
|
||||
}
|
||||
|
||||
// Strip whitespace so markdown doesn't process things as code blocks
|
||||
return result.replace(/^ +| +$/gm, '');
|
||||
});
|
||||
|
||||
next(content);
|
||||
});
|
||||
|
||||
// Wrap tables so we can scroll them horizontally when needed
|
||||
hook.doneEach(() => {
|
||||
const content = document.querySelector('.content');
|
||||
const tables = [...content.querySelectorAll('table')];
|
||||
|
||||
tables.forEach(table => {
|
||||
table.outerHTML = `
|
||||
<div class="table-wrapper">
|
||||
${table.outerHTML}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
@@ -1,24 +0,0 @@
|
||||
(() => {
|
||||
if (!window.$docsify) {
|
||||
throw new Error('Docsify must be loaded before installing this plugin.');
|
||||
}
|
||||
|
||||
//
|
||||
// Docsify generates pages dynamically and asynchronously, so when a reload happens, the scroll position can't be
|
||||
// be restored immediately. This plugin waits until Docsify loads the page and then restores it.
|
||||
//
|
||||
window.$docsify.plugins.push(hook => {
|
||||
hook.ready(() => {
|
||||
// Restore
|
||||
const scrollTop = sessionStorage.getItem('bs-scroll');
|
||||
if (scrollTop) {
|
||||
document.documentElement.scrollTop = scrollTop;
|
||||
}
|
||||
|
||||
// Remember
|
||||
document.addEventListener('scroll', () => {
|
||||
sessionStorage.setItem('bs-scroll', document.documentElement.scrollTop);
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
1311
docs/assets/plugins/search/lunr.min.js
vendored
@@ -1,180 +0,0 @@
|
||||
body.site-search-visible {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar .search-box {
|
||||
margin: 1.25rem 26px;
|
||||
}
|
||||
|
||||
.sidebar .search-box kbd {
|
||||
margin-top: 2px;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
/* Site search */
|
||||
.site-search {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.site-search[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.site-search__overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: var(--sl-overlay-background-color);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.site-search__panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 460px;
|
||||
max-height: calc(100vh - 20rem);
|
||||
background-color: var(--sl-panel-background-color);
|
||||
border-radius: var(--sl-border-radius-large);
|
||||
box-shadow: var(--sl-shadow-x-large);
|
||||
margin: 10rem auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.site-search__panel {
|
||||
max-width: 100%;
|
||||
max-height: calc(92vh - 120px); /* allow iOS browser chrome */
|
||||
margin: 4vh var(--sl-spacing-medium);
|
||||
}
|
||||
}
|
||||
|
||||
.site-search__input::part(base) {
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: var(--sl-border-radius-large);
|
||||
}
|
||||
|
||||
.site-search__input:focus-within::part(base) {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.site-search__input {
|
||||
--sl-input-height-large: 4rem;
|
||||
}
|
||||
|
||||
.site-search__body {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.site-search--has-results .site-search__body {
|
||||
border-top: solid 1px var(--sl-color-neutral-200);
|
||||
}
|
||||
|
||||
.site-search__results {
|
||||
display: none;
|
||||
line-height: var(--sl-line-height-dense);
|
||||
list-style: none;
|
||||
padding: var(--sl-spacing-x-small) 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.site-search--has-results .site-search__results {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.site-search__results a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
padding: var(--sl-spacing-x-small) var(--sl-spacing-large);
|
||||
}
|
||||
|
||||
.site-search__results li a:hover,
|
||||
.site-search__results li a:hover small {
|
||||
background-color: var(--sl-color-neutral-100);
|
||||
}
|
||||
|
||||
.site-search__results li[aria-selected='true'] a,
|
||||
.site-search__results li[aria-selected='true'] a small,
|
||||
.site-search__results li[aria-selected='true'] a sl-icon {
|
||||
outline: none;
|
||||
color: var(--sl-color-neutral-0);
|
||||
background-color: var(--sl-color-primary-600);
|
||||
}
|
||||
|
||||
.sl-theme-dark .site-search__results li[aria-selected='true'] a,
|
||||
.sl-theme-dark .site-search__results li[aria-selected='true'] a small,
|
||||
.sl-theme-dark .site-search__results li[aria-selected='true'] a sl-icon {
|
||||
background-color: var(--sl-color-primary-400);
|
||||
color: var(--sl-color-neutral-1000);
|
||||
}
|
||||
|
||||
.site-search__results h3 {
|
||||
font-weight: var(--sl-font-weight-semibold);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.site-search__results small {
|
||||
display: block;
|
||||
color: var(--sl-color-neutral-600);
|
||||
}
|
||||
|
||||
.site-search__result {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.site-search__result a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sl-spacing-medium);
|
||||
}
|
||||
|
||||
.site-search__result-icon {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
color: var(--sl-color-neutral-400);
|
||||
font-size: var(--sl-font-size-x-large);
|
||||
}
|
||||
|
||||
.site-search__result-description {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.site-search__empty {
|
||||
display: none;
|
||||
border-top: solid 1px var(--sl-color-neutral-200);
|
||||
text-align: center;
|
||||
padding: var(--sl-spacing-x-large);
|
||||
}
|
||||
|
||||
.site-search--no-results .site-search__empty {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.site-search__footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: var(--sl-spacing-large);
|
||||
border-top: solid 1px var(--sl-color-neutral-200);
|
||||
border-bottom-left-radius: inherit;
|
||||
border-bottom-right-radius: inherit;
|
||||
padding: var(--sl-spacing-medium);
|
||||
}
|
||||
|
||||
.site-search__footer small {
|
||||
color: var(--sl-color-neutral-700);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.site-search__footer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -1,297 +0,0 @@
|
||||
/* global lunr */
|
||||
(() => {
|
||||
if (!window.$docsify) {
|
||||
throw new Error('Docsify must be loaded before installing this plugin.');
|
||||
}
|
||||
|
||||
window.$docsify.plugins.push(hook => {
|
||||
// Append the search box to the sidebar
|
||||
hook.mounted(() => {
|
||||
const appName = document.querySelector('.sidebar .app-name');
|
||||
const searchBox = document.createElement('div');
|
||||
searchBox.classList.add('search-box');
|
||||
searchBox.innerHTML = `
|
||||
<sl-input
|
||||
type="search"
|
||||
placeholder="Search"
|
||||
clearable
|
||||
pill
|
||||
>
|
||||
<sl-icon slot="prefix" name="search"></sl-icon>
|
||||
<kbd slot="suffix" title="Press / to search">/</kbd>
|
||||
</sl-input>
|
||||
`;
|
||||
const searchBoxInput = searchBox.querySelector('sl-input');
|
||||
|
||||
appName.insertAdjacentElement('afterend', searchBox);
|
||||
|
||||
// Show the search panel when the search is clicked
|
||||
searchBoxInput.addEventListener('mousedown', event => {
|
||||
event.preventDefault();
|
||||
show();
|
||||
});
|
||||
|
||||
// Show the search panel when a key is pressed
|
||||
searchBoxInput.addEventListener('keydown', event => {
|
||||
if (event.key === 'Tab') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass the character that was typed through to the search input
|
||||
if (event.key.length === 1) {
|
||||
event.preventDefault();
|
||||
input.value = event.key;
|
||||
show();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Append the search panel to the body
|
||||
const siteSearch = document.createElement('div');
|
||||
siteSearch.classList.add('site-search');
|
||||
siteSearch.hidden = true;
|
||||
siteSearch.innerHTML = `
|
||||
<div class="site-search__overlay"></div>
|
||||
<div class="site-search__panel">
|
||||
<header class="site-search__header">
|
||||
<sl-input
|
||||
class="site-search__input"
|
||||
type="search"
|
||||
placeholder="Search this site"
|
||||
size="large"
|
||||
clearable
|
||||
>
|
||||
<sl-icon slot="prefix" name="search"></sl-icon>
|
||||
</sl-input>
|
||||
</header>
|
||||
<div class="site-search__body">
|
||||
<ul class="site-search__results"></ul>
|
||||
<div class="site-search__empty">No results found.</div>
|
||||
</div>
|
||||
<footer class="site-search__footer">
|
||||
<small><kbd>↑</kbd> <kbd>↓</kbd> navigate</small>
|
||||
<small><kbd>↲</kbd> select</small>
|
||||
<small><kbd>esc</kbd> close</small>
|
||||
</footer>
|
||||
</div>
|
||||
`;
|
||||
document.body.append(siteSearch);
|
||||
|
||||
const overlay = siteSearch.querySelector('.site-search__overlay');
|
||||
const panel = siteSearch.querySelector('.site-search__panel');
|
||||
const input = siteSearch.querySelector('.site-search__input');
|
||||
const results = siteSearch.querySelector('.site-search__results');
|
||||
const animationDuration = 150;
|
||||
const searchDebounce = 200;
|
||||
let isShowing = false;
|
||||
let searchTimeout;
|
||||
let searchIndex;
|
||||
let map;
|
||||
|
||||
// Load search data
|
||||
fetch('../../../search.json')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
searchIndex = lunr.Index.load(data.searchIndex);
|
||||
map = data.map;
|
||||
});
|
||||
|
||||
async function show() {
|
||||
isShowing = true;
|
||||
document.body.classList.add('site-search-visible');
|
||||
siteSearch.hidden = false;
|
||||
input.focus();
|
||||
updateResults();
|
||||
|
||||
await Promise.all([
|
||||
panel.animate(
|
||||
[
|
||||
{ opacity: 0, transform: 'scale(.9)' },
|
||||
{ opacity: 1, transform: 'scale(1)' }
|
||||
],
|
||||
{ duration: animationDuration }
|
||||
).finished,
|
||||
overlay.animate([{ opacity: 0 }, { opacity: 1 }], { duration: animationDuration }).finished
|
||||
]);
|
||||
|
||||
document.addEventListener('mousedown', handleDocumentMouseDown);
|
||||
document.addEventListener('keydown', handleDocumentKeyDown);
|
||||
document.addEventListener('focusin', handleDocumentFocusIn);
|
||||
}
|
||||
|
||||
async function hide() {
|
||||
isShowing = false;
|
||||
document.body.classList.remove('site-search-visible');
|
||||
|
||||
await Promise.all([
|
||||
panel.animate(
|
||||
[
|
||||
{ opacity: 1, transform: 'scale(1)' },
|
||||
{ opacity: 0, transform: 'scale(.9)' }
|
||||
],
|
||||
{ duration: animationDuration }
|
||||
).finished,
|
||||
overlay.animate([{ opacity: 1 }, { opacity: 0 }], { duration: animationDuration }).finished
|
||||
]);
|
||||
|
||||
siteSearch.hidden = true;
|
||||
input.value = '';
|
||||
updateResults();
|
||||
|
||||
document.removeEventListener('mousedown', handleDocumentMouseDown);
|
||||
document.removeEventListener('keydown', handleDocumentKeyDown);
|
||||
document.removeEventListener('focusin', handleDocumentFocusIn);
|
||||
}
|
||||
|
||||
function handleInput() {
|
||||
// Debounce search queries
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => updateResults(input.value), searchDebounce);
|
||||
}
|
||||
|
||||
function handleDocumentFocusIn(event) {
|
||||
// Close when focus leaves the panel
|
||||
if (event.target.closest('.site-search__panel') !== panel) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
function handleDocumentMouseDown(event) {
|
||||
// Close when clicking outside of the panel
|
||||
if (event.target.closest('.site-search__overlay') === overlay) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
function handleDocumentKeyDown(event) {
|
||||
// Close when pressing escape
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle keyboard selections
|
||||
if (['ArrowDown', 'ArrowUp', 'Home', 'End', 'Enter'].includes(event.key)) {
|
||||
event.preventDefault();
|
||||
|
||||
const currentEl = results.querySelector('[aria-selected="true"]');
|
||||
const items = [...results.querySelectorAll('li')];
|
||||
const index = items.indexOf(currentEl);
|
||||
let nextEl;
|
||||
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowUp':
|
||||
nextEl = items[Math.max(0, index - 1)];
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
nextEl = items[Math.min(items.length - 1, index + 1)];
|
||||
break;
|
||||
case 'Home':
|
||||
nextEl = items[0];
|
||||
break;
|
||||
case 'End':
|
||||
nextEl = items[items.length - 1];
|
||||
break;
|
||||
case 'Enter':
|
||||
currentEl?.querySelector('a')?.click();
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the selected item
|
||||
items.forEach(item => {
|
||||
if (item === nextEl) {
|
||||
item.setAttribute('aria-selected', 'true');
|
||||
nextEl.scrollIntoView({ block: 'nearest' });
|
||||
} else {
|
||||
item.setAttribute('aria-selected', 'false');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updateResults(query = '') {
|
||||
try {
|
||||
await searchIndex;
|
||||
|
||||
const hasQuery = query.length > 0;
|
||||
const matches = hasQuery ? searchIndex.search(`t:*${query}*^20 h:*${query}*^10 ${query}~1^5 *${query}*^0`) : [];
|
||||
const hasResults = hasQuery && matches.length > 0;
|
||||
siteSearch.classList.toggle('site-search--has-results', hasQuery && hasResults);
|
||||
siteSearch.classList.toggle('site-search--no-results', hasQuery && !hasResults);
|
||||
panel.setAttribute('aria-expanded', hasQuery && hasResults ? 'true' : 'false');
|
||||
|
||||
results.innerHTML = '';
|
||||
|
||||
matches.forEach((match, index) => {
|
||||
const page = map[match.ref];
|
||||
const li = document.createElement('li');
|
||||
const a = document.createElement('a');
|
||||
let icon = 'file-text';
|
||||
|
||||
if (page.url.includes('getting-started/')) {
|
||||
icon = 'lightbulb';
|
||||
}
|
||||
if (page.url.includes('resources/')) {
|
||||
icon = 'book';
|
||||
}
|
||||
if (page.url.includes('components/')) {
|
||||
icon = 'puzzle';
|
||||
}
|
||||
if (page.url.includes('tokens/')) {
|
||||
icon = 'palette2';
|
||||
}
|
||||
if (page.url.includes('utilities/')) {
|
||||
icon = 'wrench';
|
||||
}
|
||||
if (page.url.includes('tutorials/')) {
|
||||
icon = 'joystick';
|
||||
}
|
||||
|
||||
a.href = window.$docsify.routerMode === 'hash' ? `/#/${page.url}` : `/${page.url}`;
|
||||
a.innerHTML = `
|
||||
<div class="site-search__result-icon">
|
||||
<sl-icon name="${icon}" aria-hidden="true"></sl-icon>
|
||||
</div>
|
||||
<div class="site-search__result__details">
|
||||
<h3>${page.title}</h3>
|
||||
<small>${page.url}</small>
|
||||
</div>
|
||||
`;
|
||||
|
||||
li.classList.add('site-search__result');
|
||||
li.setAttribute('aria-selected', index === 0 ? 'true' : 'false');
|
||||
li.appendChild(a);
|
||||
results.appendChild(li);
|
||||
});
|
||||
} catch {
|
||||
// Ignore query errors as the user types
|
||||
}
|
||||
}
|
||||
|
||||
// Show the search panel slash is pressed outside of a form element
|
||||
document.addEventListener('keydown', event => {
|
||||
if (
|
||||
!isShowing &&
|
||||
event.key === '/' &&
|
||||
!event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))
|
||||
) {
|
||||
event.preventDefault();
|
||||
show();
|
||||
}
|
||||
});
|
||||
|
||||
input.addEventListener('sl-input', handleInput);
|
||||
|
||||
// Close when a result is selected
|
||||
results.addEventListener('click', event => {
|
||||
if (event.target.closest('a')) {
|
||||
hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
@@ -1,29 +0,0 @@
|
||||
.theme-picker {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
.theme-picker:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.theme-picker sl-menu-label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.theme-picker sl-menu-label kbd {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.theme-picker {
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
}
|
||||
|
||||
.theme-picker sl-menu-label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
(() => {
|
||||
if (!window.$docsify) {
|
||||
throw new Error('Docsify must be loaded before installing this plugin.');
|
||||
}
|
||||
|
||||
window.$docsify.plugins.push(hook => {
|
||||
hook.mounted(() => {
|
||||
function getTheme() {
|
||||
return localStorage.getItem('theme') || 'auto';
|
||||
}
|
||||
|
||||
function isDark() {
|
||||
if (theme === 'auto') {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
return theme === 'dark';
|
||||
}
|
||||
|
||||
function setTheme(newTheme) {
|
||||
const noTransitions = Object.assign(document.createElement('style'), {
|
||||
textContent: '* { transition: none !important; }'
|
||||
});
|
||||
|
||||
theme = newTheme;
|
||||
localStorage.setItem('theme', theme);
|
||||
|
||||
// Update the UI
|
||||
[...menu.querySelectorAll('sl-menu-item')].map(item => (item.checked = item.getAttribute('value') === theme));
|
||||
menuIcon.name = isDark() ? 'moon' : 'sun';
|
||||
|
||||
// Toggle the dark mode class without transitions
|
||||
document.body.appendChild(noTransitions);
|
||||
requestAnimationFrame(() => {
|
||||
document.documentElement.classList.toggle('sl-theme-dark', isDark());
|
||||
requestAnimationFrame(() => document.body.removeChild(noTransitions));
|
||||
});
|
||||
}
|
||||
|
||||
let theme = getTheme();
|
||||
|
||||
// Generate the theme picker dropdown
|
||||
const dropdown = document.createElement('sl-dropdown');
|
||||
dropdown.classList.add('theme-picker');
|
||||
dropdown.innerHTML = `
|
||||
<sl-button size="small" pill slot="trigger" caret>
|
||||
<sl-icon name="sun" label="Select Theme"></sl-icon>
|
||||
</sl-button>
|
||||
<sl-menu>
|
||||
<sl-menu-label>Toggle <kbd>\\</kbd></sl-menu-label>
|
||||
<sl-menu-item value="light">Light</sl-menu-item>
|
||||
<sl-menu-item value="dark">Dark</sl-menu-item>
|
||||
<sl-divider></sl-divider>
|
||||
<sl-menu-item value="auto">Auto</sl-menu-item>
|
||||
</sl-menu>
|
||||
`;
|
||||
document.querySelector('.sidebar-toggle').insertAdjacentElement('afterend', dropdown);
|
||||
|
||||
// Listen for selections
|
||||
const menu = dropdown.querySelector('sl-menu');
|
||||
const menuIcon = dropdown.querySelector('sl-icon');
|
||||
menu.addEventListener('sl-select', event => setTheme(event.detail.item.value));
|
||||
|
||||
// Update the theme when the preference changes
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => setTheme(theme));
|
||||
|
||||
// Toggle themes when pressing backslash
|
||||
document.addEventListener('keydown', event => {
|
||||
if (
|
||||
event.key === '\\' &&
|
||||
!event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))
|
||||
) {
|
||||
event.preventDefault();
|
||||
|
||||
setTheme(isDark() ? 'light' : 'dark');
|
||||
}
|
||||
});
|
||||
|
||||
// Set the initial theme and sync the UI
|
||||
setTheme(theme);
|
||||
});
|
||||
});
|
||||
})();
|
||||
249
docs/assets/scripts/code-previews.js
Normal file
@@ -0,0 +1,249 @@
|
||||
(() => {
|
||||
function convertModuleLinks(html) {
|
||||
html = html
|
||||
.replace(/@shoelace-style\/shoelace/g, `https://esm.sh/@shoelace-style/shoelace@${shoelaceVersion}`)
|
||||
.replace(/from 'react'/g, `from 'https://esm.sh/react@${reactVersion}'`)
|
||||
.replace(/from "react"/g, `from "https://esm.sh/react@${reactVersion}"`);
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function getAdjacentExample(name, pre) {
|
||||
let currentPre = pre.nextElementSibling;
|
||||
|
||||
while (currentPre?.tagName.toLowerCase() === 'pre') {
|
||||
if (currentPre?.getAttribute('data-lang').split(' ').includes(name)) {
|
||||
return currentPre;
|
||||
}
|
||||
|
||||
currentPre = currentPre.nextElementSibling;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function runScript(script) {
|
||||
const newScript = document.createElement('script');
|
||||
|
||||
if (script.type === 'module') {
|
||||
newScript.type = 'module';
|
||||
newScript.textContent = script.innerHTML;
|
||||
} else {
|
||||
newScript.appendChild(document.createTextNode(`(() => { ${script.innerHTML} })();`));
|
||||
}
|
||||
|
||||
script.parentNode.replaceChild(newScript, script);
|
||||
}
|
||||
|
||||
function getFlavor() {
|
||||
return sessionStorage.getItem('flavor') || 'html';
|
||||
}
|
||||
|
||||
function setFlavor(newFlavor) {
|
||||
flavor = ['html', 'react'].includes(newFlavor) ? newFlavor : 'html';
|
||||
sessionStorage.setItem('flavor', flavor);
|
||||
|
||||
// Set the flavor class on the body
|
||||
document.documentElement.classList.toggle('flavor-html', flavor === 'html');
|
||||
document.documentElement.classList.toggle('flavor-react', flavor === 'react');
|
||||
}
|
||||
|
||||
function syncFlavor() {
|
||||
setFlavor(getFlavor());
|
||||
|
||||
document.querySelectorAll('.code-preview__button--html').forEach(preview => {
|
||||
if (flavor === 'html') {
|
||||
preview.classList.add('code-preview__button--selected');
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('.code-preview__button--react').forEach(preview => {
|
||||
if (flavor === 'react') {
|
||||
preview.classList.add('code-preview__button--selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const shoelaceVersion = document.documentElement.getAttribute('data-shoelace-version');
|
||||
const reactVersion = '18.2.0';
|
||||
const cdndir = 'cdn';
|
||||
const npmdir = 'dist';
|
||||
let flavor = getFlavor();
|
||||
let count = 1;
|
||||
|
||||
// We need the version to open
|
||||
if (!shoelaceVersion) {
|
||||
throw new Error('The data-shoelace-version attribute is missing from <html>.');
|
||||
}
|
||||
|
||||
// Sync flavor UI on page load
|
||||
syncFlavor();
|
||||
|
||||
//
|
||||
// Resizing previews
|
||||
//
|
||||
document.addEventListener('mousedown', handleResizerDrag);
|
||||
document.addEventListener('touchstart', handleResizerDrag, { passive: true });
|
||||
|
||||
function handleResizerDrag(event) {
|
||||
const resizer = event.target.closest('.code-preview__resizer');
|
||||
const preview = event.target.closest('.code-preview__preview');
|
||||
|
||||
if (!resizer || !preview) return;
|
||||
|
||||
let startX = event.changedTouches ? event.changedTouches[0].pageX : event.clientX;
|
||||
let startWidth = parseInt(document.defaultView.getComputedStyle(preview).width, 10);
|
||||
|
||||
event.preventDefault();
|
||||
preview.classList.add('code-preview__preview--dragging');
|
||||
document.documentElement.addEventListener('mousemove', dragMove);
|
||||
document.documentElement.addEventListener('touchmove', dragMove);
|
||||
document.documentElement.addEventListener('mouseup', dragStop);
|
||||
document.documentElement.addEventListener('touchend', dragStop);
|
||||
|
||||
function dragMove(event) {
|
||||
const width = startWidth + (event.changedTouches ? event.changedTouches[0].pageX : event.pageX) - startX;
|
||||
preview.style.width = `${width}px`;
|
||||
}
|
||||
|
||||
function dragStop() {
|
||||
preview.classList.remove('code-preview__preview--dragging');
|
||||
document.documentElement.removeEventListener('mousemove', dragMove);
|
||||
document.documentElement.removeEventListener('touchmove', dragMove);
|
||||
document.documentElement.removeEventListener('mouseup', dragStop);
|
||||
document.documentElement.removeEventListener('touchend', dragStop);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Toggle source mode
|
||||
//
|
||||
document.addEventListener('click', event => {
|
||||
const button = event.target.closest('.code-preview__button');
|
||||
const codeBlock = button?.closest('.code-preview');
|
||||
|
||||
if (button?.classList.contains('code-preview__button--html')) {
|
||||
// Show HTML
|
||||
setFlavor('html');
|
||||
toggleSource(codeBlock, true);
|
||||
} else if (button?.classList.contains('code-preview__button--react')) {
|
||||
// Show React
|
||||
setFlavor('react');
|
||||
toggleSource(codeBlock, true);
|
||||
} else if (button?.classList.contains('code-preview__toggle')) {
|
||||
// Toggle source
|
||||
toggleSource(codeBlock);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update flavor buttons
|
||||
[...document.querySelectorAll('.code-preview')].forEach(cb => {
|
||||
cb.querySelector('.code-preview__button--html')?.classList.toggle(
|
||||
'code-preview__button--selected',
|
||||
flavor === 'html'
|
||||
);
|
||||
cb.querySelector('.code-preview__button--react')?.classList.toggle(
|
||||
'code-preview__button--selected',
|
||||
flavor === 'react'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function toggleSource(codeBlock, force) {
|
||||
codeBlock.classList.toggle('code-preview--expanded', force);
|
||||
event.target.setAttribute('aria-expanded', codeBlock.classList.contains('code-preview--expanded'));
|
||||
}
|
||||
|
||||
//
|
||||
// Open in CodePen
|
||||
//
|
||||
document.addEventListener('click', event => {
|
||||
const button = event.target.closest('button');
|
||||
|
||||
if (button?.classList.contains('code-preview__button--codepen')) {
|
||||
const codeBlock = button.closest('.code-preview');
|
||||
const htmlExample = codeBlock.querySelector('.code-preview__source--html > pre > code')?.textContent;
|
||||
const reactExample = codeBlock.querySelector('.code-preview__source--react > pre > code')?.textContent;
|
||||
const isReact = flavor === 'react' && typeof reactExample === 'string';
|
||||
const theme = document.documentElement.classList.contains('sl-theme-dark') ? 'dark' : 'light';
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const isDark = theme === 'dark' || (theme === 'auto' && prefersDark);
|
||||
const editors = isReact ? '0010' : '1000';
|
||||
let htmlTemplate = '';
|
||||
let jsTemplate = '';
|
||||
let cssTemplate = '';
|
||||
|
||||
const form = document.createElement('form');
|
||||
form.action = 'https://codepen.io/pen/define';
|
||||
form.method = 'POST';
|
||||
form.target = '_blank';
|
||||
|
||||
// HTML templates
|
||||
if (!isReact) {
|
||||
htmlTemplate =
|
||||
`<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/shoelace.js"></script>\n` +
|
||||
`\n${htmlExample}`;
|
||||
jsTemplate = '';
|
||||
}
|
||||
|
||||
// React templates
|
||||
if (isReact) {
|
||||
htmlTemplate = '<div id="root"></div>';
|
||||
jsTemplate =
|
||||
`import React from 'https://esm.sh/react@${reactVersion}';\n` +
|
||||
`import ReactDOM from 'https://esm.sh/react-dom@${reactVersion}';\n` +
|
||||
`import { setBasePath } from 'https://esm.sh/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/utilities/base-path';\n` +
|
||||
`\n` +
|
||||
`// Set the base path for Shoelace assets\n` +
|
||||
`setBasePath('https://esm.sh/@shoelace-style/shoelace@${shoelaceVersion}/${npmdir}/')\n` +
|
||||
`\n${convertModuleLinks(reactExample)}\n` +
|
||||
`\n` +
|
||||
`ReactDOM.render(<App />, document.getElementById('root'));`;
|
||||
}
|
||||
|
||||
// CSS templates
|
||||
cssTemplate =
|
||||
`@import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/themes/${
|
||||
isDark ? 'dark' : 'light'
|
||||
}.css';\n` +
|
||||
'\n' +
|
||||
'body {\n' +
|
||||
' font: 16px sans-serif;\n' +
|
||||
' background-color: var(--sl-color-neutral-0);\n' +
|
||||
' color: var(--sl-color-neutral-900);\n' +
|
||||
' padding: 1rem;\n' +
|
||||
'}';
|
||||
|
||||
// Docs: https://blog.codepen.io/documentation/prefill/
|
||||
const data = {
|
||||
title: '',
|
||||
description: '',
|
||||
tags: ['shoelace', 'web components'],
|
||||
editors,
|
||||
head: `<meta name="viewport" content="width=device-width">`,
|
||||
html_classes: `sl-theme-${isDark ? 'dark' : 'light'}`,
|
||||
css_external: ``,
|
||||
js_external: ``,
|
||||
js_module: true,
|
||||
js_pre_processor: isReact ? 'babel' : 'none',
|
||||
html: htmlTemplate,
|
||||
css: cssTemplate,
|
||||
js: jsTemplate
|
||||
};
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'data';
|
||||
input.value = JSON.stringify(data);
|
||||
form.append(input);
|
||||
|
||||
document.documentElement.append(form);
|
||||
form.submit();
|
||||
form.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Set the initial flavor
|
||||
window.addEventListener('turbo:load', syncFlavor);
|
||||
})();
|
||||
272
docs/assets/scripts/docs.js
Normal file
@@ -0,0 +1,272 @@
|
||||
//
|
||||
// Sidebar
|
||||
//
|
||||
// When the sidebar is hidden, we apply the inert attribute to prevent focus from reaching it. Due to the many states
|
||||
// the sidebar can have (e.g. static, hidden, expanded), we test for visibility by checking to see if it's placed
|
||||
// offscreen or not. Then, on resize/transition we make sure to update the attribute accordingly.
|
||||
//
|
||||
(() => {
|
||||
function getSidebar() {
|
||||
return document.getElementById('sidebar');
|
||||
}
|
||||
|
||||
function isSidebarOpen() {
|
||||
return document.documentElement.classList.contains('sidebar-open');
|
||||
}
|
||||
|
||||
function isSidebarVisible() {
|
||||
return getSidebar().getBoundingClientRect().x >= 0;
|
||||
}
|
||||
|
||||
function toggleSidebar(force) {
|
||||
const isOpen = typeof force === 'boolean' ? force : !isSidebarOpen();
|
||||
return document.documentElement.classList.toggle('sidebar-open', isOpen);
|
||||
}
|
||||
|
||||
function updateInert() {
|
||||
getSidebar().inert = !isSidebarVisible();
|
||||
}
|
||||
|
||||
// Toggle the menu
|
||||
document.addEventListener('click', event => {
|
||||
const menuToggle = event.target.closest('#menu-toggle');
|
||||
if (!menuToggle) return;
|
||||
toggleSidebar();
|
||||
});
|
||||
|
||||
// Update the sidebar's inert state when the window resizes and when the sidebar transitions
|
||||
window.addEventListener('resize', () => toggleSidebar(false));
|
||||
|
||||
document.addEventListener('transitionend', event => {
|
||||
const sidebar = event.target.closest('#sidebar');
|
||||
if (!sidebar) return;
|
||||
updateInert();
|
||||
});
|
||||
|
||||
// Close when a menu item is selected on mobile
|
||||
document.addEventListener('click', event => {
|
||||
const sidebar = event.target.closest('#sidebar');
|
||||
const link = event.target.closest('a');
|
||||
if (!sidebar || !link) return;
|
||||
|
||||
if (isSidebarOpen()) {
|
||||
toggleSidebar();
|
||||
}
|
||||
});
|
||||
|
||||
// Close when open and escape is pressed
|
||||
document.addEventListener('keydown', event => {
|
||||
if (event.key === 'Escape' && isSidebarOpen()) {
|
||||
event.stopImmediatePropagation();
|
||||
toggleSidebar();
|
||||
}
|
||||
});
|
||||
|
||||
// Close when clicking outside of the sidebar
|
||||
document.addEventListener('mousedown', event => {
|
||||
if (isSidebarOpen() & !event.target?.closest('#sidebar, #menu-toggle')) {
|
||||
event.stopImmediatePropagation();
|
||||
toggleSidebar();
|
||||
}
|
||||
});
|
||||
|
||||
updateInert();
|
||||
})();
|
||||
|
||||
//
|
||||
// Theme selector
|
||||
//
|
||||
(() => {
|
||||
function getTheme() {
|
||||
return localStorage.getItem('theme') || 'auto';
|
||||
}
|
||||
|
||||
function isDark() {
|
||||
if (theme === 'auto') {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
return theme === 'dark';
|
||||
}
|
||||
|
||||
function setTheme(newTheme) {
|
||||
theme = newTheme;
|
||||
localStorage.setItem('theme', theme);
|
||||
|
||||
// Update the UI
|
||||
updateSelection();
|
||||
|
||||
// Toggle the dark mode class
|
||||
document.documentElement.classList.toggle('sl-theme-dark', isDark());
|
||||
}
|
||||
|
||||
function updateSelection() {
|
||||
const menu = document.querySelector('#theme-selector sl-menu');
|
||||
if (!menu) return;
|
||||
[...menu.querySelectorAll('sl-menu-item')].map(item => (item.checked = item.getAttribute('value') === theme));
|
||||
}
|
||||
|
||||
let theme = getTheme();
|
||||
|
||||
// Selection is not preserved when changing page, so update when opening dropdown
|
||||
document.addEventListener('sl-show', event => {
|
||||
const themeSelector = event.target.closest('#theme-selector');
|
||||
if (!themeSelector) return;
|
||||
updateSelection();
|
||||
});
|
||||
|
||||
// Listen for selections
|
||||
document.addEventListener('sl-select', event => {
|
||||
const menu = event.target.closest('#theme-selector sl-menu');
|
||||
if (!menu) return;
|
||||
setTheme(event.detail.item.value);
|
||||
});
|
||||
|
||||
// Update the theme when the preference changes
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => setTheme(theme));
|
||||
|
||||
// Toggle with backslash
|
||||
document.addEventListener('keydown', event => {
|
||||
if (
|
||||
event.key === '\\' &&
|
||||
!event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))
|
||||
) {
|
||||
event.preventDefault();
|
||||
setTheme(isDark() ? 'light' : 'dark');
|
||||
}
|
||||
});
|
||||
|
||||
// Set the initial theme and sync the UI
|
||||
setTheme(theme);
|
||||
})();
|
||||
|
||||
//
|
||||
// Open details when printing
|
||||
//
|
||||
(() => {
|
||||
const detailsOpenOnPrint = new Set();
|
||||
|
||||
window.addEventListener('beforeprint', () => {
|
||||
detailsOpenOnPrint.clear();
|
||||
document.querySelectorAll('details').forEach(details => {
|
||||
if (details.open) {
|
||||
detailsOpenOnPrint.add(details);
|
||||
}
|
||||
details.open = true;
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('afterprint', () => {
|
||||
document.querySelectorAll('details').forEach(details => {
|
||||
details.open = detailsOpenOnPrint.has(details);
|
||||
});
|
||||
detailsOpenOnPrint.clear();
|
||||
});
|
||||
})();
|
||||
|
||||
//
|
||||
// Smooth links
|
||||
//
|
||||
(() => {
|
||||
document.addEventListener('click', event => {
|
||||
const link = event.target.closest('a');
|
||||
const id = (link?.hash ?? '').substr(1);
|
||||
const isFragment = link?.hasAttribute('href') && link?.getAttribute('href').startsWith('#');
|
||||
|
||||
if (!link || !isFragment || link.getAttribute('data-smooth-link') === 'false') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Scroll to the top
|
||||
if (link.hash === '') {
|
||||
event.preventDefault();
|
||||
window.scroll({ top: 0, behavior: 'smooth' });
|
||||
history.pushState(undefined, undefined, location.pathname);
|
||||
}
|
||||
|
||||
// Scroll to an id
|
||||
if (id) {
|
||||
const target = document.getElementById(id);
|
||||
|
||||
if (target) {
|
||||
event.preventDefault();
|
||||
window.scroll({ top: target.offsetTop, behavior: 'smooth' });
|
||||
history.pushState(undefined, undefined, `#${id}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
//
|
||||
// Table of Contents scrollspy
|
||||
//
|
||||
(() => {
|
||||
// This will be stale if its not a function.
|
||||
const getLinks = () => [...document.querySelectorAll('.content__toc a')];
|
||||
const linkTargets = new WeakMap();
|
||||
const visibleTargets = new WeakSet();
|
||||
const observer = new IntersectionObserver(handleIntersect, { rootMargin: '0px 0px' });
|
||||
let debounce;
|
||||
|
||||
function handleIntersect(entries) {
|
||||
entries.forEach(entry => {
|
||||
// Remember which targets are visible
|
||||
if (entry.isIntersecting) {
|
||||
visibleTargets.add(entry.target);
|
||||
} else {
|
||||
visibleTargets.delete(entry.target);
|
||||
}
|
||||
});
|
||||
|
||||
updateActiveLinks();
|
||||
}
|
||||
|
||||
function updateActiveLinks() {
|
||||
const links = getLinks();
|
||||
// Find the first visible target and activate the respective link
|
||||
links.find(link => {
|
||||
const target = linkTargets.get(link);
|
||||
|
||||
if (target && visibleTargets.has(target)) {
|
||||
links.forEach(el => el.classList.toggle('active', el === link));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// Observe link targets
|
||||
function observeLinks() {
|
||||
getLinks().forEach(link => {
|
||||
const hash = link.hash.slice(1);
|
||||
const target = hash ? document.querySelector(`.content__body #${hash}`) : null;
|
||||
|
||||
if (target) {
|
||||
linkTargets.set(link, target);
|
||||
observer.observe(target);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
observeLinks();
|
||||
|
||||
document.addEventListener('turbo:load', updateActiveLinks);
|
||||
document.addEventListener('turbo:load', observeLinks);
|
||||
})();
|
||||
|
||||
//
|
||||
// Show custom versions in the sidebar
|
||||
//
|
||||
(() => {
|
||||
function updateVersion() {
|
||||
const el = document.querySelector('.sidebar-version');
|
||||
if (!el) return;
|
||||
|
||||
if (location.hostname === 'next.shoelace.style') el.textContent = 'Next';
|
||||
if (location.hostname === 'localhost') el.textContent = 'Development';
|
||||
}
|
||||
|
||||
updateVersion();
|
||||
|
||||
document.addEventListener('turbo:load', updateVersion);
|
||||
})();
|
||||
376
docs/assets/scripts/search.js
Normal file
@@ -0,0 +1,376 @@
|
||||
(() => {
|
||||
// Append the search dialog to the body
|
||||
const siteSearch = document.createElement('div');
|
||||
const scrollbarWidth = Math.abs(window.innerWidth - document.documentElement.clientWidth);
|
||||
|
||||
siteSearch.classList.add('search');
|
||||
siteSearch.innerHTML = `
|
||||
<div class="search__overlay"></div>
|
||||
<dialog id="search-dialog" class="search__dialog">
|
||||
<div class="search__content">
|
||||
<div class="search__header">
|
||||
<div id="search-combobox" class="search__input-wrapper">
|
||||
<sl-icon name="search"></sl-icon>
|
||||
<input
|
||||
id="search-input"
|
||||
class="search__input"
|
||||
type="search"
|
||||
placeholder="Search"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
enterkeyhint="go"
|
||||
spellcheck="false"
|
||||
maxlength="100"
|
||||
role="combobox"
|
||||
aria-autocomplete="list"
|
||||
aria-expanded="true"
|
||||
aria-controls="search-listbox"
|
||||
aria-haspopup="listbox"
|
||||
aria-activedescendant
|
||||
>
|
||||
<button type="button" class="search__clear-button" aria-label="Clear entry" tabindex="-1" hidden>
|
||||
<sl-icon name="x-circle-fill"></sl-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search__body">
|
||||
<ul
|
||||
id="search-listbox"
|
||||
class="search__results"
|
||||
role="listbox"
|
||||
aria-label="Search results"
|
||||
></ul>
|
||||
<div class="search__empty">No matching pages</div>
|
||||
</div>
|
||||
<footer class="search__footer">
|
||||
<small><kbd>↑</kbd> <kbd>↓</kbd> Navigate</small>
|
||||
<small><kbd>↲</kbd> Select</small>
|
||||
<small><kbd>Esc</kbd> Close</small>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
`;
|
||||
|
||||
const overlay = siteSearch.querySelector('.search__overlay');
|
||||
const dialog = siteSearch.querySelector('.search__dialog');
|
||||
const input = siteSearch.querySelector('.search__input');
|
||||
const clearButton = siteSearch.querySelector('.search__clear-button');
|
||||
const results = siteSearch.querySelector('.search__results');
|
||||
const version = document.documentElement.getAttribute('data-shoelace-version');
|
||||
const key = `search_${version}`;
|
||||
const searchDebounce = 50;
|
||||
const animationDuration = 150;
|
||||
let isShowing = false;
|
||||
let searchTimeout;
|
||||
let searchIndex;
|
||||
let map;
|
||||
|
||||
const loadSearchIndex = new Promise(resolve => {
|
||||
const cache = localStorage.getItem(key);
|
||||
const wait = 'requestIdleCallback' in window ? requestIdleCallback : requestAnimationFrame;
|
||||
|
||||
// Cleanup older search indices (everything before this version)
|
||||
try {
|
||||
const items = { ...localStorage };
|
||||
|
||||
Object.keys(items).forEach(k => {
|
||||
if (key > k) {
|
||||
localStorage.removeItem(k);
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
// Look for a cached index
|
||||
try {
|
||||
if (cache) {
|
||||
const data = JSON.parse(cache);
|
||||
|
||||
searchIndex = window.lunr.Index.load(data.searchIndex);
|
||||
map = data.map;
|
||||
|
||||
return resolve();
|
||||
}
|
||||
} catch {
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
// Wait until idle to fetch the index
|
||||
wait(() => {
|
||||
fetch('/assets/search.json')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (!window.lunr) {
|
||||
console.error('The Lunr search client has not yet been loaded.');
|
||||
}
|
||||
|
||||
searchIndex = window.lunr.Index.load(data.searchIndex);
|
||||
map = data.map;
|
||||
|
||||
// Cache the search index for this version
|
||||
if (version) {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(data));
|
||||
} catch (err) {
|
||||
console.warn(`Unable to cache the search index: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function show() {
|
||||
isShowing = true;
|
||||
document.body.append(siteSearch);
|
||||
document.body.classList.add('search-visible');
|
||||
document.body.style.setProperty('--docs-search-scroll-lock-size', `${scrollbarWidth}px`);
|
||||
clearButton.hidden = true;
|
||||
requestAnimationFrame(() => input.focus());
|
||||
updateResults();
|
||||
|
||||
dialog.showModal();
|
||||
|
||||
await Promise.all([
|
||||
dialog.animate(
|
||||
[
|
||||
{ opacity: 0, transform: 'scale(.9)', transformOrigin: 'top' },
|
||||
{ opacity: 1, transform: 'scale(1)', transformOrigin: 'top' }
|
||||
],
|
||||
{ duration: animationDuration }
|
||||
).finished,
|
||||
overlay.animate([{ opacity: 0 }, { opacity: 1 }], { duration: animationDuration }).finished
|
||||
]);
|
||||
|
||||
dialog.addEventListener('mousedown', handleMouseDown);
|
||||
dialog.addEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
|
||||
async function hide() {
|
||||
isShowing = false;
|
||||
|
||||
await Promise.all([
|
||||
dialog.animate(
|
||||
[
|
||||
{ opacity: 1, transform: 'scale(1)', transformOrigin: 'top' },
|
||||
{ opacity: 0, transform: 'scale(.9)', transformOrigin: 'top' }
|
||||
],
|
||||
{ duration: animationDuration }
|
||||
).finished,
|
||||
overlay.animate([{ opacity: 1 }, { opacity: 0 }], { duration: animationDuration }).finished
|
||||
]);
|
||||
|
||||
dialog.close();
|
||||
|
||||
input.blur(); // otherwise Safari will scroll to the bottom of the page on close
|
||||
input.value = '';
|
||||
document.body.classList.remove('search-visible');
|
||||
document.body.style.removeProperty('--docs-search-scroll-lock-size');
|
||||
siteSearch.remove();
|
||||
updateResults();
|
||||
|
||||
dialog.removeEventListener('mousedown', handleMouseDown);
|
||||
dialog.removeEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
|
||||
function handleInput() {
|
||||
clearButton.hidden = input.value === '';
|
||||
|
||||
// Debounce search queries
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => updateResults(input.value), searchDebounce);
|
||||
}
|
||||
|
||||
function handleClear() {
|
||||
clearButton.hidden = true;
|
||||
input.value = '';
|
||||
input.focus();
|
||||
updateResults();
|
||||
}
|
||||
|
||||
function handleMouseDown(event) {
|
||||
if (!event.target.closest('.search__content')) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(event) {
|
||||
// Close when pressing escape
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault(); // prevent <dialog> from closing immediately so it can animate
|
||||
event.stopImmediatePropagation();
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle keyboard selections
|
||||
if (['ArrowDown', 'ArrowUp', 'Home', 'End', 'Enter'].includes(event.key)) {
|
||||
event.preventDefault();
|
||||
|
||||
const currentEl = results.querySelector('[data-selected="true"]');
|
||||
const items = [...results.querySelectorAll('li')];
|
||||
const index = items.indexOf(currentEl);
|
||||
let nextEl;
|
||||
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowUp':
|
||||
nextEl = items[Math.max(0, index - 1)];
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
nextEl = items[Math.min(items.length - 1, index + 1)];
|
||||
break;
|
||||
case 'Home':
|
||||
nextEl = items[0];
|
||||
break;
|
||||
case 'End':
|
||||
nextEl = items[items.length - 1];
|
||||
break;
|
||||
case 'Enter':
|
||||
currentEl?.querySelector('a')?.click();
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the selected item
|
||||
items.forEach(item => {
|
||||
if (item === nextEl) {
|
||||
input.setAttribute('aria-activedescendant', item.id);
|
||||
item.setAttribute('data-selected', 'true');
|
||||
nextEl.scrollIntoView({ block: 'nearest' });
|
||||
} else {
|
||||
item.setAttribute('data-selected', 'false');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updateResults(query = '') {
|
||||
try {
|
||||
await loadSearchIndex;
|
||||
|
||||
const hasQuery = query.length > 0;
|
||||
const searchTerms = query
|
||||
.split(' ')
|
||||
.map((term, index, arr) => {
|
||||
// Search API: https://lunrjs.com/guides/searching.html
|
||||
if (index === arr.length - 1) {
|
||||
// The last term is not mandatory and 1x fuzzy. We also duplicate it with a wildcard to match partial words
|
||||
// as the user types.
|
||||
return `${term}~1 ${term}*`;
|
||||
} else {
|
||||
// All other terms are mandatory and 1x fuzzy
|
||||
return `+${term}~1`;
|
||||
}
|
||||
})
|
||||
.join(' ');
|
||||
const matches = hasQuery ? searchIndex.search(searchTerms) : [];
|
||||
const hasResults = hasQuery && matches.length > 0;
|
||||
|
||||
siteSearch.classList.toggle('search--has-results', hasQuery && hasResults);
|
||||
siteSearch.classList.toggle('search--no-results', hasQuery && !hasResults);
|
||||
|
||||
input.setAttribute('aria-activedescendant', '');
|
||||
results.innerHTML = '';
|
||||
|
||||
matches.forEach((match, index) => {
|
||||
const page = map[match.ref];
|
||||
const li = document.createElement('li');
|
||||
const a = document.createElement('a');
|
||||
const displayTitle = page.title ?? '';
|
||||
const displayDescription = page.description ?? '';
|
||||
const displayUrl = page.url.replace(/^\//, '').replace(/\/$/, '');
|
||||
let icon = 'file-text';
|
||||
|
||||
a.setAttribute('role', 'option');
|
||||
a.setAttribute('id', `search-result-item-${match.ref}`);
|
||||
|
||||
if (page.url.includes('getting-started/')) {
|
||||
icon = 'lightbulb';
|
||||
}
|
||||
if (page.url.includes('resources/')) {
|
||||
icon = 'book';
|
||||
}
|
||||
if (page.url.includes('components/')) {
|
||||
icon = 'puzzle';
|
||||
}
|
||||
if (page.url.includes('tokens/')) {
|
||||
icon = 'palette2';
|
||||
}
|
||||
if (page.url.includes('utilities/')) {
|
||||
icon = 'wrench';
|
||||
}
|
||||
if (page.url.includes('tutorials/')) {
|
||||
icon = 'joystick';
|
||||
}
|
||||
|
||||
li.classList.add('search__result');
|
||||
li.setAttribute('role', 'option');
|
||||
li.setAttribute('id', `search-result-item-${match.ref}`);
|
||||
li.setAttribute('data-selected', index === 0 ? 'true' : 'false');
|
||||
|
||||
a.href = page.url;
|
||||
a.innerHTML = `
|
||||
<div class="search__result-icon" aria-hidden="true">
|
||||
<sl-icon name="${icon}"></sl-icon>
|
||||
</div>
|
||||
<div class="search__result__details">
|
||||
<div class="search__result-title"></div>
|
||||
<div class="search__result-description"></div>
|
||||
<div class="search__result-url"></div>
|
||||
</div>
|
||||
`;
|
||||
a.querySelector('.search__result-title').textContent = displayTitle;
|
||||
a.querySelector('.search__result-description').textContent = displayDescription;
|
||||
a.querySelector('.search__result-url').textContent = displayUrl;
|
||||
|
||||
li.appendChild(a);
|
||||
results.appendChild(li);
|
||||
});
|
||||
} catch {
|
||||
// Ignore query errors as the user types
|
||||
}
|
||||
}
|
||||
|
||||
// Show the search dialog when clicking on data-plugin="search"
|
||||
document.addEventListener('click', event => {
|
||||
const searchButton = event.target.closest('[data-plugin="search"]');
|
||||
if (searchButton) {
|
||||
show();
|
||||
}
|
||||
});
|
||||
|
||||
// Show the search dialog when slash (or CMD+K) is pressed and focus is not inside a form element
|
||||
document.addEventListener('keydown', event => {
|
||||
if (
|
||||
!isShowing &&
|
||||
(event.key === '/' || (event.key === 'k' && (event.metaKey || event.ctrlKey))) &&
|
||||
!event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))
|
||||
) {
|
||||
event.preventDefault();
|
||||
show();
|
||||
}
|
||||
});
|
||||
|
||||
// Purge cache when we press CMD+CTRL+R
|
||||
document.addEventListener('keydown', event => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key === 'r') {
|
||||
localStorage.clear();
|
||||
}
|
||||
});
|
||||
|
||||
input.addEventListener('input', handleInput);
|
||||
clearButton.addEventListener('click', handleClear);
|
||||
|
||||
// Close when a result is selected
|
||||
results.addEventListener('click', event => {
|
||||
if (event.target.closest('a')) {
|
||||
hide();
|
||||
}
|
||||
});
|
||||
})();
|
||||
29
docs/assets/scripts/turbo.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as Turbo from 'https://cdn.jsdelivr.net/npm/@hotwired/turbo@7.3.0/+esm';
|
||||
|
||||
(() => {
|
||||
if (!window.scrollPositions) {
|
||||
window.scrollPositions = {};
|
||||
}
|
||||
|
||||
function preserveScroll() {
|
||||
document.querySelectorAll('[data-preserve-scroll').forEach(element => {
|
||||
scrollPositions[element.id] = element.scrollTop;
|
||||
});
|
||||
}
|
||||
|
||||
function restoreScroll(event) {
|
||||
document.querySelectorAll('[data-preserve-scroll').forEach(element => {
|
||||
element.scrollTop = scrollPositions[element.id];
|
||||
});
|
||||
|
||||
if (event.detail && event.detail.newBody) {
|
||||
event.detail.newBody.querySelectorAll('[data-preserve-scroll').forEach(element => {
|
||||
element.scrollTop = scrollPositions[element.id];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('turbo:before-cache', preserveScroll);
|
||||
window.addEventListener('turbo:before-render', restoreScroll);
|
||||
window.addEventListener('turbo:render', restoreScroll);
|
||||
})();
|
||||
@@ -1,11 +1,12 @@
|
||||
.code-block {
|
||||
/* Interactive code blocks */
|
||||
.code-preview {
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
background-color: var(--sl-color-neutral-50);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.code-block__preview {
|
||||
.code-preview__preview {
|
||||
position: relative;
|
||||
border: solid 1px var(--sl-color-neutral-200);
|
||||
border-bottom: none;
|
||||
@@ -18,7 +19,7 @@
|
||||
}
|
||||
|
||||
/* Block the preview while dragging to prevent iframes from intercepting drag events */
|
||||
.code-block__preview--dragging:after {
|
||||
.code-preview__preview--dragging:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -29,7 +30,7 @@
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.code-block__resizer {
|
||||
.code-preview__resizer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -44,35 +45,34 @@
|
||||
border-left: solid 1px var(--sl-color-neutral-200);
|
||||
border-top-right-radius: 3px;
|
||||
cursor: ew-resize;
|
||||
transition: 250ms background-color;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.code-block__preview {
|
||||
.code-preview__preview {
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.code-block__resizer {
|
||||
.code-preview__resizer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.code-block__source {
|
||||
.code-preview__source {
|
||||
border: solid 1px var(--sl-color-neutral-200);
|
||||
border-bottom: none;
|
||||
border-radius: 0 !important;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.code-block--expanded .code-block__source {
|
||||
.code-preview--expanded .code-preview__source {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.code-block__source pre {
|
||||
.code-preview__source pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.code-block__buttons {
|
||||
.code-preview__buttons {
|
||||
position: relative;
|
||||
border: solid 1px var(--sl-color-neutral-200);
|
||||
border-bottom-left-radius: 3px;
|
||||
@@ -80,7 +80,7 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.code-block__button {
|
||||
.code-preview__button {
|
||||
flex: 0 0 auto;
|
||||
height: 2.5rem;
|
||||
min-width: 2.5rem;
|
||||
@@ -96,57 +96,53 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.code-block__button:not(:last-of-type) {
|
||||
.code-preview__button:not(:last-of-type) {
|
||||
border-right: solid 1px var(--sl-color-neutral-200);
|
||||
}
|
||||
|
||||
.code-block__button--html,
|
||||
.code-block__button--react {
|
||||
.code-preview__button--html,
|
||||
.code-preview__button--react {
|
||||
width: 70px;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.code-block__button--selected {
|
||||
.code-preview__button--selected {
|
||||
font-weight: 700;
|
||||
color: var(--sl-color-primary-600);
|
||||
}
|
||||
|
||||
.code-block__button--codepen {
|
||||
.code-preview__button--codepen {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
width: 6rem;
|
||||
}
|
||||
|
||||
.code-block__button:first-of-type {
|
||||
.code-preview__button:first-of-type {
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
|
||||
.code-block__button:last-of-type {
|
||||
.code-preview__button:last-of-type {
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
.code-block__button:hover,
|
||||
.code-block__button:active {
|
||||
.code-preview__button:hover,
|
||||
.code-preview__button:active {
|
||||
box-shadow: 0 0 0 1px var(--sl-color-primary-400);
|
||||
border-right-color: transparent;
|
||||
background-color: var(--sl-color-primary-50);
|
||||
color: var(--sl-color-primary-700);
|
||||
color: var(--sl-color-primary-600);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.code-block__button:focus-visible {
|
||||
.code-preview__button:focus-visible {
|
||||
outline: none;
|
||||
color: var(--sl-color-primary-600);
|
||||
border-color: var(--sl-color-primary-400);
|
||||
border-right-color: transparent;
|
||||
background-color: var(--sl-color-primary-50);
|
||||
box-shadow: 0 0 0 1px var(--sl-color-primary-400), var(--sl-focus-ring);
|
||||
outline: var(--sl-focus-ring);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.code-block__toggle {
|
||||
.code-preview__toggle {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
@@ -155,74 +151,23 @@
|
||||
width: 100%;
|
||||
color: var(--sl-color-neutral-600);
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.code-block__toggle svg {
|
||||
.code-preview__toggle svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
.code-block--expanded .code-block__toggle svg {
|
||||
.code-preview--expanded .code-preview__toggle svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* Copy button styles */
|
||||
.markdown-section .docsify-copy-code-button {
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
background-color: var(--sl-color-neutral-500);
|
||||
border-radius: var(--sl-border-radius-medium);
|
||||
font-family: var(--sl-font-sans);
|
||||
font-size: var(--sl-font-size-x-small);
|
||||
font-weight: var(--sl-font-weight-semibold);
|
||||
text-transform: uppercase;
|
||||
padding: 8px;
|
||||
user-select: none;
|
||||
transition: 0.1s all;
|
||||
}
|
||||
|
||||
.markdown-section .docsify-copy-code-button.copied {
|
||||
animation: pulse 0.75s;
|
||||
--pulse-color: var(--sl-color-neutral-600);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 var(--pulse-color);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 0.5rem transparent;
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.markdown-section .docsify-copy-code-button .label {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.markdown-section .docsify-copy-code-button .success,
|
||||
.markdown-section .docsify-copy-code-button .error {
|
||||
/* We can apply data-flavor="html|react" to any element on the page to toggle it when the flavor changes */
|
||||
.flavor-html [data-flavor]:not([data-flavor='html']) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.markdown-section .docsify-copy-code-button:focus-visible {
|
||||
box-shadow: var(--sl-focus-ring);
|
||||
}
|
||||
|
||||
.markdown-section .docsify-copy-code-button:active {
|
||||
background-color: var(--sl-color-neutral-600);
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
/* We can apply data-flavor="html|react" to any element on the page to toggle it when the user's flavor changes */
|
||||
body.flavor-html [data-flavor]:not([data-flavor='html']) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.flavor-react [data-flavor]:not([data-flavor='react']) {
|
||||
.flavor-react [data-flavor]:not([data-flavor='react']) {
|
||||
display: none;
|
||||
}
|
||||
347
docs/assets/styles/search.css
Normal file
@@ -0,0 +1,347 @@
|
||||
/* Search plugin */
|
||||
:root,
|
||||
:root.sl-theme-dark {
|
||||
--docs-search-box-background: var(--sl-color-neutral-0);
|
||||
--docs-search-box-border-width: 1px;
|
||||
--docs-search-box-border-color: var(--sl-color-neutral-300);
|
||||
--docs-search-box-color: var(--sl-color-neutral-600);
|
||||
--docs-search-dialog-background: var(--sl-color-neutral-0);
|
||||
--docs-search-border-width: var(--docs-border-width);
|
||||
--docs-search-border-color: var(--docs-border-color);
|
||||
--docs-search-text-color: var(--sl-color-neutral-900);
|
||||
--docs-search-text-color-muted: var(--sl-color-neutral-500);
|
||||
--docs-search-font-weight-normal: var(--sl-font-weight-normal);
|
||||
--docs-search-font-weight-semibold: var(--sl-font-weight-semibold);
|
||||
--docs-search-border-radius: calc(2 * var(--docs-border-radius));
|
||||
--docs-search-accent-color: var(--sl-color-primary-600);
|
||||
--docs-search-icon-color: var(--sl-color-neutral-500);
|
||||
--docs-search-icon-color-active: var(--sl-color-neutral-600);
|
||||
--docs-search-shadow: var(--docs-shadow-x-large);
|
||||
--docs-search-result-background-hover: var(--sl-color-neutral-100);
|
||||
--docs-search-result-color-hover: var(--sl-color-neutral-900);
|
||||
--docs-search-result-background-active: var(--sl-color-primary-600);
|
||||
--docs-search-result-color-active: var(--sl-color-neutral-0);
|
||||
--docs-search-focus-ring: var(--sl-focus-ring);
|
||||
--docs-search-overlay-background: rgb(0 0 0 / 0.33);
|
||||
}
|
||||
|
||||
:root.sl-theme-dark {
|
||||
--docs-search-overlay-background: rgb(71 71 71 / 0.33);
|
||||
}
|
||||
|
||||
body.search-visible {
|
||||
padding-right: var(--docs-search-scroll-lock-size) !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* Search box */
|
||||
.search-box {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-radius: 9999px;
|
||||
background: var(--docs-search-box-background);
|
||||
border: solid var(--docs-search-box-border-width) var(--docs-search-box-border-color);
|
||||
font: inherit;
|
||||
color: var(--docs-search-box-color);
|
||||
padding: 0.75rem 1rem;
|
||||
margin: var(--sl-spacing-large) 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-box span {
|
||||
flex: 1 1 auto;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
text-align: left;
|
||||
line-height: 1;
|
||||
margin: 0 0.75rem;
|
||||
}
|
||||
|
||||
.search-box:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search-box:focus-visible {
|
||||
outline: var(--docs-search-focus-ring);
|
||||
}
|
||||
|
||||
/* Site search */
|
||||
.search {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.search[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search__overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: var(--docs-search-overlay-background);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.search__dialog {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: none;
|
||||
max-height: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search__dialog:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search__dialog::backdrop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Fixes an iOS Safari 16.4 bug that draws the parent element's border radius incorrectly when showing/hiding results */
|
||||
.search__header {
|
||||
background-color: var(--docs-search-dialog-background);
|
||||
border-radius: var(--docs-search-border-radius);
|
||||
}
|
||||
|
||||
.search--has-results .search__header {
|
||||
border-top-left-radius: var(--docs-search-border-radius);
|
||||
border-top-right-radius: var(--docs-search-border-radius);
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.search__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
max-height: calc(100vh - 20rem);
|
||||
background-color: var(--docs-search-dialog-background);
|
||||
border-radius: var(--docs-search-border-radius);
|
||||
box-shadow: var(--docs-search-shadow);
|
||||
padding: 0;
|
||||
margin: 10rem auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.search__content {
|
||||
max-width: calc(100% - 2rem);
|
||||
max-height: calc(90svh);
|
||||
margin: 4vh 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.search__input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search__input-wrapper sl-icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
flex: 0 0 auto;
|
||||
color: var(--docs-search-icon-color);
|
||||
margin: 0 1.5rem;
|
||||
}
|
||||
|
||||
.search__clear-button {
|
||||
display: flex;
|
||||
background: none;
|
||||
border: none;
|
||||
font: inherit;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search__clear-button[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search__clear-button:active sl-icon {
|
||||
color: var(--docs-search-icon-color-active);
|
||||
}
|
||||
|
||||
.search__input {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
border: none;
|
||||
font: inherit;
|
||||
font-size: 1.5rem;
|
||||
font-weight: var(--docs-search-font-weight-normal);
|
||||
color: var(--docs-search-text-color);
|
||||
background: transparent;
|
||||
padding: 1rem 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search__input::placeholder {
|
||||
color: var(--docs-search-text-color-muted);
|
||||
}
|
||||
|
||||
.search__input::-webkit-search-decoration,
|
||||
.search__input::-webkit-search-cancel-button,
|
||||
.search__input::-webkit-search-results-button,
|
||||
.search__input::-webkit-search-results-decoration {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search__input:focus,
|
||||
.search__input:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search__body {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.search--has-results .search__body {
|
||||
border-top: solid var(--docs-search-border-width) var(--docs-search-border-color);
|
||||
}
|
||||
|
||||
.search__results {
|
||||
display: none;
|
||||
line-height: 1.2;
|
||||
list-style: none;
|
||||
padding: 0.5rem 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search--has-results .search__results {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.search__results a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
padding: 0.5rem 1.5rem;
|
||||
}
|
||||
|
||||
.search__results a:focus-visible {
|
||||
outline: var(--docs-search-focus-ring);
|
||||
}
|
||||
|
||||
.search__results li a:hover,
|
||||
.search__results li a:hover small {
|
||||
background-color: var(--docs-search-result-background-hover);
|
||||
color: var(--docs-search-result-color-hover);
|
||||
}
|
||||
|
||||
.search__results li[data-selected='true'] a,
|
||||
.search__results li[data-selected='true'] a * {
|
||||
outline: none;
|
||||
background-color: var(--docs-search-result-background-active);
|
||||
color: var(--docs-search-result-color-active);
|
||||
}
|
||||
|
||||
.search__results h3 {
|
||||
font-weight: var(--docs-search-font-weight-semibold);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search__results small {
|
||||
display: block;
|
||||
color: var(--docs-search-text-color-muted);
|
||||
}
|
||||
|
||||
.search__result {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search__result a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.search__result-icon {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
color: var(--docs-search-text-color-muted);
|
||||
}
|
||||
|
||||
.search__result-icon sl-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.search__result__details {
|
||||
width: calc(100% - 3rem);
|
||||
}
|
||||
|
||||
.search__result-title,
|
||||
.search__result-description,
|
||||
.search__result-url {
|
||||
max-width: 400px;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.search__result-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: var(--docs-search-font-weight-semibold);
|
||||
color: var(--docs-search-accent-color);
|
||||
}
|
||||
|
||||
.search__result-description {
|
||||
font-size: 0.875rem;
|
||||
color: var(--docs-search-text-color);
|
||||
}
|
||||
|
||||
.search__result-url {
|
||||
font-size: 0.875rem;
|
||||
color: var(--docs-search-text-color-muted);
|
||||
}
|
||||
|
||||
.search__empty {
|
||||
display: none;
|
||||
border-top: solid var(--docs-search-border-width) var(--docs-search-border-color);
|
||||
text-align: center;
|
||||
color: var(--docs-search-text-color-muted);
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.search--no-results .search__empty {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.search__footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
border-top: solid var(--docs-search-border-width) var(--docs-search-border-color);
|
||||
border-bottom-left-radius: inherit;
|
||||
border-bottom-right-radius: inherit;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.search__footer small {
|
||||
color: var(--docs-search-text-color-muted);
|
||||
}
|
||||
|
||||
.search__footer small kbd:last-of-type {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.search__footer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
# Checkbox
|
||||
|
||||
[component-header:sl-checkbox]
|
||||
|
||||
Checkboxes allow the user to toggle an option on or off.
|
||||
|
||||
```html preview
|
||||
<sl-checkbox>Checkbox</sl-checkbox>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlCheckbox } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlCheckbox>Checkbox</SlCheckbox>
|
||||
);
|
||||
```
|
||||
|
||||
?> This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
|
||||
## Examples
|
||||
|
||||
### Checked
|
||||
|
||||
Use the `checked` attribute to activate the checkbox.
|
||||
|
||||
```html preview
|
||||
<sl-checkbox checked>Checked</sl-checkbox>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlCheckbox } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlCheckbox checked>Checked</SlCheckbox>
|
||||
);
|
||||
```
|
||||
|
||||
### Indeterminate
|
||||
|
||||
Use the `indeterminate` attribute to make the checkbox indeterminate.
|
||||
|
||||
```html preview
|
||||
<sl-checkbox indeterminate>Indeterminate</sl-checkbox>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlCheckbox } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlCheckbox indeterminate>Indeterminate</SlCheckbox>
|
||||
);
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` attribute to disable the checkbox.
|
||||
|
||||
```html preview
|
||||
<sl-checkbox disabled>Disabled</sl-checkbox>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlCheckbox } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlCheckbox disabled>Disabled</SlCheckbox>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-checkbox]
|
||||
@@ -1,101 +0,0 @@
|
||||
# Color Picker
|
||||
|
||||
[component-header:sl-color-picker]
|
||||
|
||||
Color pickers allow the user to select a color.
|
||||
|
||||
```html preview
|
||||
<sl-color-picker></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlColorPicker />
|
||||
);
|
||||
```
|
||||
|
||||
?> This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
|
||||
## Examples
|
||||
|
||||
### 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`.
|
||||
|
||||
```html preview
|
||||
<sl-color-picker opacity></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlColorPicker opacity />
|
||||
);
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
To prevent users from toggling the format themselves, add the `no-format-toggle` attribute.
|
||||
|
||||
```html preview
|
||||
<sl-color-picker format="hex" value="#4a90e2"></sl-color-picker>
|
||||
<sl-color-picker format="rgb" value="rgb(80, 227, 194)"></sl-color-picker>
|
||||
<sl-color-picker format="hsl" value="hsl(290, 87%, 47%)"></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlColorPicker format="hex" value="#4a90e2" />
|
||||
<SlColorPicker format="rgb" value="rgb(80, 227, 194)" />
|
||||
<SlColorPicker format="hsl" value="hsl(290, 87%, 47%)" />
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Sizes
|
||||
|
||||
Use the `size` attribute to change the color picker's trigger size.
|
||||
|
||||
```html preview
|
||||
<sl-color-picker size="small"></sl-color-picker>
|
||||
<sl-color-picker size="medium"></sl-color-picker>
|
||||
<sl-color-picker size="large"></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlColorPicker size="small" />
|
||||
<SlColorPicker size="medium" />
|
||||
<SlColorPicker size="large" />
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Inline
|
||||
|
||||
The color picker can be rendered inline instead of in a dropdown using the `inline` attribute.
|
||||
|
||||
```html preview
|
||||
<sl-color-picker inline></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlColorPicker } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlColorPicker inline />
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-color-picker]
|
||||
@@ -1,126 +0,0 @@
|
||||
# Format Bytes
|
||||
|
||||
[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>
|
||||
<sl-input type="number" value="1000" label="Number to Format" style="max-width: 180px;"></sl-input>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const container = document.querySelector('.format-bytes-overview');
|
||||
const formatter = container.querySelector('sl-format-bytes');
|
||||
const input = container.querySelector('sl-input');
|
||||
|
||||
input.addEventListener('sl-input', () => formatter.value = input.value || 0);
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
```jsx react
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
SlButton,
|
||||
SlFormatBytes,
|
||||
SlInput
|
||||
} from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => {
|
||||
const [value, setValue] = useState(1000);
|
||||
|
||||
return (
|
||||
<>
|
||||
The file is <SlFormatBytes value={value} /> in size.
|
||||
<br /><br />
|
||||
<SlInput
|
||||
type="number"
|
||||
value={value}
|
||||
label="Number to Format"
|
||||
style={{ maxWidth: '180px' }}
|
||||
onSlInput={event => setValue(event.target.value)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Formatting Bytes
|
||||
|
||||
Set the `value` attribute to a number to get the value in bytes.
|
||||
|
||||
```html preview
|
||||
<sl-format-bytes value="12"></sl-format-bytes><br>
|
||||
<sl-format-bytes value="1200"></sl-format-bytes><br>
|
||||
<sl-format-bytes value="1200000"></sl-format-bytes><br>
|
||||
<sl-format-bytes value="1200000000"></sl-format-bytes>
|
||||
```
|
||||
|
||||
|
||||
```jsx react
|
||||
import { SlFormatBytes } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlFormatBytes value="12" /><br />
|
||||
<SlFormatBytes value="1200" /><br />
|
||||
<SlFormatBytes value="1200000" /><br />
|
||||
<SlFormatBytes value="1200000000" />
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Formatting Bits
|
||||
|
||||
To get the value in bits, set the `unit` attribute to `bit`.
|
||||
|
||||
```html preview
|
||||
<sl-format-bytes value="12" unit="bit"></sl-format-bytes><br>
|
||||
<sl-format-bytes value="1200" unit="bit"></sl-format-bytes><br>
|
||||
<sl-format-bytes value="1200000" unit="bit"></sl-format-bytes><br>
|
||||
<sl-format-bytes value="1200000000" unit="bit"></sl-format-bytes>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlFormatBytes } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlFormatBytes value="12" unit="bit" /><br />
|
||||
<SlFormatBytes value="1200" unit="bit" /><br />
|
||||
<SlFormatBytes value="1200000" unit="bit" /><br />
|
||||
<SlFormatBytes value="1200000000" unit="bit" />
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Localization
|
||||
|
||||
Use the `lang` attribute to set the number formatting locale.
|
||||
|
||||
```html preview
|
||||
<sl-format-bytes value="12" lang="de"></sl-format-bytes><br>
|
||||
<sl-format-bytes value="1200" lang="de"></sl-format-bytes><br>
|
||||
<sl-format-bytes value="1200000" lang="de"></sl-format-bytes><br>
|
||||
<sl-format-bytes value="1200000000" lang="de"></sl-format-bytes>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlFormatBytes } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlFormatBytes value="12" lang="de" /><br />
|
||||
<SlFormatBytes value="1200" lang="de" /><br />
|
||||
<SlFormatBytes value="1200000" lang="de" /><br />
|
||||
<SlFormatBytes value="1200000000" lang="de" />
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-format-bytes]
|
||||
@@ -1,51 +0,0 @@
|
||||
# Image Comparer
|
||||
|
||||
[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
|
||||
<sl-image-comparer>
|
||||
<img slot="before" src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80&sat=-100&bri=-5" alt="Grayscale version of kittens in a basket looking around.">
|
||||
<img slot="after" src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80" alt="Color version of kittens in a basket looking around.">
|
||||
</sl-image-comparer>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlImageComparer } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlImageComparer>
|
||||
<img slot="before" src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80&sat=-100&bri=-5" alt="Grayscale version of kittens in a basket looking around." />
|
||||
<img slot="after" src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80" alt="Color version of kittens in a basket looking around." />
|
||||
</SlImageComparer>
|
||||
);
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Initial Position
|
||||
|
||||
Use the `position` attribute to set the initial position of the slider. This is a percentage from `0` to `100`.
|
||||
|
||||
```html preview
|
||||
<sl-image-comparer position="25">
|
||||
<img slot="before" src="https://images.unsplash.com/photo-1520903074185-8eca362b3dce?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1200&q=80" alt="A person sitting on bricks wearing untied boots.">
|
||||
<img slot="after" src="https://images.unsplash.com/photo-1520640023173-50a135e35804?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80" alt="A person sitting on a yellow curb tying shoelaces on a boot.">
|
||||
</sl-image-comparer>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlImageComparer } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlImageComparer position={25}>
|
||||
<img slot="before" src="https://images.unsplash.com/photo-1520903074185-8eca362b3dce?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1200&q=80" alt="A person sitting on bricks wearing untied boots." />
|
||||
<img slot="after" src="https://images.unsplash.com/photo-1520640023173-50a135e35804?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80" alt="A person sitting on a yellow curb tying shoelaces on a boot." />
|
||||
</SlImageComparer>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-image-comparer]
|
||||
@@ -1,283 +0,0 @@
|
||||
# Input
|
||||
|
||||
[component-header:sl-input]
|
||||
|
||||
Inputs collect data from the user.
|
||||
|
||||
```html preview
|
||||
<sl-input></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlInput />
|
||||
);
|
||||
```
|
||||
|
||||
?> This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
|
||||
## Examples
|
||||
|
||||
### Placeholders
|
||||
|
||||
Use the `placeholder` attribute to add a placeholder.
|
||||
|
||||
```html preview
|
||||
<sl-input placeholder="Type something"></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlInput placeholder="Type something" />
|
||||
);
|
||||
```
|
||||
|
||||
### Clearable
|
||||
|
||||
Add the `clearable` attribute to add a clear button when the input has content.
|
||||
|
||||
```html preview
|
||||
<sl-input placeholder="Clearable" clearable></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlInput placeholder="Clearable" clearable />
|
||||
);
|
||||
```
|
||||
|
||||
### Toggle Password
|
||||
|
||||
Add the `toggle-password` attribute to add a toggle button that will show the password when activated.
|
||||
|
||||
```html preview
|
||||
<sl-input type="password" placeholder="Password Toggle" size="small" toggle-password></sl-input>
|
||||
<br>
|
||||
<sl-input type="password" placeholder="Password Toggle" size="medium" toggle-password></sl-input>
|
||||
<br>
|
||||
<sl-input type="password" placeholder="Password Toggle" size="large" toggle-password></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlInput type="password" placeholder="Password Toggle" size="small" toggle-password />
|
||||
<br />
|
||||
<SlInput type="password" placeholder="Password Toggle" size="medium" toggle-password />
|
||||
<br />
|
||||
<SlInput type="password" placeholder="Password Toggle" size="large" toggle-password />
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Filled Inputs
|
||||
|
||||
Add the `filled` attribute to draw a filled input.
|
||||
|
||||
```html preview
|
||||
<sl-input placeholder="Type something" filled></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlInput placeholder="Type something" filled />
|
||||
);
|
||||
```
|
||||
|
||||
### Pill
|
||||
|
||||
Use the `pill` attribute to give inputs rounded edges.
|
||||
|
||||
```html preview
|
||||
<sl-input placeholder="Small" size="small" pill></sl-input>
|
||||
<br>
|
||||
<sl-input placeholder="Medium" size="medium" pill></sl-input>
|
||||
<br>
|
||||
<sl-input placeholder="Large" size="large" pill></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlInput placeholder="Small" size="small" pill />
|
||||
<br />
|
||||
<SlInput placeholder="Medium" size="medium" pill />
|
||||
<br />
|
||||
<SlInput placeholder="Large" size="large" pill />
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Input Types
|
||||
|
||||
The `type` attribute controls the type of input the browser renders.
|
||||
|
||||
```html preview
|
||||
<sl-input type="email" Placeholder="Email"></sl-input>
|
||||
<br>
|
||||
<sl-input type="number" Placeholder="Number"></sl-input>
|
||||
<br>
|
||||
<sl-input type="date" Placeholder="Date"></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlInput type="email" Placeholder="Email" />
|
||||
<br />
|
||||
<SlInput type="number" Placeholder="Number" />
|
||||
<br />
|
||||
<SlInput type="date" Placeholder="Date" />
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
```html preview
|
||||
<sl-input placeholder="Small" size="small">
|
||||
<sl-icon name="house" slot="prefix"></sl-icon>
|
||||
<sl-icon name="chat" slot="suffix"></sl-icon>
|
||||
</sl-input>
|
||||
<br>
|
||||
<sl-input placeholder="Medium" size="medium">
|
||||
<sl-icon name="house" slot="prefix"></sl-icon>
|
||||
<sl-icon name="chat" slot="suffix"></sl-icon>
|
||||
</sl-input>
|
||||
<br>
|
||||
<sl-input placeholder="Large" size="large">
|
||||
<sl-icon name="house" slot="prefix"></sl-icon>
|
||||
<sl-icon name="chat" slot="suffix"></sl-icon>
|
||||
</sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlIcon, SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlInput placeholder="Small" size="small">
|
||||
<SlIcon name="house" slot="prefix"></SlIcon>
|
||||
<SlIcon name="chat" slot="suffix"></SlIcon>
|
||||
</SlInput>
|
||||
<br />
|
||||
<SlInput placeholder="Medium" size="medium">
|
||||
<SlIcon name="house" slot="prefix"></SlIcon>
|
||||
<SlIcon name="chat" slot="suffix"></SlIcon>
|
||||
</SlInput>
|
||||
<br />
|
||||
<SlInput placeholder="Large" size="large">
|
||||
<SlIcon name="house" slot="prefix"></SlIcon>
|
||||
<SlIcon name="chat" slot="suffix"></SlIcon>
|
||||
</SlInput>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Labels
|
||||
|
||||
Use the `label` attribute to give the input an accessible label. For labels that contain HTML, use the `label` slot instead.
|
||||
|
||||
```html preview
|
||||
<sl-input label="What is your name?"></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlIcon, SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlInput label="What is your name?" />
|
||||
);
|
||||
```
|
||||
|
||||
### Help Text
|
||||
|
||||
Add descriptive help text to an input with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.
|
||||
|
||||
```html preview
|
||||
<sl-input
|
||||
label="Nickname"
|
||||
help-text="What would you like people to call you?"
|
||||
></sl-input>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlIcon, SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlInput
|
||||
label="Nickname"
|
||||
help-text="What would you like people to call you?"
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-input]
|
||||
@@ -1,49 +0,0 @@
|
||||
# Menu
|
||||
|
||||
[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
|
||||
<sl-menu style="max-width: 200px; border: solid 1px var(--sl-panel-border-color); background: var(--sl-panel-background-color); border-radius: var(--sl-border-radius-medium);">
|
||||
<sl-menu-item value="undo">Undo</sl-menu-item>
|
||||
<sl-menu-item value="redo">Redo</sl-menu-item>
|
||||
<sl-divider></sl-divider>
|
||||
<sl-menu-item value="cut">Cut</sl-menu-item>
|
||||
<sl-menu-item value="copy">Copy</sl-menu-item>
|
||||
<sl-menu-item value="paste">Paste</sl-menu-item>
|
||||
<sl-menu-item value="delete">Delete</sl-menu-item>
|
||||
</sl-menu>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import {
|
||||
SlDivider,
|
||||
SlMenu,
|
||||
SlMenuItem
|
||||
} from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlMenu
|
||||
style={{
|
||||
maxWidth: '200px',
|
||||
border: 'solid 1px var(--sl-panel-border-color)',
|
||||
borderRadius: 'var(--sl-border-radius-medium)'
|
||||
}}
|
||||
>
|
||||
<SlMenuItem value="undo">Undo</SlMenuItem>
|
||||
<SlMenuItem value="redo">Redo</SlMenuItem>
|
||||
<SlDivider />
|
||||
<SlMenuItem value="cut">Cut</SlMenuItem>
|
||||
<SlMenuItem value="copy">Copy</SlMenuItem>
|
||||
<SlMenuItem value="paste">Paste</SlMenuItem>
|
||||
<SlMenuItem value="delete">Delete</SlMenuItem>
|
||||
</SlMenu>
|
||||
);
|
||||
```
|
||||
|
||||
?> Menus are intended for system menus (dropdown menus, select menus, context menus, etc.). They should not be mistaken for navigation menus which serve a different purpose and have a different semantic meaning. If you're building navigation, use `<nav>` and `<a>` elements instead.
|
||||
|
||||
[component-metadata:sl-menu]
|
||||
@@ -1,178 +0,0 @@
|
||||
# Progress Ring
|
||||
|
||||
[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>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlProgressRing } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlProgressRing value="25" />
|
||||
);
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Size
|
||||
|
||||
Use the `--size` custom property to set the diameter of the progress ring.
|
||||
|
||||
```html preview
|
||||
<sl-progress-ring value="50" style="--size: 200px;"></sl-progress-ring>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlProgressRing } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlProgressRing
|
||||
value="50"
|
||||
style={{ '--size': '200px' }}
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
### Track Width
|
||||
|
||||
Use the `--track-width` custom property to set the width of the progress ring's track.
|
||||
|
||||
```html preview
|
||||
<sl-progress-ring value="50" style="--track-width: 10px;"></sl-progress-ring>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlProgressRing } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlProgressRing
|
||||
value="50"
|
||||
style={{ '--track-width': '10px' }}
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
### Colors
|
||||
|
||||
To change the color, use the `--track-color` and `--indicator-color` custom properties.
|
||||
|
||||
```html preview
|
||||
<sl-progress-ring
|
||||
value="50"
|
||||
style="
|
||||
--track-color: pink;
|
||||
--indicator-color: deeppink;
|
||||
"
|
||||
></sl-progress-ring>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlProgressRing } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlProgressRing
|
||||
value="50"
|
||||
style={{
|
||||
'--track-color': 'pink',
|
||||
'--indicator-color': 'deeppink'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
### Labels
|
||||
|
||||
Use the `label` attribute to label the progress ring and tell assistive devices how to announce it.
|
||||
|
||||
```html preview
|
||||
<sl-progress-ring value="50" label="Upload progress"></sl-progress-ring>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlProgressRing } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlProgressRing
|
||||
value="50"
|
||||
label="Upload progress"
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
### Showing Values
|
||||
|
||||
Use the default slot to show a label inside the progress ring.
|
||||
|
||||
```html preview
|
||||
<sl-progress-ring value="50" class="progress-ring-values" style="margin-bottom: .5rem;">50%</sl-progress-ring>
|
||||
|
||||
<br>
|
||||
|
||||
<sl-button circle><sl-icon name="dash"></sl-icon></sl-button>
|
||||
<sl-button circle><sl-icon name="plus"></sl-icon></sl-button>
|
||||
|
||||
<script>
|
||||
const progressRing = document.querySelector('.progress-ring-values');
|
||||
const subtractButton = progressRing.nextElementSibling.nextElementSibling;
|
||||
const addButton = subtractButton.nextElementSibling;
|
||||
|
||||
addButton.addEventListener('click', () => {
|
||||
const value = Math.min(100, progressRing.value + 10);
|
||||
progressRing.value = value;
|
||||
progressRing.textContent = `${value}%`;
|
||||
});
|
||||
|
||||
subtractButton.addEventListener('click', () => {
|
||||
const value = Math.max(0, progressRing.value - 10)
|
||||
progressRing.value = value;
|
||||
progressRing.textContent = `${value}%`;
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
SlButton,
|
||||
SlIcon,
|
||||
SlProgressRing
|
||||
} from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => {
|
||||
const [value, setValue] = useState(50);
|
||||
|
||||
function adjustValue(amount) {
|
||||
let newValue = value + amount;
|
||||
if (newValue < 0) newValue = 0;
|
||||
if (newValue > 100) newValue = 100;
|
||||
setValue(newValue);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SlProgressRing
|
||||
value={value}
|
||||
style={{ marginBottom: '.5rem' }}
|
||||
>
|
||||
{value}%
|
||||
</SlProgressRing>
|
||||
|
||||
<br />
|
||||
|
||||
<SlButton circle onClick={() => adjustValue(-10)}>
|
||||
<SlIcon name="dash" />
|
||||
</SlButton>
|
||||
|
||||
<SlButton circle onClick={() => adjustValue(10)}>
|
||||
<SlIcon name="plus" />
|
||||
</SlButton>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
[component-metadata:sl-progress-ring]
|
||||
@@ -1,52 +0,0 @@
|
||||
# Radio Group
|
||||
|
||||
[component-header:sl-radio-group]
|
||||
|
||||
Radio Groups are used to group multiple radios so they function as a single control.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio value="1" checked>Option 1</sl-radio>
|
||||
<sl-radio value="2">Option 2</sl-radio>
|
||||
<sl-radio value="3">Option 3</sl-radio>
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadio value="1" checked>Option 1</SlRadio>
|
||||
<SlRadio value="2">Option 2</SlRadio>
|
||||
<SlRadio value="3">Option 3</SlRadio>
|
||||
</SlRadioGroup>
|
||||
);
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Showing the Fieldset
|
||||
|
||||
You can show a fieldset and legend that wraps the radio group using the `fieldset` attribute.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option" fieldset>
|
||||
<sl-radio value="1" checked>Option 1</sl-radio>
|
||||
<sl-radio value="2">Option 2</sl-radio>
|
||||
<sl-radio value="3">Option 3</sl-radio>
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRadioGroup label="Select an option" fieldset>
|
||||
<SlRadio value="1" checked>Option 1</SlRadio>
|
||||
<SlRadio value="2">Option 2</SlRadio>
|
||||
<SlRadio value="3">Option 3</SlRadio>
|
||||
</SlRadioGroup>
|
||||
);
|
||||
```
|
||||
[component-metadata:sl-radio-group]
|
||||
@@ -1,59 +0,0 @@
|
||||
# Radio
|
||||
|
||||
[component-header:sl-radio]
|
||||
|
||||
Radios allow the user to select one option from a group of many.
|
||||
|
||||
Radios are designed to be used with [radio groups](/components/radio-group). As such, all of the examples on this page utilize them to demonstrate their correct usage.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio value="1" checked>Option 1</sl-radio>
|
||||
<sl-radio value="2">Option 2</sl-radio>
|
||||
<sl-radio value="3">Option 3</sl-radio>
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadio value="1" checked>Option 1</SlRadio>
|
||||
<SlRadio value="2">Option 2</SlRadio>
|
||||
<SlRadio value="3">Option 3</SlRadio>
|
||||
</SlRadioGroup>
|
||||
);
|
||||
```
|
||||
|
||||
?> This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
|
||||
## Examples
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` attribute to disable a radio.
|
||||
|
||||
```html preview
|
||||
<sl-radio-group label="Select an option">
|
||||
<sl-radio value="1" checked>Option 1</sl-radio>
|
||||
<sl-radio value="2">Option 2</sl-radio>
|
||||
<sl-radio value="3">Option 3</sl-radio>
|
||||
<sl-radio value="4" disabled>Disabled</sl-radio>
|
||||
</sl-radio-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRadioGroup label="Select an option">
|
||||
<SlRadio value="1" checked>Option 1</SlRadio>
|
||||
<SlRadio value="2">Option 2</SlRadio>
|
||||
<SlRadio value="3">Option 3</SlRadio>
|
||||
<SlRadio value="4" disabled>Disabled</SlRadio>
|
||||
</SlRadioGroup>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-radio]
|
||||
@@ -1,180 +0,0 @@
|
||||
# Range
|
||||
|
||||
[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>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRange />
|
||||
);
|
||||
```
|
||||
|
||||
?> This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
|
||||
## Examples
|
||||
|
||||
### Min, Max, and Step
|
||||
|
||||
Use the `min` and `max` attributes to set the range's minimum and maximum values, respectively. The `step` attribute determines the value's interval when increasing and decreasing.
|
||||
|
||||
```html preview
|
||||
<sl-range min="0" max="10" step="1"></sl-range>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRange min={0} max={10} step={1} />
|
||||
);
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` attribute to disable a slider.
|
||||
|
||||
```html preview
|
||||
<sl-range disabled></sl-range>
|
||||
```
|
||||
|
||||
|
||||
```jsx react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRange disabled />
|
||||
);
|
||||
```
|
||||
|
||||
### Tooltip Placement
|
||||
|
||||
By default, the tooltip is shown on top. Set `tooltip` to `bottom` to show it below the slider.
|
||||
|
||||
```html preview
|
||||
<sl-range tooltip="bottom"></sl-range>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRange tooltip="bottom" />
|
||||
);
|
||||
```
|
||||
|
||||
### Disable the Tooltip
|
||||
|
||||
To disable the tooltip, set `tooltip` to `none`.
|
||||
|
||||
```html preview
|
||||
<sl-range tooltip="none"></sl-range>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRange tooltip="none" />
|
||||
);
|
||||
```
|
||||
|
||||
### Custom Track Colors
|
||||
|
||||
You can customize the active and inactive portions of the track using the `--track-color-active` and `--track-color-inactive` custom properties.
|
||||
|
||||
```html preview
|
||||
<sl-range style="
|
||||
--track-color-active: var(--sl-color-primary-600);
|
||||
--track-color-inactive: var(--sl-color-primary-100);
|
||||
"></sl-range>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRange
|
||||
style={{
|
||||
'--track-color-active': 'var(--sl-color-primary-600)',
|
||||
'--track-color-inactive': 'var(--sl-color-primary-200)'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
### Custom Tooltip Formatter
|
||||
|
||||
You can change the tooltip's content by setting the `tooltipFormatter` property to a function that accepts the range's value as an argument.
|
||||
|
||||
```html preview
|
||||
<sl-range min="0" max="100" step="1" class="range-with-custom-formatter"></sl-range>
|
||||
|
||||
<script>
|
||||
const range = document.querySelector('.range-with-custom-formatter');
|
||||
range.tooltipFormatter = value => `Total - ${value}%`;
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRange
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
tooltipFormatter={value => `Total - ${value}%`}
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
### Labels
|
||||
|
||||
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>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRange label="Volume" min={0} max={100} />
|
||||
);
|
||||
```
|
||||
|
||||
### Help Text
|
||||
|
||||
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>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRange } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRange
|
||||
label="Volume"
|
||||
help-text="Controls the volume of the current song."
|
||||
min={0}
|
||||
max={100}
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-range]
|
||||
@@ -1,153 +0,0 @@
|
||||
# Rating
|
||||
|
||||
[component-header:sl-rating]
|
||||
|
||||
Ratings give users a way to quickly view and provide feedback.
|
||||
|
||||
```html preview
|
||||
<sl-rating></sl-rating>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRating />
|
||||
);
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Maximum Value
|
||||
|
||||
Ratings are 0-5 by default. To change the maximum possible value, use the `max` attribute.
|
||||
|
||||
```html preview
|
||||
<sl-rating max="3"></sl-rating>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRating max={3} />
|
||||
);
|
||||
```
|
||||
|
||||
### Precision
|
||||
|
||||
Use the `precision` attribute to let users select fractional ratings.
|
||||
|
||||
```html preview
|
||||
<sl-rating precision="0.5" value="2.5"></sl-rating>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRating precision={0.5} value={2.5} />
|
||||
);
|
||||
```
|
||||
|
||||
## Symbol Sizes
|
||||
|
||||
Set the `--symbol-size` custom property to adjust the size.
|
||||
|
||||
```html preview
|
||||
<sl-rating style="--symbol-size: 2rem;"></sl-rating>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRating style={{ '--symbol-size': '2rem' }} />
|
||||
);
|
||||
```
|
||||
|
||||
### Readonly
|
||||
|
||||
Use the `readonly` attribute to display a rating that users can't change.
|
||||
|
||||
```html preview
|
||||
<sl-rating readonly value="3"></sl-rating>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRating readonly value={3} />
|
||||
);
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disable` attribute to disable the rating.
|
||||
|
||||
```html preview
|
||||
<sl-rating disabled value="3"></sl-rating>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRating disabled value={3} />
|
||||
);
|
||||
```
|
||||
|
||||
### Custom Icons
|
||||
|
||||
```html preview
|
||||
<sl-rating class="rating-hearts" style="--symbol-color-active: #ff4136;"></sl-rating>
|
||||
|
||||
<script>
|
||||
const rating = document.querySelector('.rating-hearts');
|
||||
rating.getSymbol = () => '<sl-icon name="heart-fill"></sl-icon>';
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import '@shoelace-style/shoelace/dist/components/icon/icon';
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlRating
|
||||
getSymbol={() => '<sl-icon name="heart-fill"></sl-icon>'}
|
||||
style={{ '--symbol-color-active': '#ff4136' }}
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
### Value-based Icons
|
||||
|
||||
```html preview
|
||||
<sl-rating class="rating-emojis"></sl-rating>
|
||||
|
||||
<script>
|
||||
const rating = document.querySelector('.rating-emojis');
|
||||
|
||||
rating.getSymbol = (value) => {
|
||||
const icons = ['emoji-angry', 'emoji-frown', 'emoji-expressionless', 'emoji-smile', 'emoji-laughing'];
|
||||
return `<sl-icon name="${icons[value - 1]}"></sl-icon>`;
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import '@shoelace-style/shoelace/dist/components/icon/icon';
|
||||
import { SlRating } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
function getSymbol(value) {
|
||||
const icons = ['emoji-angry', 'emoji-frown', 'emoji-expressionless', 'emoji-smile', 'emoji-laughing'];
|
||||
return `<sl-icon name="${icons[value - 1]}"></sl-icon>`;
|
||||
}
|
||||
|
||||
const App = () => (
|
||||
<SlRating getSymbol={getSymbol} />
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-rating]
|
||||
@@ -1,73 +0,0 @@
|
||||
# Responsive Media
|
||||
|
||||
[component-header:sl-responsive-media]
|
||||
|
||||
Displays media in the desired aspect ratio.
|
||||
|
||||
You can slot in any [replaced element](https://developer.mozilla.org/en-US/docs/Web/CSS/Replaced_element), including `<iframe>`, `<img>`, and `<video>`. As the element's width changes, its height will resize proportionally. Only one element should be slotted into the container. The default aspect ratio is `16:9`.
|
||||
|
||||
```html preview
|
||||
<sl-responsive-media>
|
||||
<img src="https://images.unsplash.com/photo-1541427468627-a89a96e5ca1d?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1800&q=80" alt="A train riding through autumn foliage with mountains in the distance.">
|
||||
</sl-responsive-media>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlResponsiveMedia } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlResponsiveMedia>
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1541427468627-a89a96e5ca1d?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1800&q=80"
|
||||
alt="A train riding through autumn foliage with mountains in the distance."
|
||||
/>
|
||||
</SlResponsiveMedia>
|
||||
);
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Responsive Images
|
||||
|
||||
The following image maintains a `4:3` aspect ratio as its container is resized.
|
||||
|
||||
```html preview
|
||||
<sl-responsive-media aspect-ratio="4:3">
|
||||
<img src="https://images.unsplash.com/photo-1473186578172-c141e6798cf4?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1800&q=80" alt="Two blue chairs on a sandy beach.">
|
||||
</sl-responsive-media>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlResponsiveMedia } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlResponsiveMedia aspect-ratio="4:3">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1473186578172-c141e6798cf4?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1800&q=80"
|
||||
alt="Two blue chairs on a sandy beach."
|
||||
/>
|
||||
</SlResponsiveMedia>
|
||||
);
|
||||
```
|
||||
|
||||
### Responsive Videos
|
||||
|
||||
The following video is embedded using an `iframe` and maintains a `16:9` aspect ratio as its container is resized.
|
||||
|
||||
```html preview
|
||||
<sl-responsive-media aspect-ratio="16:9">
|
||||
<iframe src="https://player.vimeo.com/video/1053647?title=0&byline=0&portrait=0" frameborder="0" allow="autoplay; fullscreen" allowfullscreen></iframe>
|
||||
</sl-responsive-media>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlResponsiveMedia } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlResponsiveMedia aspect-ratio="16:9">
|
||||
<iframe src="https://player.vimeo.com/video/1053647?title=0&byline=0&portrait=0" frameborder="0" allow="autoplay; fullscreen" allowfullscreen />
|
||||
</SlResponsiveMedia>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-responsive-media]
|
||||
@@ -1,459 +0,0 @@
|
||||
# Select
|
||||
|
||||
[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-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlDivider, SlMenuItem, 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>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
|
||||
?> This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
|
||||
## Examples
|
||||
|
||||
### Placeholders
|
||||
|
||||
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-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlMenuItem, 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>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
|
||||
### Clearable
|
||||
|
||||
Use the `clearable` attribute to make the control clearable.
|
||||
|
||||
```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>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlMenuItem, 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>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
|
||||
### Filled Selects
|
||||
|
||||
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-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlMenuItem, 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>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
|
||||
### Pill
|
||||
|
||||
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-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlMenuItem, 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>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
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-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlMenuItem, 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>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
|
||||
### Multiple
|
||||
|
||||
To allow multiple options to be selected, use the `multiple` attribute. It's a good practice to use `clearable` when this option is enabled. When using this option, the `value` property will be an array of strings instead of a string.
|
||||
|
||||
```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>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlDivider, SlMenuItem, 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>
|
||||
);
|
||||
```
|
||||
|
||||
?> When using the `multiple` attribute, the `value` property will be an array instead of a string. To set it in your HTML, obtain a reference to the element and set `value` to an array of strings.
|
||||
|
||||
### Grouping Options
|
||||
|
||||
Options can be grouped visually using menu labels and dividers.
|
||||
|
||||
```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>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlDivider, SlMenuItem, SlMenuLabel, 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>
|
||||
);
|
||||
```
|
||||
|
||||
### Sizes
|
||||
|
||||
Use the `size` attribute to change a select's size.
|
||||
|
||||
```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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlMenuItem, 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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Selecting Options Programmatically
|
||||
|
||||
The `value` property is bound to the current selection. As the selection changes, so will the value. To programmatically manage the selection, update the `value` property.
|
||||
|
||||
```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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Labels
|
||||
|
||||
Use the `label` attribute to give the select an accessible label. For labels that contain HTML, use the `label` slot instead.
|
||||
|
||||
```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-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlMenuItem, 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>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
|
||||
### Help Text
|
||||
|
||||
Add descriptive help text to a select with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.
|
||||
|
||||
```html preview
|
||||
<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-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlMenuItem, 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>
|
||||
</SlSelect>
|
||||
);
|
||||
```
|
||||
|
||||
### Prefix & Suffix Icons
|
||||
|
||||
Use the `prefix` and `suffix` slots to add icons.
|
||||
|
||||
```html preview
|
||||
<sl-select placeholder="Small" size="small">
|
||||
<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-select>
|
||||
<br>
|
||||
<sl-select placeholder="Medium" size="medium">
|
||||
<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-select>
|
||||
<br>
|
||||
<sl-select placeholder="Large" size="large">
|
||||
<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-select>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlIcon, SlMenuItem, 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>
|
||||
</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>
|
||||
</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>
|
||||
</SlSelect>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-select]
|
||||
@@ -1,77 +0,0 @@
|
||||
# Switch
|
||||
|
||||
[component-header:sl-switch]
|
||||
|
||||
Switches allow the user to toggle an option on or off.
|
||||
|
||||
```html preview
|
||||
<sl-switch>Switch</sl-switch>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSwitch>Switch</SlSwitch>
|
||||
);
|
||||
```
|
||||
|
||||
?> This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
|
||||
## Examples
|
||||
|
||||
### Checked
|
||||
|
||||
Use the `checked` attribute to activate the switch.
|
||||
|
||||
```html preview
|
||||
<sl-switch checked>Checked</sl-switch>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSwitch checked>Checked</SlSwitch>
|
||||
);
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` attribute to disable the switch.
|
||||
|
||||
```html preview
|
||||
<sl-switch disabled>Disabled</sl-switch>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSwitch disabled>Disabled</SlSwitch>
|
||||
);
|
||||
```
|
||||
|
||||
### Custom Size
|
||||
|
||||
Use the available custom properties to make the switch a different size.
|
||||
|
||||
```html preview
|
||||
<sl-switch style="--width: 80px; --height: 32px; --thumb-size: 26px;"></sl-switch>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlSwitch } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlSwitch
|
||||
style={{
|
||||
'--width': '80px',
|
||||
'--height': '32px',
|
||||
'--thumb-size': '26px'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-switch]
|
||||
@@ -1,29 +0,0 @@
|
||||
# Tab
|
||||
|
||||
[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>
|
||||
<sl-tab closable>Closable</sl-tab>
|
||||
<sl-tab disabled>Disabled</sl-tab>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlTab } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlTab>Tab</SlTab>
|
||||
<SlTab active>Active</SlTab>
|
||||
<SlTab closable>Closable</SlTab>
|
||||
<SlTab disabled>Disabled</SlTab>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
?> Additional demonstrations can be found in the [tab group examples](/components/tab-group).
|
||||
|
||||
[component-metadata:sl-tab]
|
||||
@@ -1,184 +0,0 @@
|
||||
# Textarea
|
||||
|
||||
[component-header:sl-textarea]
|
||||
|
||||
Textareas collect data from the user and allow multiple lines of text.
|
||||
|
||||
```html preview
|
||||
<sl-textarea></sl-textarea>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlTextarea />
|
||||
);
|
||||
```
|
||||
|
||||
?> This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
|
||||
## Examples
|
||||
|
||||
### Rows
|
||||
|
||||
Use the `rows` attribute to change the number of text rows that get shown.
|
||||
|
||||
```html preview
|
||||
<sl-textarea rows="2"></sl-textarea>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlTextarea rows={2} />
|
||||
);
|
||||
```
|
||||
|
||||
### Placeholders
|
||||
|
||||
Use the `placeholder` attribute to add a placeholder.
|
||||
|
||||
```html preview
|
||||
<sl-textarea placeholder="Type something"></sl-textarea>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlTextarea placeholder="Type something" />
|
||||
);
|
||||
```
|
||||
|
||||
### Filled Textareas
|
||||
|
||||
Add the `filled` attribute to draw a filled textarea.
|
||||
|
||||
```html preview
|
||||
<sl-textarea placeholder="Type something" filled></sl-textarea>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlTextarea placeholder="Type something" filled />
|
||||
);
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` attribute to disable a textarea.
|
||||
|
||||
```html preview
|
||||
<sl-textarea placeholder="Textarea" disabled></sl-textarea>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlTextarea placeholder="Textarea" disabled />
|
||||
);
|
||||
```
|
||||
|
||||
### Sizes
|
||||
|
||||
Use the `size` attribute to change a textarea's size.
|
||||
|
||||
```html preview
|
||||
<sl-textarea placeholder="Small" size="small"></sl-textarea>
|
||||
<br>
|
||||
<sl-textarea placeholder="Medium" size="medium"></sl-textarea>
|
||||
<br>
|
||||
<sl-textarea placeholder="Large" size="large"></sl-textarea>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlTextarea placeholder="Small" size="small"></SlTextarea>
|
||||
<br />
|
||||
<SlTextarea placeholder="Medium" size="medium"></SlTextarea>
|
||||
<br />
|
||||
<SlTextarea placeholder="Large" size="large"></SlTextarea>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Labels
|
||||
|
||||
Use the `label` attribute to give the textarea an accessible label. For labels that contain HTML, use the `label` slot instead.
|
||||
|
||||
```html preview
|
||||
<sl-textarea label="Comments"></sl-textarea>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlTextarea label="Comments" />
|
||||
);
|
||||
```
|
||||
|
||||
### Help Text
|
||||
|
||||
Add descriptive help text to a textarea with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.
|
||||
|
||||
```html preview
|
||||
<sl-textarea
|
||||
label="Feedback"
|
||||
help-text="Please tell us what you think."
|
||||
>
|
||||
</sl-textarea>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlTextarea
|
||||
label="Feedback"
|
||||
help-text="Please tell us what you think."
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
### Prevent Resizing
|
||||
|
||||
By default, textareas can be resized vertically by the user. To prevent resizing, set the `resize` attribute to `none`.
|
||||
|
||||
```html preview
|
||||
<sl-textarea resize="none"></sl-textarea>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlTextarea resize="none" />
|
||||
);
|
||||
```
|
||||
|
||||
### Expand with Content
|
||||
|
||||
Textareas will automatically resize to expand to fit their content when `resize` is set to `auto`.
|
||||
|
||||
```html preview
|
||||
<sl-textarea resize="auto"></sl-textarea>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlTextarea } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => (
|
||||
<SlTextarea resize="auto" />
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-textarea]
|
||||
239
docs/eleventy.config.cjs
Normal file
@@ -0,0 +1,239 @@
|
||||
/* eslint-disable no-invalid-this */
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const lunr = require('lunr');
|
||||
const { capitalCase } = require('change-case');
|
||||
const { JSDOM } = require('jsdom');
|
||||
const { customElementsManifest, getAllComponents } = require('./_utilities/cem.cjs');
|
||||
const shoelaceFlavoredMarkdown = require('./_utilities/markdown.cjs');
|
||||
const activeLinks = require('./_utilities/active-links.cjs');
|
||||
const anchorHeadings = require('./_utilities/anchor-headings.cjs');
|
||||
const codePreviews = require('./_utilities/code-previews.cjs');
|
||||
const copyCodeButtons = require('./_utilities/copy-code-buttons.cjs');
|
||||
const externalLinks = require('./_utilities/external-links.cjs');
|
||||
const highlightCodeBlocks = require('./_utilities/highlight-code.cjs');
|
||||
const tableOfContents = require('./_utilities/table-of-contents.cjs');
|
||||
const prettier = require('./_utilities/prettier.cjs');
|
||||
const scrollingTables = require('./_utilities/scrolling-tables.cjs');
|
||||
const typography = require('./_utilities/typography.cjs');
|
||||
const replacer = require('./_utilities/replacer.cjs');
|
||||
|
||||
const assetsDir = 'assets';
|
||||
const cdndir = 'cdn';
|
||||
const npmdir = 'dist';
|
||||
const allComponents = getAllComponents();
|
||||
let hasBuiltSearchIndex = false;
|
||||
|
||||
module.exports = function (eleventyConfig) {
|
||||
//
|
||||
// Global data
|
||||
//
|
||||
eleventyConfig.addGlobalData('baseUrl', 'https://shoelace.style/'); // the production URL
|
||||
eleventyConfig.addGlobalData('layout', 'default'); // make 'default' the default layout
|
||||
eleventyConfig.addGlobalData('toc', true); // enable the table of contents
|
||||
eleventyConfig.addGlobalData('meta', {
|
||||
title: 'Shoelace',
|
||||
description: 'A forward-thinking library of web components.',
|
||||
image: 'images/og-image.png',
|
||||
version: customElementsManifest.package.version,
|
||||
components: allComponents,
|
||||
cdndir,
|
||||
npmdir
|
||||
});
|
||||
|
||||
//
|
||||
// Layout aliases
|
||||
//
|
||||
eleventyConfig.addLayoutAlias('default', 'default.njk');
|
||||
|
||||
//
|
||||
// Copy assets
|
||||
//
|
||||
eleventyConfig.addPassthroughCopy(assetsDir);
|
||||
eleventyConfig.setServerPassthroughCopyBehavior('passthrough'); // emulates passthrough copy during --serve
|
||||
|
||||
//
|
||||
// Functions
|
||||
//
|
||||
|
||||
// Generates a URL relative to the site's root
|
||||
eleventyConfig.addNunjucksGlobal('rootUrl', (value = '', absolute = false) => {
|
||||
value = path.join('/', value);
|
||||
return absolute ? new URL(value, eleventyConfig.globalData.baseUrl).toString() : value;
|
||||
});
|
||||
|
||||
// Generates a URL relative to the site's asset directory
|
||||
eleventyConfig.addNunjucksGlobal('assetUrl', (value = '', absolute = false) => {
|
||||
value = path.join(`/${assetsDir}`, value);
|
||||
return absolute ? new URL(value, eleventyConfig.globalData.baseUrl).toString() : value;
|
||||
});
|
||||
|
||||
// Fetches a specific component's metadata
|
||||
eleventyConfig.addNunjucksGlobal('getComponent', tagName => {
|
||||
const component = allComponents.find(c => c.tagName === tagName);
|
||||
if (!component) {
|
||||
throw new Error(
|
||||
`Unable to find a component called "${tagName}". Make sure the file name is the same as the component's tag ` +
|
||||
`name (minus the sl- prefix).`
|
||||
);
|
||||
}
|
||||
return component;
|
||||
});
|
||||
|
||||
//
|
||||
// Custom markdown syntaxes
|
||||
//
|
||||
eleventyConfig.setLibrary('md', shoelaceFlavoredMarkdown);
|
||||
|
||||
//
|
||||
// Filters
|
||||
//
|
||||
eleventyConfig.addFilter('markdown', content => {
|
||||
return shoelaceFlavoredMarkdown.render(content);
|
||||
});
|
||||
|
||||
eleventyConfig.addFilter('markdownInline', content => {
|
||||
return shoelaceFlavoredMarkdown.renderInline(content);
|
||||
});
|
||||
|
||||
eleventyConfig.addFilter('classNameToComponentName', className => {
|
||||
let name = capitalCase(className.replace(/^Sl/, ''));
|
||||
if (name === 'Qr Code') name = 'QR Code'; // manual override
|
||||
return name;
|
||||
});
|
||||
|
||||
eleventyConfig.addFilter('removeSlPrefix', tagName => {
|
||||
return tagName.replace(/^sl-/, '');
|
||||
});
|
||||
|
||||
//
|
||||
// Transforms
|
||||
//
|
||||
eleventyConfig.addTransform('html-transform', function (content) {
|
||||
// Parse the template and get a Document object
|
||||
const doc = new JSDOM(content, {
|
||||
// We must set a default URL so links are parsed with a hostname. Let's use a bogus TLD so we can easily
|
||||
// identify which ones are internal and which ones are external.
|
||||
url: `https://internal/`
|
||||
}).window.document;
|
||||
|
||||
// DOM transforms
|
||||
activeLinks(doc, { pathname: this.page.url });
|
||||
anchorHeadings(doc, {
|
||||
within: '#content .content__body',
|
||||
levels: ['h2', 'h3', 'h4', 'h5']
|
||||
});
|
||||
tableOfContents(doc, {
|
||||
levels: ['h2', 'h3'],
|
||||
container: '#content .content__toc > ul',
|
||||
within: '#content .content__body'
|
||||
});
|
||||
codePreviews(doc);
|
||||
externalLinks(doc, { target: '_blank' });
|
||||
highlightCodeBlocks(doc);
|
||||
scrollingTables(doc);
|
||||
copyCodeButtons(doc); // must be after codePreviews + highlightCodeBlocks
|
||||
typography(doc, '#content');
|
||||
replacer(doc, [
|
||||
{ pattern: '%VERSION%', replacement: customElementsManifest.package.version },
|
||||
{ pattern: '%CDNDIR%', replacement: cdndir },
|
||||
{ pattern: '%NPMDIR%', replacement: npmdir }
|
||||
]);
|
||||
|
||||
// Serialize the Document object to an HTML string and prepend the doctype
|
||||
content = `<!DOCTYPE html>\n${doc.documentElement.outerHTML}`;
|
||||
|
||||
// String transforms
|
||||
content = prettier(content);
|
||||
|
||||
return content;
|
||||
});
|
||||
|
||||
//
|
||||
// Build a search index
|
||||
//
|
||||
eleventyConfig.on('eleventy.after', ({ results }) => {
|
||||
// We only want to build the search index on the first run so all pages get indexed.
|
||||
if (hasBuiltSearchIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
const map = {};
|
||||
const searchIndexFilename = path.join(eleventyConfig.dir.output, assetsDir, 'search.json');
|
||||
const lunrInput = path.resolve('../node_modules/lunr/lunr.min.js');
|
||||
const lunrOutput = path.join(eleventyConfig.dir.output, assetsDir, 'scripts/lunr.js');
|
||||
const searchIndex = lunr(function () {
|
||||
// The search index uses these field names extensively, so shortening them can save some serious bytes. The
|
||||
// initial index file went from 468 KB => 401 KB by using single-character names!
|
||||
this.ref('id'); // id
|
||||
this.field('t', { boost: 50 }); // title
|
||||
this.field('h', { boost: 25 }); // headings
|
||||
this.field('c'); // content
|
||||
|
||||
results.forEach((result, index) => {
|
||||
const url = path
|
||||
.join('/', path.relative(eleventyConfig.dir.output, result.outputPath))
|
||||
.replace(/\\/g, '/') // convert backslashes to forward slashes
|
||||
.replace(/\/index.html$/, '/'); // convert trailing /index.html to /
|
||||
const doc = new JSDOM(result.content, {
|
||||
// We must set a default URL so links are parsed with a hostname. Let's use a bogus TLD so we can easily
|
||||
// identify which ones are internal and which ones are external.
|
||||
url: `https://internal/`
|
||||
}).window.document;
|
||||
const content = doc.querySelector('#content');
|
||||
|
||||
// Get title and headings
|
||||
const title = (doc.querySelector('title')?.textContent || path.basename(result.outputPath)).trim();
|
||||
const headings = [...content.querySelectorAll('h1, h2, h3, h4')]
|
||||
.map(heading => heading.textContent)
|
||||
.join(' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
|
||||
// Remove code blocks and whitespace from content
|
||||
[...content.querySelectorAll('code[class|=language]')].forEach(code => code.remove());
|
||||
const textContent = content.textContent.replace(/\s+/g, ' ').trim();
|
||||
|
||||
// Update the index and map
|
||||
this.add({ id: index, t: title, h: headings, c: textContent });
|
||||
map[index] = { title, url };
|
||||
});
|
||||
});
|
||||
|
||||
// Copy the Lunr search client and write the index
|
||||
fs.mkdirSync(path.dirname(lunrOutput), { recursive: true });
|
||||
fs.copyFileSync(lunrInput, lunrOutput);
|
||||
fs.writeFileSync(searchIndexFilename, JSON.stringify({ searchIndex, map }), 'utf-8');
|
||||
|
||||
hasBuiltSearchIndex = true;
|
||||
});
|
||||
|
||||
//
|
||||
// Send a signal to stdout that let's the build know we've reached this point
|
||||
//
|
||||
eleventyConfig.on('eleventy.after', () => {
|
||||
console.log('[eleventy.after]');
|
||||
});
|
||||
|
||||
//
|
||||
// Dev server options (see https://www.11ty.dev/docs/dev-server/#options)
|
||||
//
|
||||
eleventyConfig.setServerOptions({
|
||||
domDiff: false, // disable dom diffing so custom elements don't break on reload,
|
||||
port: 4000, // if port 4000 is taken, 11ty will use the next one available
|
||||
watch: ['cdn/**/*'] // additional files to watch that will trigger server updates (array of paths or globs)
|
||||
});
|
||||
|
||||
//
|
||||
// 11ty config
|
||||
//
|
||||
return {
|
||||
dir: {
|
||||
input: 'pages',
|
||||
output: '../_site',
|
||||
includes: '../_includes' // resolved relative to the input dir
|
||||
},
|
||||
markdownTemplateEngine: 'njk', // use Nunjucks instead of Liquid for markdown files
|
||||
templateEngineOverride: ['njk'] // just Nunjucks and then markdown
|
||||
};
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
# Angular
|
||||
|
||||
Angular [plays nice](https://custom-elements-everywhere.com/#angular) with custom elements, so you can use Shoelace in your Angular apps with ease.
|
||||
|
||||
## Installation
|
||||
|
||||
To add Shoelace to your Angular app, install the package from npm.
|
||||
|
||||
```bash
|
||||
npm install @shoelace-style/shoelace
|
||||
```
|
||||
|
||||
Next, [include a theme](/getting-started/themes) and set the [base path](/getting-started/installation#setting-the-base-path) for icons and other assets. In this example, we'll import the light theme and use the CDN as a base path.
|
||||
|
||||
```jsx
|
||||
import '@shoelace-style/shoelace/dist/themes/light.css';
|
||||
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path';
|
||||
|
||||
setBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/');
|
||||
```
|
||||
|
||||
?> If you'd rather not use the CDN for assets, you can create a build task that copies `node_modules/@shoelace-style/shoelace/dist/assets` into a public folder in your app. Then you can point the base path to that folder instead.
|
||||
|
||||
## Configuration
|
||||
|
||||
Then make sure to apply the custom elements schema as shown below.
|
||||
|
||||
```js
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [BrowserModule],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
Now you can start using Shoelace components in your app!
|
||||
|
||||
?> Are you using Shoelace with Angular? [Help us improve this page!](https://github.com/shoelace-style/shoelace/blob/next/docs/frameworks/angular.md)
|
||||
@@ -1,336 +0,0 @@
|
||||
# Form Controls
|
||||
|
||||
Every Shoelace component makes use of a [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) to encapsulate markup, styles, and behavior. One caveat of this approach is that native `<form>` elements do not recognize form controls located inside a shadow root.
|
||||
|
||||
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.
|
||||
|
||||
## Form 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.
|
||||
|
||||
The [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) interface offers a standard way to serialize forms in the browser. You can create a `FormData` object from any `<form>` element like this.
|
||||
|
||||
```js
|
||||
const form = document.querySelector('form');
|
||||
const data = new FormData(form);
|
||||
|
||||
// All form control data is available in a FormData object
|
||||
```
|
||||
|
||||
However, some folks find `FormData` tricky to work with or they need to pass a JSON payload to their server. To accommodate this, Shoelace offers a serialization utility that gathers form data and returns a simple JavaScript object instead.
|
||||
|
||||
```js
|
||||
import { serialize } from '@shoelace-style/shoelace/dist/utilities/form.js';
|
||||
|
||||
const form = document.querySelector('form');
|
||||
const data = serialize(form);
|
||||
|
||||
// All form control data is available in a plain object
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
!> 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.
|
||||
|
||||
```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-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="submit" variant="primary">Submit</sl-button>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
const form = document.querySelector('.input-validation-required');
|
||||
form.addEventListener('submit', event => {
|
||||
event.preventDefault();
|
||||
alert('All fields are valid!')
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import {
|
||||
SlButton,
|
||||
SlCheckbox,
|
||||
SlInput,
|
||||
SlMenuItem,
|
||||
SlSelect,
|
||||
SlTextarea
|
||||
} from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => {
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
alert('All fields are valid!');
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<SlInput name="name" label="Name" required />
|
||||
<br />
|
||||
<SlSelect label="Favorite Animal" clearable required>
|
||||
<SlMenuItem value="birds">Birds</SlMenuItem>
|
||||
<SlMenuItem value="cats">Cats</SlMenuItem>
|
||||
<SlMenuItem value="dogs">Dogs</SlMenuItem>
|
||||
<SlMenuItem value="other">Other</SlMenuItem>
|
||||
</SlSelect>
|
||||
<br />
|
||||
<SlTextarea name="comment" label="Comment" required></SlTextarea>
|
||||
<br />
|
||||
<SlCheckbox required>Check me before submitting</SlCheckbox>
|
||||
<br /><br />
|
||||
<SlButton type="submit" variant="primary">Submit</SlButton>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Input Patterns
|
||||
|
||||
To restrict a value to a specific [pattern](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern), use the `pattern` attribute. This example only allows the letters A-Z, so the form will not submit if a number or symbol is entered. This only works with `<sl-input>` elements.
|
||||
|
||||
```html preview
|
||||
<form class="input-validation-pattern">
|
||||
<sl-input name="letters" required label="Letters" pattern="[A-Za-z]+"></sl-input>
|
||||
<br>
|
||||
<sl-button type="submit" variant="primary">Submit</sl-button>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
const form = document.querySelector('.input-validation-pattern');
|
||||
form.addEventListener('submit', event => {
|
||||
event.preventDefault();
|
||||
alert('All fields are valid!')
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton, SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => {
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
alert('All fields are valid!');
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<SlInput name="letters" required label="Letters" pattern="[A-Za-z]+" />
|
||||
<br />
|
||||
<SlButton type="submit" variant="primary">Submit</SlButton>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Input Types
|
||||
|
||||
Some input types will automatically trigger constraints, such as `email` and `url`.
|
||||
|
||||
```html preview
|
||||
<form class="input-validation-type">
|
||||
<sl-input variant="email" label="Email" placeholder="you@example.com" required></sl-input>
|
||||
<br>
|
||||
<sl-input variant="url" label="URL" placeholder="https://example.com/" required></sl-input>
|
||||
<br>
|
||||
<sl-button type="submit" variant="primary">Submit</sl-button>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
const form = document.querySelector('.input-validation-type');
|
||||
form.addEventListener('submit', event => {
|
||||
event.preventDefault();
|
||||
alert('All fields are valid!')
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton, SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => {
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
alert('All fields are valid!');
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<SlInput variant="email" label="Email" placeholder="you@example.com" required />
|
||||
<br />
|
||||
<SlInput variant="url" label="URL" placeholder="https://example.com/" required />
|
||||
<br />
|
||||
<SlButton type="submit" variant="primary">Submit</SlButton>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Custom Validation
|
||||
|
||||
To create a custom validation error, use the `setCustomValidity` method. The form will not be submitted when this method is called with anything other than an empty string, and its message will be shown by the browser as the validation error. To make the input valid again, call the method a second time with an empty string as the argument.
|
||||
|
||||
```html preview
|
||||
<form class="input-validation-custom">
|
||||
<sl-input label="Type 'shoelace'" required></sl-input>
|
||||
<br>
|
||||
<sl-button type="submit" variant="primary">Submit</sl-button>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
const form = document.querySelector('.input-validation-custom');
|
||||
const input = form.querySelector('sl-input');
|
||||
|
||||
form.addEventListener('submit', event => {
|
||||
event.preventDefault();
|
||||
alert('All fields are valid!')
|
||||
});
|
||||
|
||||
input.addEventListener('sl-input', () => {
|
||||
if (input.value === 'shoelace') {
|
||||
input.setCustomValidity('');
|
||||
} else {
|
||||
input.setCustomValidity('Hey, you\'re supposed to type \'shoelace\' before submitting this!');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { useRef, useState } from 'react';
|
||||
import { SlButton, SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
|
||||
const App = () => {
|
||||
const input = useRef(null);
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
function handleInput(event) {
|
||||
setValue(event.target.value);
|
||||
|
||||
if (event.target.value === 'shoelace') {
|
||||
input.current.setCustomValidity('');
|
||||
} else {
|
||||
input.current.setCustomValidity('Hey, you\'re supposed to type \'shoelace\' before submitting this!');
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
alert('All fields are valid!');
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<SlInput
|
||||
ref={input}
|
||||
label="Type 'shoelace'"
|
||||
required
|
||||
value={value}
|
||||
onSlInput={handleInput}
|
||||
/>
|
||||
<br />
|
||||
<SlButton type="submit" variant="primary">Submit</SlButton>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
```html preview
|
||||
<sl-input class="custom-input" required pattern="shoelace">
|
||||
<small slot="help-text">Please enter "shoelace" to continue</small>
|
||||
</sl-input>
|
||||
|
||||
<style>
|
||||
.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);
|
||||
}
|
||||
</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>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
sl-input.invalid {
|
||||
...
|
||||
}
|
||||
</style>
|
||||
```
|
||||
@@ -1,137 +0,0 @@
|
||||
# Installation
|
||||
|
||||
You can use Shoelace via CDN or by installing it locally. You can also [cherry pick](#cherry-picking) individual components for faster load times.
|
||||
|
||||
If you're using a framework, make sure to check out the pages for [React](/frameworks/react), [Vue](/frameworks/vue), and [Angular](/frameworks/angular).
|
||||
|
||||
## CDN Installation (Easiest)
|
||||
|
||||
The easiest way to install Shoelace is with the CDN. Just add the following tags to your page to get all components and the default light theme.
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/themes/light.css">
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/shoelace.js"></script>
|
||||
```
|
||||
|
||||
?> If you're only using a handful of components, it will be more efficient to [cherry pick](#cherry-picking) the ones you need.
|
||||
|
||||
### Dark Theme
|
||||
|
||||
If you prefer to use the [dark theme](/getting-started/themes#dark-theme) instead, use this code and add `<html class="sl-theme-dark">` to the page.
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/themes/dark.css">
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/shoelace.js"></script>
|
||||
```
|
||||
|
||||
### Light & Dark Theme
|
||||
|
||||
If you want to load the light or dark theme based on the user's `prefers-color-scheme` setting, use this. The `media` attributes ensure that only the user's preferred theme stylesheet loads and the `onload` attribute sets the appropriate [theme class](/getting-started/themes) on the `<html>` element.
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" media="(prefers-color-scheme:light)" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/themes/light.css">
|
||||
<link rel="stylesheet" media="(prefers-color-scheme:dark)"
|
||||
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/themes/dark.css"
|
||||
onload="document.documentElement.classList.add('sl-theme-dark');">
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/shoelace.js"></script>
|
||||
```
|
||||
|
||||
Now you can [start using Shoelace!](/getting-started/usage)
|
||||
|
||||
## Local Installation
|
||||
|
||||
If you don't want to use the CDN, you can install Shoelace locally with the following command.
|
||||
|
||||
```bash
|
||||
npm install @shoelace-style/shoelace
|
||||
```
|
||||
|
||||
It's up to you to make the source files available to your app. One way to do this is to create a route in your app called `/shoelace` that serves static files from `node_modules/@shoelace-style/shoelace`.
|
||||
|
||||
Once you've done that, add the following tags to your page. Make sure to update `href` and `src` so they point to the route you created.
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="/shoelace/dist/themes/light.css">
|
||||
<script type="module" src="/shoelace/dist/shoelace.js"></script>
|
||||
```
|
||||
|
||||
Alternatively, [you can use a bundler](#bundling).
|
||||
|
||||
?> For clarity, the docs will usually show imports from `@shoelace-style/shoelace`. If you're not using a module resolver or bundler, you'll need to adjust these paths to point to the folder Shoelace is in.
|
||||
|
||||
## Setting the Base Path
|
||||
|
||||
Some components rely on assets (icons, images, etc.) and Shoelace needs to know where they're located. For convenience, Shoelace will try to auto-detect the correct location based on the script you've loaded it from. This assumes assets are colocated with `shoelace.js` and will "just work" for most users.
|
||||
|
||||
However, if you're [cherry picking](#cherry-picking) or [bundling](#bundling) Shoelace, you'll need to set the base path. You can do this one of two ways.
|
||||
|
||||
```html
|
||||
<!-- Option 1: the data-shoelace attribute -->
|
||||
<script src="bundle.js" data-shoelace="/path/to/shoelace/dist"></script>
|
||||
|
||||
<!-- Option 2: the setBasePath() method -->
|
||||
<script src="bundle.js"></script>
|
||||
<script type="module">
|
||||
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js';
|
||||
setBasePath('/path/to/shoelace/dist');
|
||||
</script>
|
||||
```
|
||||
|
||||
?> The library also exports a `getBasePath()` method you can use to reference assets.
|
||||
|
||||
## Cherry Picking
|
||||
|
||||
The previous approach is the _easiest_ way to load Shoelace, but easy isn't always efficient. You'll incur the full size of the library even if you only use a handful of components. This is convenient for prototyping or if you're using most of the components, but it may result in longer load times in production. To improve this, you can cherry pick the components you need.
|
||||
|
||||
Cherry picking can be done from your local install or [directly from the CDN](https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/). This will limit the number of files the browser has to download and reduce the amount of bytes being transferred. The disadvantage is that you need to load component manually.
|
||||
|
||||
Here's an example that loads only the button component. Again, if you're not using a module resolver, you'll need to adjust the path to point to the folder Shoelace is in.
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="/path/to/shoelace/dist/themes/light.css">
|
||||
|
||||
<script type="module" data-shoelace="/path/to/shoelace/dist">
|
||||
import '@shoelace-style/shoelace/dist/components/button/button.js';
|
||||
|
||||
// <sl-button> is ready to use!
|
||||
</script>
|
||||
```
|
||||
|
||||
You can copy and paste the code to import a component from the "Importing" section of the component's documentation. Note that some components have dependencies that are automatically imported when you cherry pick. If a component has dependencies, they will be listed in the "Dependencies" section of its docs.
|
||||
|
||||
!> Never cherry pick components or utilities from `shoelace.js` as this will cause the browser to load the entire library. Instead, cherry pick from specific modules as shown above.
|
||||
|
||||
!> You will see files named `chunk.[hash].js` in the `chunks` directory. Never import these files directly, as they are generated and change from version to version.
|
||||
|
||||
## Bundling
|
||||
|
||||
Shoelace is distributed as a collection of standard ES modules that [all modern browsers can understand](https://caniuse.com/es6-module). However, importing a lot of modules can result in a lot of HTTP requests and potentially longer load times. Using a CDN can alleviate this, but some users may wish to further optimize their imports with a bundler.
|
||||
|
||||
To use Shoelace with a bundler, first install Shoelace along with your bundler of choice.
|
||||
|
||||
```bash
|
||||
npm install @shoelace-style/shoelace
|
||||
```
|
||||
|
||||
Now it's time to configure your bundler. Configurations vary for each tool, but here are some examples to help you get started.
|
||||
|
||||
- [Example webpack config](https://github.com/shoelace-style/webpack-example/blob/master/webpack.config.js)
|
||||
- [Example Rollup config](https://github.com/shoelace-style/rollup-example/blob/master/rollup.config.js)
|
||||
|
||||
Once your bundler is configured, you'll be able to import Shoelace components and utilities.
|
||||
|
||||
```js
|
||||
import '@shoelace-style/shoelace/dist/themes/light.css';
|
||||
import '@shoelace-style/shoelace/dist/components/button/button.js';
|
||||
import '@shoelace-style/shoelace/dist/components/icon/icon.js';
|
||||
import '@shoelace-style/shoelace/dist/components/input/input.js';
|
||||
import '@shoelace-style/shoelace/dist/components/rating/rating.js';
|
||||
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js';
|
||||
|
||||
// Set the base path to the folder you copied Shoelace's assets to
|
||||
setBasePath('/path/to/shoelace/dist');
|
||||
|
||||
// <sl-button>, <sl-icon>, <sl-input>, and <sl-rating> are ready to use!
|
||||
```
|
||||
|
||||
!> Component modules include side effects for registration purposes. Because of this, importing directly from `@shoelace-style/shoelace` may result in a larger bundle size than necessary. For optimal tree shaking, always cherry pick, i.e. import components and utilities from their respective files, as shown above.
|
||||
@@ -1,98 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Shoelace: A forward-thinking library of web components.</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Shoelace provides a collection of professionally designed, every day UI components built on a framework-agnostic technology."
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="custom-elements-manifest" content="dist/custom-elements.json" />
|
||||
<meta property="og:title" content="Shoelace" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Shoelace provides a collection of professionally designed, every day UI components built on a framework-agnostic technology."
|
||||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://shoelace.style/" />
|
||||
<meta property="og:image" content="https://shoelace.style/assets/images/og-image.png" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:site" content="@shoelace_style" />
|
||||
<meta name="twitter:creator" content="@claviska" />
|
||||
<meta name="twitter:title" content="Shoelace" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Shoelace provides a collection of professionally designed, every day UI components built on a framework-agnostic technology."
|
||||
/>
|
||||
<meta name="twitter:image" content="https://shoelace.style/assets/images/twitter-card.png" />
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify@4/themes/pure.css" />
|
||||
<link rel="stylesheet" href="/assets/styles/docs.css" />
|
||||
<link rel="stylesheet" href="/assets/plugins/code-block/code-block.css" />
|
||||
<link rel="stylesheet" href="/assets/plugins/search/search.css" />
|
||||
<link rel="stylesheet" href="/assets/plugins/theme-picker/theme-picker.css" />
|
||||
<link rel="icon" href="/assets/images/logo.svg" type="image/x-icon" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/touch-icon.png" />
|
||||
|
||||
<!-- Import Shoelace -->
|
||||
<link rel="stylesheet" href="/dist/themes/light.css" />
|
||||
<link rel="stylesheet" href="/dist/themes/dark.css" />
|
||||
<script type="module" src="/dist/shoelace.js"></script>
|
||||
</head>
|
||||
<body data-shoelace="/dist">
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
// Set the initial theme to prevent flashing
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const theme = localStorage.getItem('theme') || 'auto';
|
||||
document.documentElement.classList.toggle('sl-theme-dark', theme === 'dark' || (theme === 'auto' && prefersDark));
|
||||
</script>
|
||||
<script>
|
||||
window.$docsify = {
|
||||
alias: {
|
||||
'/.*/_sidebar.md': '/_sidebar.md'
|
||||
},
|
||||
auto2top: true,
|
||||
copyCode: {
|
||||
buttonText: 'Copy',
|
||||
errorText: 'Failed to copy',
|
||||
successText: 'Copied'
|
||||
},
|
||||
coverpage: false,
|
||||
executeScript: true,
|
||||
ga: 'UA-6412891-16',
|
||||
homepage: '/getting-started/overview.md',
|
||||
loadSidebar: true,
|
||||
logo: '/assets/images/wordmark.svg',
|
||||
maxLevel: 3,
|
||||
subMaxLevel: 2,
|
||||
name: 'Shoelace',
|
||||
nameLink: '/',
|
||||
notFoundPage: '404.md',
|
||||
pagination: {
|
||||
previousText: 'Previous',
|
||||
nextText: 'Next',
|
||||
crossChapter: true,
|
||||
crossChapterText: false
|
||||
},
|
||||
routerMode: 'history',
|
||||
themeColor: 'var(--sl-color-primary-500)'
|
||||
};
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify@4/lib/docsify.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify@4/lib/plugins/ga.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify-copy-code@2"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify-pagination@2/dist/docsify-pagination.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.25.0/components/prism-bash.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.25.0/components/prism-jsx.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.25.0/components/prism-tsx.min.js"></script>
|
||||
<script src="/assets/plugins/code-block/code-block.js"></script>
|
||||
<script src="/assets/plugins/metadata/metadata.js"></script>
|
||||
<script src="/assets/plugins/scroll-position/scroll-position.js"></script>
|
||||
<script src="/assets/plugins/search/lunr.min.js"></script>
|
||||
<script src="/assets/plugins/search/search.js"></script>
|
||||
<script src="/assets/plugins/theme-picker/theme-picker.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
19
docs/pages/404.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
meta:
|
||||
title: Page Not Found
|
||||
description: "The page you were looking for couldn't be found."
|
||||
permalink: 404.html
|
||||
toc: false
|
||||
---
|
||||
|
||||
<div style="text-align: center;">
|
||||
|
||||
# Page Not Found
|
||||
|
||||

|
||||
|
||||
The page you were looking for couldn't be found.
|
||||
|
||||
Press [[/]] to search, or [head back to the homepage](/).
|
||||
|
||||
</div>
|
||||
@@ -1,18 +1,20 @@
|
||||
# Alert
|
||||
---
|
||||
meta:
|
||||
title: Alert
|
||||
description: Alerts are used to display important messages inline or as toast notifications.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-alert]
|
||||
|
||||
Alerts are used to display important messages inline or as toast notifications.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-alert open>
|
||||
<sl-icon slot="icon" name="info-circle"></sl-icon>
|
||||
This is a standard alert. You can customize its content and even the icon.
|
||||
</sl-alert>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlAlert, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
|
||||
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
const App = () => (
|
||||
<SlAlert open>
|
||||
@@ -22,7 +24,9 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
?> Alerts will not be visible if the `open` attribute is not present.
|
||||
:::tip
|
||||
Alerts will not be visible if the `open` attribute is not present.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -30,54 +34,56 @@ const App = () => (
|
||||
|
||||
Set the `variant` attribute to change the alert's variant.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-alert variant="primary" open>
|
||||
<sl-icon slot="icon" name="info-circle"></sl-icon>
|
||||
<strong>This is super informative</strong><br>
|
||||
<strong>This is super informative</strong><br />
|
||||
You can tell by how pretty the alert is.
|
||||
</sl-alert>
|
||||
|
||||
<br>
|
||||
<br />
|
||||
|
||||
<sl-alert variant="success" open>
|
||||
<sl-icon slot="icon" name="check2-circle"></sl-icon>
|
||||
<strong>Your changes have been saved</strong><br>
|
||||
<strong>Your changes have been saved</strong><br />
|
||||
You can safely exit the app now.
|
||||
</sl-alert>
|
||||
|
||||
<br>
|
||||
<br />
|
||||
|
||||
<sl-alert variant="neutral" open>
|
||||
<sl-icon slot="icon" name="gear"></sl-icon>
|
||||
<strong>Your settings have been updated</strong><br>
|
||||
<strong>Your settings have been updated</strong><br />
|
||||
Settings will take affect on next login.
|
||||
</sl-alert>
|
||||
|
||||
<br>
|
||||
<br />
|
||||
|
||||
<sl-alert variant="warning" open>
|
||||
<sl-icon slot="icon" name="exclamation-triangle"></sl-icon>
|
||||
<strong>Your session has ended</strong><br>
|
||||
<strong>Your session has ended</strong><br />
|
||||
Please login again to continue.
|
||||
</sl-alert>
|
||||
|
||||
<br>
|
||||
<br />
|
||||
|
||||
<sl-alert variant="danger" open>
|
||||
<sl-icon slot="icon" name="exclamation-octagon"></sl-icon>
|
||||
<strong>Your account has been deleted</strong><br>
|
||||
We're very sorry to see you go!
|
||||
<strong>Your account has been deleted</strong><br />
|
||||
We're very sorry to see you go!
|
||||
</sl-alert>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlAlert, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
|
||||
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlAlert variant="primary" open>
|
||||
<SlIcon slot="icon" name="info-circle" />
|
||||
<strong>This is super informative</strong><br />
|
||||
<strong>This is super informative</strong>
|
||||
<br />
|
||||
You can tell by how pretty the alert is.
|
||||
</SlAlert>
|
||||
|
||||
@@ -85,7 +91,8 @@ const App = () => (
|
||||
|
||||
<SlAlert variant="success" open>
|
||||
<SlIcon slot="icon" name="check2-circle" />
|
||||
<strong>Your changes have been saved</strong><br />
|
||||
<strong>Your changes have been saved</strong>
|
||||
<br />
|
||||
You can safely exit the app now.
|
||||
</SlAlert>
|
||||
|
||||
@@ -93,7 +100,8 @@ const App = () => (
|
||||
|
||||
<SlAlert variant="neutral" open>
|
||||
<SlIcon slot="icon" name="gear" />
|
||||
<strong>Your settings have been updated</strong><br />
|
||||
<strong>Your settings have been updated</strong>
|
||||
<br />
|
||||
Settings will take affect on next login.
|
||||
</SlAlert>
|
||||
|
||||
@@ -101,7 +109,8 @@ const App = () => (
|
||||
|
||||
<SlAlert variant="warning" open>
|
||||
<SlIcon slot="icon" name="exclamation-triangle" />
|
||||
<strong>Your session has ended</strong><br />
|
||||
<strong>Your session has ended</strong>
|
||||
<br />
|
||||
Please login again to continue.
|
||||
</SlAlert>
|
||||
|
||||
@@ -109,8 +118,9 @@ const App = () => (
|
||||
|
||||
<SlAlert variant="danger" open>
|
||||
<SlIcon slot="icon" name="exclamation-octagon" />
|
||||
<strong>Your account has been deleted</strong><br />
|
||||
We're very sorry to see you go!
|
||||
<strong>Your account has been deleted</strong>
|
||||
<br />
|
||||
We're very sorry to see you go!
|
||||
</SlAlert>
|
||||
</>
|
||||
);
|
||||
@@ -120,7 +130,7 @@ const App = () => (
|
||||
|
||||
Add the `closable` attribute to show a close button that will hide the alert.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-alert variant="primary" open closable class="alert-closable">
|
||||
<sl-icon slot="icon" name="info-circle"></sl-icon>
|
||||
You can close this alert any time!
|
||||
@@ -129,14 +139,15 @@ Add the `closable` attribute to show a close button that will hide the alert.
|
||||
<script>
|
||||
const alert = document.querySelector('.alert-closable');
|
||||
alert.addEventListener('sl-after-hide', () => {
|
||||
setTimeout(() => alert.open = true, 2000);
|
||||
setTimeout(() => (alert.open = true), 2000);
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlAlert, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
|
||||
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
const App = () => {
|
||||
const [open, setOpen] = useState(true);
|
||||
@@ -147,11 +158,7 @@ const App = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<SlAlert
|
||||
open={open}
|
||||
closable
|
||||
onSlAfterHide={handleHide}
|
||||
>
|
||||
<SlAlert open={open} closable onSlAfterHide={handleHide}>
|
||||
<SlIcon slot="icon" name="info-circle" />
|
||||
You can close this alert any time!
|
||||
</SlAlert>
|
||||
@@ -163,14 +170,12 @@ const App = () => {
|
||||
|
||||
Icons are optional. Simply omit the `icon` slot if you don't want them.
|
||||
|
||||
```html preview
|
||||
<sl-alert variant="primary" open>
|
||||
Nothing fancy here, just a simple alert.
|
||||
</sl-alert>
|
||||
```html:preview
|
||||
<sl-alert variant="primary" open> Nothing fancy here, just a simple alert. </sl-alert>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlAlert } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
|
||||
|
||||
const App = () => (
|
||||
<SlAlert variant="primary" open>
|
||||
@@ -183,7 +188,7 @@ const App = () => (
|
||||
|
||||
Set the `duration` attribute to automatically hide an alert after a period of time. This is useful for alerts that don't require acknowledgement.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="alert-duration">
|
||||
<sl-button variant="primary">Show Alert</sl-button>
|
||||
|
||||
@@ -208,13 +213,11 @@ Set the `duration` attribute to automatically hide an alert after a period of ti
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
SlAlert,
|
||||
SlButton,
|
||||
SlIcon
|
||||
} from '@shoelace-style/shoelace/dist/react';
|
||||
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
const css = `
|
||||
.alert-duration sl-alert {
|
||||
@@ -228,15 +231,11 @@ const App = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="alert-duration">
|
||||
<SlButton variant="primary" onClick={() => setOpen(true)}>Show Alert</SlButton>
|
||||
<SlButton variant="primary" onClick={() => setOpen(true)}>
|
||||
Show Alert
|
||||
</SlButton>
|
||||
|
||||
<SlAlert
|
||||
variant="primary"
|
||||
duration="3000"
|
||||
open={open}
|
||||
closable
|
||||
onSlAfterHide={() => setOpen(false)}
|
||||
>
|
||||
<SlAlert variant="primary" duration="3000" open={open} closable onSlAfterHide={() => setOpen(false)}>
|
||||
<SlIcon slot="icon" name="info-circle" />
|
||||
This alert will automatically hide itself after three seconds, unless you interact with it.
|
||||
</SlAlert>
|
||||
@@ -254,41 +253,41 @@ To display an alert as a toast notification, or "toast", create the alert and ca
|
||||
|
||||
You should always use the `closable` attribute so users can dismiss the notification. It's also common to set a reasonable `duration` when the notification doesn't require acknowledgement.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="alert-toast">
|
||||
<sl-button variant="primary">Primary</sl-button>
|
||||
<sl-button variant="success">Success</sl-button>
|
||||
<sl-button variant="neutral">Neutral</sl-button>
|
||||
<sl-button variant="warning">Warning</sl-button>
|
||||
<sl-button variant="danger">Danger</sl-button>
|
||||
|
||||
|
||||
<sl-alert variant="primary" duration="3000" closable>
|
||||
<sl-icon slot="icon" name="info-circle"></sl-icon>
|
||||
<strong>This is super informative</strong><br>
|
||||
<strong>This is super informative</strong><br />
|
||||
You can tell by how pretty the alert is.
|
||||
</sl-alert>
|
||||
|
||||
<sl-alert variant="success" duration="3000" closable>
|
||||
<sl-icon slot="icon" name="check2-circle"></sl-icon>
|
||||
<strong>Your changes have been saved</strong><br>
|
||||
<strong>Your changes have been saved</strong><br />
|
||||
You can safely exit the app now.
|
||||
</sl-alert>
|
||||
|
||||
<sl-alert variant="neutral" duration="3000" closable>
|
||||
<sl-icon slot="icon" name="gear"></sl-icon>
|
||||
<strong>Your settings have been updated</strong><br>
|
||||
<strong>Your settings have been updated</strong><br />
|
||||
Settings will take affect on next login.
|
||||
</sl-alert>
|
||||
|
||||
<sl-alert variant="warning" duration="3000" closable>
|
||||
<sl-icon slot="icon" name="exclamation-triangle"></sl-icon>
|
||||
<strong>Your session has ended</strong><br>
|
||||
<strong>Your session has ended</strong><br />
|
||||
Please login again to continue.
|
||||
</sl-alert>
|
||||
|
||||
<sl-alert variant="danger" duration="3000" closable>
|
||||
<sl-icon slot="icon" name="exclamation-octagon"></sl-icon>
|
||||
<strong>Your account has been deleted</strong><br>
|
||||
<strong>Your account has been deleted</strong><br />
|
||||
We're very sorry to see you go!
|
||||
</sl-alert>
|
||||
</div>
|
||||
@@ -305,13 +304,11 @@ You should always use the `closable` attribute so users can dismiss the notifica
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useRef } from 'react';
|
||||
import {
|
||||
SlAlert,
|
||||
SlButton,
|
||||
SlIcon
|
||||
} from '@shoelace-style/shoelace/dist/react';
|
||||
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
function showToast(alert) {
|
||||
alert.toast();
|
||||
@@ -348,31 +345,36 @@ const App = () => {
|
||||
|
||||
<SlAlert ref={primary} variant="primary" duration="3000" closable>
|
||||
<SlIcon slot="icon" name="info-circle" />
|
||||
<strong>This is super informative</strong><br />
|
||||
<strong>This is super informative</strong>
|
||||
<br />
|
||||
You can tell by how pretty the alert is.
|
||||
</SlAlert>
|
||||
|
||||
<SlAlert ref={success} variant="success" duration="3000" closable>
|
||||
<SlIcon slot="icon" name="check2-circle" />
|
||||
<strong>Your changes have been saved</strong><br />
|
||||
<strong>Your changes have been saved</strong>
|
||||
<br />
|
||||
You can safely exit the app now.
|
||||
</SlAlert>
|
||||
|
||||
<SlAlert ref={neutral} variant="neutral" duration="3000" closable>
|
||||
<SlIcon slot="icon" name="gear" />
|
||||
<strong>Your settings have been updated</strong><br />
|
||||
<strong>Your settings have been updated</strong>
|
||||
<br />
|
||||
Settings will take affect on next login.
|
||||
</SlAlert>
|
||||
|
||||
<SlAlert ref={warning} variant="warning" duration="3000" closable>
|
||||
<SlIcon slot="icon" name="exclamation-triangle" />
|
||||
<strong>Your session has ended</strong><br />
|
||||
<strong>Your session has ended</strong>
|
||||
<br />
|
||||
Please login again to continue.
|
||||
</SlAlert>
|
||||
|
||||
<SlAlert ref={danger} variant="danger" duration="3000" closable>
|
||||
<SlIcon slot="icon" name="exclamation-octagon" />
|
||||
<strong>Your account has been deleted</strong><br />
|
||||
<strong>Your account has been deleted</strong>
|
||||
<br />
|
||||
We're very sorry to see you go!
|
||||
</SlAlert>
|
||||
</>
|
||||
@@ -384,7 +386,7 @@ const App = () => {
|
||||
|
||||
For convenience, you can create a utility that emits toast notifications with a function call rather than composing them in your HTML. To do this, generate the alert with JavaScript, append it to the body, and call the `toast()` method as shown in the example below.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="alert-toast-wrapper">
|
||||
<sl-button variant="primary">Create Toast</sl-button>
|
||||
</div>
|
||||
@@ -399,7 +401,7 @@ For convenience, you can create a utility that emits toast notifications with a
|
||||
const div = document.createElement('div');
|
||||
div.textContent = html;
|
||||
return div.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
// Custom function to emit toast notifications
|
||||
function notify(message, variant = 'primary', icon = 'info-circle', duration = 3000) {
|
||||
@@ -436,6 +438,6 @@ By default, the toast stack is positioned at the top-right of the viewport. You
|
||||
}
|
||||
```
|
||||
|
||||
?> By design, it is not possible to show toasts in more than one stack simultaneously. Such behavior is confusing and makes for a poor user experience.
|
||||
|
||||
[component-metadata:sl-alert]
|
||||
:::tip
|
||||
By design, it is not possible to show toasts in more than one stack simultaneously. Such behavior is confusing and makes for a poor user experience.
|
||||
:::
|
||||
@@ -1,28 +1,31 @@
|
||||
# Animated Image
|
||||
---
|
||||
meta:
|
||||
title: Animated Image
|
||||
description: A component for displaying animated GIFs and WEBPs that play and pause on interaction.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-animated-image]
|
||||
|
||||
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"
|
||||
```html:preview
|
||||
<sl-animated-image
|
||||
src="https://shoelace.style/assets/images/walk.gif"
|
||||
alt="Animation of untied shoes walking on pavement"
|
||||
></sl-animated-image>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlAnimatedImage } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';
|
||||
|
||||
const App = () => (
|
||||
<SlAnimatedImage
|
||||
src="https://shoelace.style/assets/images/walk.gif"
|
||||
src="https://shoelace.style/assets/images/walk.gif"
|
||||
alt="Animation of untied shoes walking on pavement"
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
?> This component uses `<canvas>` to draw freeze frames, so images are subject to [cross-origin restrictions](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image).
|
||||
:::tip
|
||||
This component uses `<canvas>` to draw freeze frames, so images are subject to [cross-origin restrictions](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image).
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -30,21 +33,18 @@ const App = () => (
|
||||
|
||||
Both GIF and WEBP images are supported.
|
||||
|
||||
```html preview
|
||||
<sl-animated-image
|
||||
src="https://shoelace.style/assets/images/tie.webp"
|
||||
```html:preview
|
||||
<sl-animated-image
|
||||
src="https://shoelace.style/assets/images/tie.webp"
|
||||
alt="Animation of a shoe being tied"
|
||||
></sl-animated-image>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlAnimatedImage } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';
|
||||
|
||||
const App = () => (
|
||||
<SlAnimatedImage
|
||||
src="https://shoelace.style/assets/images/tie.webp"
|
||||
alt="Animation of a shoe being tied"
|
||||
/>
|
||||
<SlAnimatedImage src="https://shoelace.style/assets/images/tie.webp" alt="Animation of a shoe being tied" />
|
||||
);
|
||||
```
|
||||
|
||||
@@ -52,34 +52,38 @@ const App = () => (
|
||||
|
||||
To set a custom size, apply a width and/or height to the host element.
|
||||
|
||||
```html preview
|
||||
<sl-animated-image
|
||||
src="https://shoelace.style/assets/images/walk.gif"
|
||||
```html:preview
|
||||
<sl-animated-image
|
||||
src="https://shoelace.style/assets/images/walk.gif"
|
||||
alt="Animation of untied shoes walking on pavement"
|
||||
style="width: 150px; height: 200px;"
|
||||
>
|
||||
</sl-animated-image>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlAnimatedImage } from '@shoelace-style/shoelace/dist/react';
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import SlAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';
|
||||
|
||||
const App = () => (
|
||||
<SlAnimatedImage
|
||||
src="https://shoelace.style/assets/images/walk.gif"
|
||||
src="https://shoelace.style/assets/images/walk.gif"
|
||||
alt="Animation of untied shoes walking on pavement"
|
||||
style={{ width: '150px', height: '200px' }}
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Customizing the Control Box
|
||||
|
||||
You can change the appearance and location of the control box by targeting the `control-box` part in your styles.
|
||||
|
||||
```html preview
|
||||
<sl-animated-image
|
||||
src="https://shoelace.style/assets/images/walk.gif"
|
||||
```html:preview
|
||||
<sl-animated-image
|
||||
src="https://shoelace.style/assets/images/walk.gif"
|
||||
alt="Animation of untied shoes walking on pavement"
|
||||
class="animated-image-custom-control-box"
|
||||
></sl-animated-image>
|
||||
@@ -97,8 +101,8 @@ You can change the appearance and location of the control box by targeting the `
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlAnimatedImage } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';
|
||||
|
||||
const css = `
|
||||
.animated-image-custom-control-box::part(control-box) {
|
||||
@@ -116,7 +120,7 @@ const App = () => (
|
||||
<>
|
||||
<SlAnimatedImage
|
||||
className="animated-image-custom-control-box"
|
||||
src="https://shoelace.style/assets/images/walk.gif"
|
||||
src="https://shoelace.style/assets/images/walk.gif"
|
||||
alt="Animation of untied shoes walking on pavement"
|
||||
/>
|
||||
|
||||
@@ -124,5 +128,3 @@ const App = () => (
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-animated-image]
|
||||
@@ -1,12 +1,13 @@
|
||||
# Animation
|
||||
|
||||
[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).
|
||||
---
|
||||
meta:
|
||||
title: Animation
|
||||
description: Animate elements declaratively with nearly 100 baked-in presets, or roll your own with custom keyframes.
|
||||
layout: component
|
||||
---
|
||||
|
||||
To animate an element, wrap it in `<sl-animation>` and set an animation `name`. The animation will not start until you add the `play` attribute. Refer to the [properties table](#properties) for a list of all animation options.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="animation-overview">
|
||||
<sl-animation name="bounce" duration="2000" play><div class="box"></div></sl-animation>
|
||||
<sl-animation name="jello" duration="2000" play><div class="box"></div></sl-animation>
|
||||
@@ -25,8 +26,8 @@ To animate an element, wrap it in `<sl-animation>` and set an animation `name`.
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlAnimation } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlAnimation from '@shoelace-style/shoelace/dist/react/animation';
|
||||
|
||||
const css = `
|
||||
.animation-overview .box {
|
||||
@@ -41,18 +42,28 @@ const css = `
|
||||
const App = () => (
|
||||
<>
|
||||
<div class="animation-overview">
|
||||
<SlAnimation name="bounce" duration={2000} play><div class="box" /></SlAnimation>
|
||||
<SlAnimation name="jello" duration={2000} play><div class="box" /></SlAnimation>
|
||||
<SlAnimation name="heartBeat" duration={2000} play><div class="box" /></SlAnimation>
|
||||
<SlAnimation name="flip" duration={2000} play><div class="box" /></SlAnimation>
|
||||
<SlAnimation name="bounce" duration={2000} play>
|
||||
<div class="box" />
|
||||
</SlAnimation>
|
||||
<SlAnimation name="jello" duration={2000} play>
|
||||
<div class="box" />
|
||||
</SlAnimation>
|
||||
<SlAnimation name="heartBeat" duration={2000} play>
|
||||
<div class="box" />
|
||||
</SlAnimation>
|
||||
<SlAnimation name="flip" duration={2000} play>
|
||||
<div class="box" />
|
||||
</SlAnimation>
|
||||
</div>
|
||||
|
||||
<style>{css}</style>
|
||||
<style>{css}</style>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
?> The animation will only be applied to the first child element found in `<sl-animation>`.
|
||||
:::tip
|
||||
The animation will only be applied to the first child element found in `<sl-animation>`.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -60,7 +71,7 @@ const App = () => (
|
||||
|
||||
This example demonstrates all of the baked-in animations and easings. Animations are based on those found in the popular [Animate.css](https://animate.style/) library.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="animation-sandbox">
|
||||
<sl-animation name="bounce" easing="ease-in-out" duration="2000" play>
|
||||
<div class="box"></div>
|
||||
@@ -85,24 +96,24 @@ 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);
|
||||
});
|
||||
|
||||
easings.map(name => {
|
||||
const menuItem = Object.assign(document.createElement('sl-menu-item'), {
|
||||
textContent: name,
|
||||
value: name
|
||||
});
|
||||
easingName.appendChild(menuItem);
|
||||
animationName.appendChild(option);
|
||||
});
|
||||
|
||||
animationName.addEventListener('sl-change', () => animation.name = animationName.value);
|
||||
easingName.addEventListener('sl-change', () => animation.easing = easingName.value);
|
||||
playbackRate.addEventListener('sl-input', () => animation.playbackRate = playbackRate.value);
|
||||
easings.map(name => {
|
||||
const option = Object.assign(document.createElement('sl-option'), {
|
||||
textContent: name,
|
||||
value: name
|
||||
});
|
||||
easingName.appendChild(option);
|
||||
});
|
||||
|
||||
animationName.addEventListener('sl-change', () => (animation.name = animationName.value));
|
||||
easingName.addEventListener('sl-change', () => (animation.easing = easingName.value));
|
||||
playbackRate.addEventListener('sl-input', () => (animation.playbackRate = playbackRate.value));
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -127,7 +138,7 @@ This example demonstrates all of the baked-in animations and easings. Animations
|
||||
|
||||
Use an [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) to control the animation when an element enters or exits the viewport. For example, scroll the box below in and out of your screen. The animation stops when the box exits the viewport and restarts each time it enters the viewport.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="animation-scroll">
|
||||
<sl-animation name="jackInTheBox" duration="2000" iterations="1"><div class="box"></div></sl-animation>
|
||||
</div>
|
||||
@@ -156,13 +167,13 @@ Use an [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: var(--sl-color-primary-600);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { SlAnimation } from '@shoelace-style/shoelace/dist/react';
|
||||
import SlAnimation from '@shoelace-style/shoelace/dist/react/animation';
|
||||
|
||||
const css = `
|
||||
.animation-scroll {
|
||||
@@ -182,16 +193,14 @@ const App = () => {
|
||||
const box = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
animation.current.play = true;
|
||||
} else {
|
||||
animation.current.play = false;
|
||||
animation.current.currentTime = 0;
|
||||
}
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
if (entries[0].isIntersecting) {
|
||||
animation.current.play = true;
|
||||
} else {
|
||||
animation.current.play = false;
|
||||
animation.current.currentTime = 0;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
if (box.current) {
|
||||
observer.observe(box.current);
|
||||
@@ -201,17 +210,12 @@ const App = () => {
|
||||
return (
|
||||
<>
|
||||
<div class="animation-scroll">
|
||||
<SlAnimation
|
||||
ref={animation}
|
||||
name="jackInTheBox"
|
||||
duration={2000}
|
||||
iterations={1}
|
||||
>
|
||||
<SlAnimation ref={animation} name="jackInTheBox" duration={2000} iterations={1}>
|
||||
<div ref={box} class="box" />
|
||||
</SlAnimation>
|
||||
</div>
|
||||
|
||||
<style>{css}</style>
|
||||
<style>{css}</style>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -221,7 +225,7 @@ const App = () => {
|
||||
|
||||
Supply your own [keyframe formats](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Keyframe_Formats) to build custom animations.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="animation-keyframes">
|
||||
<sl-animation easing="ease-in-out" duration="2000" play>
|
||||
<div class="box"></div>
|
||||
@@ -257,8 +261,8 @@ Supply your own [keyframe formats](https://developer.mozilla.org/en-US/docs/Web/
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlAnimation } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlAnimation from '@shoelace-style/shoelace/dist/react/animation';
|
||||
|
||||
const css = `
|
||||
.animation-keyframes .box {
|
||||
@@ -271,9 +275,9 @@ const css = `
|
||||
const App = () => (
|
||||
<>
|
||||
<div class="animation-keyframes">
|
||||
<SlAnimation
|
||||
easing="ease-in-out"
|
||||
duration={2000}
|
||||
<SlAnimation
|
||||
easing="ease-in-out"
|
||||
duration={2000}
|
||||
play
|
||||
keyframes={[
|
||||
{
|
||||
@@ -296,7 +300,7 @@ const App = () => (
|
||||
</SlAnimation>
|
||||
</div>
|
||||
|
||||
<style>{css}</style>
|
||||
<style>{css}</style>
|
||||
</>
|
||||
);
|
||||
```
|
||||
@@ -305,7 +309,7 @@ const App = () => (
|
||||
|
||||
Animations won't play until you apply the `play` attribute. You can omit it initially, then apply it on demand such as after a user interaction. In this example, the button will animate once every time the button is clicked.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="animation-form">
|
||||
<sl-animation name="rubberBand" duration="1000" iterations="1">
|
||||
<sl-button variant="primary">Click me</sl-button>
|
||||
@@ -323,22 +327,17 @@ Animations won't play until you apply the `play` attribute. You can omit it init
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlAnimation, SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
import SlAnimation from '@shoelace-style/shoelace/dist/react/animation';
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
|
||||
const App = () => {
|
||||
const [play, setPlay] = useState(false);
|
||||
|
||||
return (
|
||||
<div class="animation-form">
|
||||
<SlAnimation
|
||||
name="rubberBand"
|
||||
duration={1000}
|
||||
iterations={1}
|
||||
play={play}
|
||||
onSlFinish={() => setPlay(false)}
|
||||
>
|
||||
<SlAnimation name="rubberBand" duration={1000} iterations={1} play={play} onSlFinish={() => setPlay(false)}>
|
||||
<SlButton variant="primary" onClick={() => setPlay(true)}>
|
||||
Click me
|
||||
</SlButton>
|
||||
@@ -347,5 +346,3 @@ const App = () => {
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
[component-metadata:sl-animation]
|
||||
@@ -1,44 +1,54 @@
|
||||
# Avatar
|
||||
---
|
||||
meta:
|
||||
title: Avatar
|
||||
description: Avatars are used to represent a person or object.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-avatar]
|
||||
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.
|
||||
|
||||
Avatars are used to represent a person or object.
|
||||
|
||||
Like [images](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img), you should always provide `alt` text for avatars as alternate text for assistive devices.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-avatar label="User avatar"></sl-avatar>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlAvatar } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
||||
|
||||
const App = () => (
|
||||
<SlAvatar label="User avatar" />
|
||||
);
|
||||
const App = () => <SlAvatar label="User avatar" />;
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Images
|
||||
|
||||
To use an image for the avatar, set the `image` and `alt` attributes. This will take priority and be shown over initials and icons.
|
||||
To use an image for the avatar, set the `image` and `label` attributes. This will take priority and be shown over initials and icons.
|
||||
Avatar images can be lazily loaded by setting the `loading` attribute to `lazy`.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-avatar
|
||||
image="https://images.unsplash.com/photo-1529778873920-4da4926a72c2?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=80"
|
||||
label="Avatar of a gray tabby kitten looking down"
|
||||
></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
|
||||
import { SlAvatar } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
||||
|
||||
const App = () => (
|
||||
<SlAvatar
|
||||
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"
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
@@ -46,23 +56,21 @@ const App = () => (
|
||||
|
||||
When you don't have an image to use, you can set the `initials` attribute to show something more personalized than an icon.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-avatar initials="SL" label="Avatar with initials: SL"></sl-avatar>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlAvatar } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
||||
|
||||
const App = () => (
|
||||
<SlAvatar initials="SL" label="Avatar with initials: SL" />
|
||||
);
|
||||
const App = () => <SlAvatar initials="SL" label="Avatar with initials: SL" />;
|
||||
```
|
||||
|
||||
### Custom Icons
|
||||
|
||||
When no image or initials are set, an icon will be shown. The default avatar shows a generic "user" icon, but you can customize this with the `icon` slot.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-avatar label="Avatar with an image icon">
|
||||
<sl-icon slot="icon" name="image"></sl-icon>
|
||||
</sl-avatar>
|
||||
@@ -76,8 +84,9 @@ When no image or initials are set, an icon will be shown. The default avatar sho
|
||||
</sl-avatar>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlAvatar, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
||||
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
@@ -100,14 +109,15 @@ const App = () => (
|
||||
|
||||
Avatars can be shaped using the `shape` attribute.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-avatar shape="square" label="Square avatar"></sl-avatar>
|
||||
<sl-avatar shape="rounded" label="Rounded avatar"></sl-avatar>
|
||||
<sl-avatar shape="circle" label="Circle avatar"></sl-avatar>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlAvatar, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
||||
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
@@ -122,24 +132,24 @@ const App = () => (
|
||||
|
||||
You can group avatars with a few lines of CSS.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="avatar-group">
|
||||
<sl-avatar
|
||||
<sl-avatar
|
||||
image="https://images.unsplash.com/photo-1490150028299-bf57d78394e0?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&q=80&crop=right"
|
||||
label="Avatar 1 of 4"
|
||||
></sl-avatar>
|
||||
|
||||
<sl-avatar
|
||||
<sl-avatar
|
||||
image="https://images.unsplash.com/photo-1503454537195-1dcabb73ffb9?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80"
|
||||
label="Avatar 2 of 4"
|
||||
></sl-avatar>
|
||||
|
||||
<sl-avatar
|
||||
<sl-avatar
|
||||
image="https://images.unsplash.com/photo-1456439663599-95b042d50252?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80"
|
||||
label="Avatar 3 of 4"
|
||||
></sl-avatar>
|
||||
|
||||
<sl-avatar
|
||||
<sl-avatar
|
||||
image="https://images.unsplash.com/flagged/photo-1554078875-e37cb8b0e27d?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=top&q=80"
|
||||
label="Avatar 4 of 4"
|
||||
></sl-avatar>
|
||||
@@ -156,8 +166,9 @@ You can group avatars with a few lines of CSS.
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlAvatar, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';
|
||||
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
const css = `
|
||||
.avatar-group sl-avatar:not(:first-of-type) {
|
||||
@@ -172,30 +183,28 @@ const css = `
|
||||
const App = () => (
|
||||
<>
|
||||
<div className="avatar-group">
|
||||
<SlAvatar
|
||||
image="https://images.unsplash.com/photo-1490150028299-bf57d78394e0?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&q=80&crop=right"
|
||||
label="Avatar 1 of 4"
|
||||
<SlAvatar
|
||||
image="https://images.unsplash.com/photo-1490150028299-bf57d78394e0?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&q=80&crop=right"
|
||||
label="Avatar 1 of 4"
|
||||
/>
|
||||
|
||||
<SlAvatar
|
||||
image="https://images.unsplash.com/photo-1503454537195-1dcabb73ffb9?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80"
|
||||
label="Avatar 2 of 4"
|
||||
<SlAvatar
|
||||
image="https://images.unsplash.com/photo-1503454537195-1dcabb73ffb9?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80"
|
||||
label="Avatar 2 of 4"
|
||||
/>
|
||||
|
||||
<SlAvatar
|
||||
image="https://images.unsplash.com/photo-1456439663599-95b042d50252?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80"
|
||||
label="Avatar 3 of 4"
|
||||
|
||||
<SlAvatar
|
||||
image="https://images.unsplash.com/photo-1456439663599-95b042d50252?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80"
|
||||
label="Avatar 3 of 4"
|
||||
/>
|
||||
|
||||
<SlAvatar
|
||||
image="https://images.unsplash.com/flagged/photo-1554078875-e37cb8b0e27d?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=top&q=80"
|
||||
label="Avatar 4 of 4"
|
||||
|
||||
<SlAvatar
|
||||
image="https://images.unsplash.com/flagged/photo-1554078875-e37cb8b0e27d?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=top&q=80"
|
||||
label="Avatar 4 of 4"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>{css}</style>
|
||||
<style>{css}</style>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-avatar]
|
||||
@@ -1,19 +1,18 @@
|
||||
# Badge
|
||||
---
|
||||
meta:
|
||||
title: Badge
|
||||
description: Badges are used to draw attention and display statuses or counts.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-badge]
|
||||
|
||||
Badges are used to draw attention and display statuses or counts.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-badge>Badge</sl-badge>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlBadge } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
|
||||
|
||||
const App = () => (
|
||||
<SlBadge>Badge</SlBadge>
|
||||
);
|
||||
const App = () => <SlBadge>Badge</SlBadge>;
|
||||
```
|
||||
|
||||
## Examples
|
||||
@@ -22,7 +21,7 @@ const App = () => (
|
||||
|
||||
Set the `variant` attribute to change the badge's variant.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-badge variant="primary">Primary</sl-badge>
|
||||
<sl-badge variant="success">Success</sl-badge>
|
||||
<sl-badge variant="neutral">Neutral</sl-badge>
|
||||
@@ -30,8 +29,8 @@ Set the `variant` attribute to change the badge's variant.
|
||||
<sl-badge variant="danger">Danger</sl-badge>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlBadge } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
@@ -39,7 +38,7 @@ const App = () => (
|
||||
<SlBadge variant="success">Success</SlBadge>
|
||||
<SlBadge variant="neutral">Neutral</SlBadge>
|
||||
<SlBadge variant="warning">Warning</SlBadge>
|
||||
<SlBadge variant="danger">Danger</SlBadge>
|
||||
<SlBadge variant="danger">Danger</SlBadge>
|
||||
</>
|
||||
);
|
||||
```
|
||||
@@ -48,7 +47,7 @@ const App = () => (
|
||||
|
||||
Use the `pill` attribute to give badges rounded edges.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-badge variant="primary" pill>Primary</sl-badge>
|
||||
<sl-badge variant="success" pill>Success</sl-badge>
|
||||
<sl-badge variant="neutral" pill>Neutral</sl-badge>
|
||||
@@ -56,16 +55,26 @@ Use the `pill` attribute to give badges rounded edges.
|
||||
<sl-badge variant="danger" pill>Danger</sl-badge>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlBadge } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlBadge variant="primary" pill>Primary</SlBadge>
|
||||
<SlBadge variant="success" pill>Success</SlBadge>
|
||||
<SlBadge variant="neutral" pill>Neutral</SlBadge>
|
||||
<SlBadge variant="warning" pill>Warning</SlBadge>
|
||||
<SlBadge variant="danger" pill>Danger</SlBadge>
|
||||
<SlBadge variant="primary" pill>
|
||||
Primary
|
||||
</SlBadge>
|
||||
<SlBadge variant="success" pill>
|
||||
Success
|
||||
</SlBadge>
|
||||
<SlBadge variant="neutral" pill>
|
||||
Neutral
|
||||
</SlBadge>
|
||||
<SlBadge variant="warning" pill>
|
||||
Warning
|
||||
</SlBadge>
|
||||
<SlBadge variant="danger" pill>
|
||||
Danger
|
||||
</SlBadge>
|
||||
</>
|
||||
);
|
||||
```
|
||||
@@ -74,7 +83,7 @@ const App = () => (
|
||||
|
||||
Use the `pulse` attribute to draw attention to the badge with a subtle animation.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="badge-pulse">
|
||||
<sl-badge variant="primary" pill pulse>1</sl-badge>
|
||||
<sl-badge variant="success" pill pulse>1</sl-badge>
|
||||
@@ -90,8 +99,8 @@ Use the `pulse` attribute to draw attention to the badge with a subtle animation
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlBadge } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
|
||||
|
||||
const css = `
|
||||
.badge-pulse sl-badge:not(:last-of-type) {
|
||||
@@ -102,11 +111,21 @@ const css = `
|
||||
const App = () => (
|
||||
<>
|
||||
<div className="badge-pulse">
|
||||
<SlBadge variant="primary" pill pulse>1</SlBadge>
|
||||
<SlBadge variant="success" pill pulse>1</SlBadge>
|
||||
<SlBadge variant="neutral" pill pulse>1</SlBadge>
|
||||
<SlBadge variant="warning" pill pulse>1</SlBadge>
|
||||
<SlBadge variant="danger" pill pulse>1</SlBadge>
|
||||
<SlBadge variant="primary" pill pulse>
|
||||
1
|
||||
</SlBadge>
|
||||
<SlBadge variant="success" pill pulse>
|
||||
1
|
||||
</SlBadge>
|
||||
<SlBadge variant="neutral" pill pulse>
|
||||
1
|
||||
</SlBadge>
|
||||
<SlBadge variant="warning" pill pulse>
|
||||
1
|
||||
</SlBadge>
|
||||
<SlBadge variant="danger" pill pulse>
|
||||
1
|
||||
</SlBadge>
|
||||
</div>
|
||||
|
||||
<style>{css}</style>
|
||||
@@ -118,25 +137,28 @@ const App = () => (
|
||||
|
||||
One of the most common use cases for badges is attaching them to buttons. To make this easier, badges will be automatically positioned at the top-right when they're a child of a button.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button>
|
||||
Requests
|
||||
<sl-badge pill>30</sl-badge>
|
||||
</sl-button>
|
||||
|
||||
<sl-button style="margin-left: 1rem;">
|
||||
<sl-button style="margin-inline-start: 1rem;">
|
||||
Warnings
|
||||
<sl-badge variant="warning" pill>8</sl-badge>
|
||||
</sl-button>
|
||||
|
||||
<sl-button style="margin-left: 1rem;">
|
||||
<sl-button style="margin-inline-start: 1rem;">
|
||||
Errors
|
||||
<sl-badge variant="danger" pill>6</sl-badge>
|
||||
</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlBadge, SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
@@ -145,36 +167,48 @@ const App = () => (
|
||||
<SlBadge pill>30</SlBadge>
|
||||
</SlButton>
|
||||
|
||||
<SlButton style={{ marginLeft: '1rem' }}>
|
||||
<SlButton style={{ marginInlineStart: '1rem' }}>
|
||||
Warnings
|
||||
<SlBadge variant="warning" pill>8</SlBadge>
|
||||
<SlBadge variant="warning" pill>
|
||||
8
|
||||
</SlBadge>
|
||||
</SlButton>
|
||||
|
||||
<SlButton style={{ marginLeft: '1rem' }}>
|
||||
<SlButton style={{ marginInlineStart: '1rem' }}>
|
||||
Errors
|
||||
<SlBadge variant="danger" pill>6</SlBadge>
|
||||
<SlBadge variant="danger" pill>
|
||||
6
|
||||
</SlBadge>
|
||||
</SlButton>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### With Menu Items
|
||||
|
||||
When including badges in menu items, use the `suffix` slot to make sure they're aligned correctly.
|
||||
|
||||
```html preview
|
||||
<sl-menu style="max-width: 240px; border: solid 1px var(--sl-panel-border-color); border-radius: var(--sl-border-radius-medium);">
|
||||
```html:preview
|
||||
<sl-menu style="max-width: 240px;">
|
||||
<sl-menu-label>Messages</sl-menu-label>
|
||||
<sl-menu-item>Comments <sl-badge slot="suffix" variant="neutral" pill>4</sl-badge></sl-menu-item>
|
||||
<sl-menu-item>Replies <sl-badge slot="suffix" variant="neutral" pill>12</sl-badge></sl-menu-item>
|
||||
</sl-menu>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlBadge, SlButton, SlMenu, SlMenuItem, SlMenuLabel } from '@shoelace-style/shoelace/dist/react';
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import SlBadge from '@shoelace-style/shoelace/dist/react/badge';
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||
import SlMenuLabel from '@shoelace-style/shoelace/dist/react/menu-label';
|
||||
|
||||
const App = () => (
|
||||
<SlMenu
|
||||
<SlMenu
|
||||
style={{
|
||||
maxWidth: '240px',
|
||||
border: 'solid 1px var(--sl-panel-border-color)',
|
||||
@@ -182,10 +216,20 @@ const App = () => (
|
||||
}}
|
||||
>
|
||||
<SlMenuLabel>Messages</SlMenuLabel>
|
||||
<SlMenuItem>Comments <SlBadge slot="suffix" variant="neutral" pill>4</SlBadge></SlMenuItem>
|
||||
<SlMenuItem>Replies <SlBadge slot="suffix" variant="neutral" pill>12</SlBadge></SlMenuItem>
|
||||
<SlMenuItem>
|
||||
Comments
|
||||
<SlBadge slot="suffix" variant="neutral" pill>
|
||||
4
|
||||
</SlBadge>
|
||||
</SlMenuItem>
|
||||
<SlMenuItem>
|
||||
Replies
|
||||
<SlBadge slot="suffix" variant="neutral" pill>
|
||||
12
|
||||
</SlBadge>
|
||||
</SlMenuItem>
|
||||
</SlMenu>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-badge]
|
||||
{% endraw %}
|
||||
@@ -1,10 +1,11 @@
|
||||
# Breadcrumb Item
|
||||
---
|
||||
meta:
|
||||
title: Breadcrumb Item
|
||||
description: Breadcrumb Items are used inside breadcrumbs to represent different links.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-breadcrumb-item]
|
||||
|
||||
Breadcrumb Items are used inside [breadcrumbs](/components/breadcrumb) to represent different links.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item>
|
||||
<sl-icon slot="prefix" name="house"></sl-icon>
|
||||
@@ -15,8 +16,10 @@ Breadcrumb Items are used inside [breadcrumbs](/components/breadcrumb) to repres
|
||||
</sl-breadcrumb>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlBreadcrumb, SlBreadcrumbItem, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
|
||||
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
|
||||
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
const App = () => (
|
||||
<SlBreadcrumb>
|
||||
@@ -30,6 +33,6 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
?> Additional demonstrations can be found in the [breadcrumb examples](/components/breadcrumb).
|
||||
|
||||
[component-metadata:sl-breadcrumb-item]
|
||||
:::tip
|
||||
Additional demonstrations can be found in the [breadcrumb examples](/components/breadcrumb).
|
||||
:::
|
||||
@@ -1,12 +1,13 @@
|
||||
# Breadcrumb
|
||||
|
||||
[component-header:sl-breadcrumb]
|
||||
|
||||
Breadcrumbs provide a group of links so users can easily navigate a website's hierarchy.
|
||||
---
|
||||
meta:
|
||||
title: Breadcrumb
|
||||
description: Breadcrumbs provide a group of links so users can easily navigate a website's hierarchy.
|
||||
layout: component
|
||||
---
|
||||
|
||||
Breadcrumbs are usually placed before a page's main content with the current page shown last to indicate the user's position in the navigation.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item>Catalog</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>Clothing</sl-breadcrumb-item>
|
||||
@@ -15,8 +16,9 @@ Breadcrumbs are usually placed before a page's main content with the current pag
|
||||
</sl-breadcrumb>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlBreadcrumb, SlBreadcrumbItem } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
|
||||
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
|
||||
|
||||
const App = () => (
|
||||
<SlBreadcrumb>
|
||||
@@ -36,46 +38,31 @@ By default, breadcrumb items are rendered as buttons so you can use them to navi
|
||||
|
||||
For websites, you'll probably want to use links instead. You can make any breadcrumb item a link by applying an `href` attribute to it. Now, when the user activates it, they'll be taken to the corresponding page — no event listeners required.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item href="https://example.com/home">
|
||||
Homepage
|
||||
</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item href="https://example.com/home">Homepage</sl-breadcrumb-item>
|
||||
|
||||
<sl-breadcrumb-item href="https://example.com/home/services">
|
||||
Our Services
|
||||
</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item href="https://example.com/home/services">Our Services</sl-breadcrumb-item>
|
||||
|
||||
<sl-breadcrumb-item href="https://example.com/home/services/digital">
|
||||
Digital Media
|
||||
</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item href="https://example.com/home/services/digital">Digital Media</sl-breadcrumb-item>
|
||||
|
||||
<sl-breadcrumb-item href="https://example.com/home/services/digital/web-design">
|
||||
Web Design
|
||||
</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item href="https://example.com/home/services/digital/web-design">Web Design</sl-breadcrumb-item>
|
||||
</sl-breadcrumb>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlBreadcrumb, SlBreadcrumbItem } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
|
||||
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
|
||||
|
||||
const App = () => (
|
||||
<SlBreadcrumb>
|
||||
<SlBreadcrumbItem href="https://example.com/home">
|
||||
Homepage
|
||||
</SlBreadcrumbItem>
|
||||
<SlBreadcrumbItem href="https://example.com/home">Homepage</SlBreadcrumbItem>
|
||||
|
||||
<SlBreadcrumbItem href="https://example.com/home/services">
|
||||
Our Services
|
||||
</SlBreadcrumbItem>
|
||||
<SlBreadcrumbItem href="https://example.com/home/services">Our Services</SlBreadcrumbItem>
|
||||
|
||||
<SlBreadcrumbItem href="https://example.com/home/services/digital">
|
||||
Digital Media
|
||||
</SlBreadcrumbItem>
|
||||
<SlBreadcrumbItem href="https://example.com/home/services/digital">Digital Media</SlBreadcrumbItem>
|
||||
|
||||
<SlBreadcrumbItem href="https://example.com/home/services/digital/web-design">
|
||||
Web Design
|
||||
</SlBreadcrumbItem>
|
||||
<SlBreadcrumbItem href="https://example.com/home/services/digital/web-design">Web Design</SlBreadcrumbItem>
|
||||
</SlBreadcrumb>
|
||||
);
|
||||
```
|
||||
@@ -84,36 +71,37 @@ const App = () => (
|
||||
|
||||
Use the `separator` slot to change the separator that goes between breadcrumb items. Icons work well, but you can also use text or an image.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-breadcrumb>
|
||||
<sl-icon name="dot" slot="separator"></sl-icon>
|
||||
<sl-breadcrumb-item>First</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>Second</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>Third</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>Third</sl-breadcrumb-item>
|
||||
</sl-breadcrumb>
|
||||
|
||||
<br>
|
||||
<br />
|
||||
|
||||
<sl-breadcrumb>
|
||||
<sl-icon name="arrow-right" slot="separator"></sl-icon>
|
||||
<sl-breadcrumb-item>First</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>Second</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>Third</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>Third</sl-breadcrumb-item>
|
||||
</sl-breadcrumb>
|
||||
|
||||
<br>
|
||||
<br />
|
||||
|
||||
<sl-breadcrumb>
|
||||
<span slot="separator">/</span>
|
||||
<sl-breadcrumb-item>First</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>Second</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>Third</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>Third</sl-breadcrumb-item>
|
||||
</sl-breadcrumb>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import '@shoelace-style/shoelace/dist/components/icon/icon.js';
|
||||
import { SlBreadcrumb, SlBreadcrumbItem } from '@shoelace-style/shoelace/dist/react';
|
||||
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
|
||||
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
@@ -121,7 +109,7 @@ const App = () => (
|
||||
<sl-icon name="dot" slot="separator" />
|
||||
<SlBreadcrumbItem>First</SlBreadcrumbItem>
|
||||
<SlBreadcrumbItem>Second</SlBreadcrumbItem>
|
||||
<SlBreadcrumbItem>Third</SlBreadcrumbItem>
|
||||
<SlBreadcrumbItem>Third</SlBreadcrumbItem>
|
||||
</SlBreadcrumb>
|
||||
|
||||
<br />
|
||||
@@ -130,7 +118,7 @@ const App = () => (
|
||||
<sl-icon name="arrow-right" slot="separator" />
|
||||
<SlBreadcrumbItem>First</SlBreadcrumbItem>
|
||||
<SlBreadcrumbItem>Second</SlBreadcrumbItem>
|
||||
<SlBreadcrumbItem>Third</SlBreadcrumbItem>
|
||||
<SlBreadcrumbItem>Third</SlBreadcrumbItem>
|
||||
</SlBreadcrumb>
|
||||
|
||||
<br />
|
||||
@@ -139,7 +127,7 @@ const App = () => (
|
||||
<span slot="separator">/</span>
|
||||
<SlBreadcrumbItem>First</SlBreadcrumbItem>
|
||||
<SlBreadcrumbItem>Second</SlBreadcrumbItem>
|
||||
<SlBreadcrumbItem>Third</SlBreadcrumbItem>
|
||||
<SlBreadcrumbItem>Third</SlBreadcrumbItem>
|
||||
</SlBreadcrumb>
|
||||
</>
|
||||
);
|
||||
@@ -149,7 +137,7 @@ const App = () => (
|
||||
|
||||
Use the `prefix` slot to add content before any breadcrumb item.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item>
|
||||
<sl-icon slot="prefix" name="house"></sl-icon>
|
||||
@@ -160,8 +148,10 @@ Use the `prefix` slot to add content before any breadcrumb item.
|
||||
</sl-breadcrumb>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlBreadcrumb, SlBreadcrumbItem, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
|
||||
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
|
||||
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
const App = () => (
|
||||
<SlBreadcrumb>
|
||||
@@ -179,7 +169,7 @@ const App = () => (
|
||||
|
||||
Use the `suffix` slot to add content after any breadcrumb item.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item>Documents</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>Policies</sl-breadcrumb-item>
|
||||
@@ -190,8 +180,10 @@ Use the `suffix` slot to add content after any breadcrumb item.
|
||||
</sl-breadcrumb>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlBreadcrumb, SlBreadcrumbItem, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';
|
||||
import SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';
|
||||
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
const App = () => (
|
||||
<SlBreadcrumb>
|
||||
@@ -209,7 +201,7 @@ const App = () => (
|
||||
|
||||
Dropdown menus can be placed in a prefix or suffix slot to provide additional options.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item>Homepage</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>Our Services</sl-breadcrumb-item>
|
||||
@@ -221,19 +213,19 @@ 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-dropdown>
|
||||
</sl-breadcrumb-item>
|
||||
</sl-breadcrumb>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import {
|
||||
SlBreadcrumb,
|
||||
SlBreadcrumbItem,
|
||||
```jsx:react
|
||||
import {
|
||||
SlBreadcrumb,
|
||||
SlBreadcrumbItem,
|
||||
SlButton,
|
||||
SlDropdown,
|
||||
SlIcon,
|
||||
@@ -253,14 +245,14 @@ 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>
|
||||
</SlDropdown>
|
||||
</SlBreadcrumbItem>
|
||||
</SlBreadcrumb>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-breadcrumb]
|
||||
@@ -1,22 +1,24 @@
|
||||
# Button Group
|
||||
---
|
||||
meta:
|
||||
title: Button Group
|
||||
description: Button groups can be used to group related buttons into sections.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-button-group]
|
||||
|
||||
Button groups can be used to group related buttons into sections.
|
||||
|
||||
```html preview
|
||||
<sl-button-group>
|
||||
```html:preview
|
||||
<sl-button-group label="Alignment">
|
||||
<sl-button>Left</sl-button>
|
||||
<sl-button>Center</sl-button>
|
||||
<sl-button>Right</sl-button>
|
||||
</sl-button-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton, SlButtonGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
|
||||
|
||||
const App = () => (
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton>Left</SlButton>
|
||||
<SlButton>Center</SlButton>
|
||||
<SlButton>Right</SlButton>
|
||||
@@ -30,52 +32,55 @@ const App = () => (
|
||||
|
||||
All button sizes are supported, but avoid mixing sizes within the same button group.
|
||||
|
||||
```html preview
|
||||
<sl-button-group>
|
||||
```html:preview
|
||||
<sl-button-group label="Alignment">
|
||||
<sl-button size="small">Left</sl-button>
|
||||
<sl-button size="small">Center</sl-button>
|
||||
<sl-button size="small">Right</sl-button>
|
||||
</sl-button-group>
|
||||
|
||||
<br><br>
|
||||
<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>
|
||||
</sl-button-group>
|
||||
|
||||
<br><br>
|
||||
<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>
|
||||
</sl-button-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton, SlButtonGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton size="small">Left</SlButton>
|
||||
<SlButton size="small">Center</SlButton>
|
||||
<SlButton size="small">Right</SlButton>
|
||||
</SlButtonGroup>
|
||||
|
||||
<br /><br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton size="medium">Left</SlButton>
|
||||
<SlButton size="medium">Center</SlButton>
|
||||
<SlButton size="medium">Right</SlButton>
|
||||
</SlButtonGroup>
|
||||
|
||||
<br /><br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton size="large">Left</SlButton>
|
||||
<SlButton size="large">Center</SlButton>
|
||||
<SlButton size="large">Right</SlButton>
|
||||
@@ -86,86 +91,91 @@ 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>
|
||||
```html:preview
|
||||
<sl-button-group label="Alignment">
|
||||
<sl-button variant="primary">Left</sl-button>
|
||||
<sl-button variant="primary">Center</sl-button>
|
||||
<sl-button variant="primary">Right</sl-button>
|
||||
</sl-button-group>
|
||||
|
||||
<br><br>
|
||||
<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>
|
||||
</sl-button-group>
|
||||
|
||||
<br><br>
|
||||
<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>
|
||||
</sl-button-group>
|
||||
|
||||
<br><br>
|
||||
<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>
|
||||
</sl-button-group>
|
||||
|
||||
<br><br>
|
||||
<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>
|
||||
</sl-button-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton, SlButtonGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton variant="primary">Left</SlButton>
|
||||
<SlButton variant="primary">Center</SlButton>
|
||||
<SlButton variant="primary">Right</SlButton>
|
||||
</SlButtonGroup>
|
||||
|
||||
<br /><br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton variant="success">Left</SlButton>
|
||||
<SlButton variant="success">Center</SlButton>
|
||||
<SlButton variant="success">Right</SlButton>
|
||||
</SlButtonGroup>
|
||||
|
||||
<br /><br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton variant="neutral">Left</SlButton>
|
||||
<SlButton variant="neutral">Center</SlButton>
|
||||
<SlButton variant="neutral">Right</SlButton>
|
||||
</SlButtonGroup>
|
||||
|
||||
<br /><br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton variant="warning">Left</SlButton>
|
||||
<SlButton variant="warning">Center</SlButton>
|
||||
<SlButton variant="warning">Right</SlButton>
|
||||
</SlButtonGroup>
|
||||
|
||||
<br /><br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton variant="danger">Left</SlButton>
|
||||
<SlButton variant="danger">Center</SlButton>
|
||||
<SlButton variant="danger">Right</SlButton>
|
||||
@@ -178,55 +188,76 @@ const App = () => (
|
||||
|
||||
Pill buttons are supported through the button's `pill` attribute.
|
||||
|
||||
```html preview
|
||||
<sl-button-group>
|
||||
```html:preview
|
||||
<sl-button-group label="Alignment">
|
||||
<sl-button size="small" pill>Left</sl-button>
|
||||
<sl-button size="small" pill>Center</sl-button>
|
||||
<sl-button size="small" pill>Right</sl-button>
|
||||
</sl-button-group>
|
||||
|
||||
<br><br>
|
||||
<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>
|
||||
</sl-button-group>
|
||||
|
||||
<br><br>
|
||||
<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>
|
||||
</sl-button-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton, SlButtonGroup } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlButtonGroup>
|
||||
<SlButton size="small" pill>Left</SlButton>
|
||||
<SlButton size="small" pill>Center</SlButton>
|
||||
<SlButton size="small" pill>Right</SlButton>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton size="small" pill>
|
||||
Left
|
||||
</SlButton>
|
||||
<SlButton size="small" pill>
|
||||
Center
|
||||
</SlButton>
|
||||
<SlButton size="small" pill>
|
||||
Right
|
||||
</SlButton>
|
||||
</SlButtonGroup>
|
||||
|
||||
<br /><br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButtonGroup>
|
||||
<SlButton size="medium" pill>Left</SlButton>
|
||||
<SlButton size="medium" pill>Center</SlButton>
|
||||
<SlButton size="medium" pill>Right</SlButton>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton size="medium" pill>
|
||||
Left
|
||||
</SlButton>
|
||||
<SlButton size="medium" pill>
|
||||
Center
|
||||
</SlButton>
|
||||
<SlButton size="medium" pill>
|
||||
Right
|
||||
</SlButton>
|
||||
</SlButtonGroup>
|
||||
|
||||
<br /><br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButtonGroup>
|
||||
<SlButton size="large" pill>Left</SlButton>
|
||||
<SlButton size="large" pill>Center</SlButton>
|
||||
<SlButton size="large" pill>Right</SlButton>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlButton size="large" pill>
|
||||
Left
|
||||
</SlButton>
|
||||
<SlButton size="large" pill>
|
||||
Center
|
||||
</SlButton>
|
||||
<SlButton size="large" pill>
|
||||
Right
|
||||
</SlButton>
|
||||
</SlButtonGroup>
|
||||
</>
|
||||
);
|
||||
@@ -236,8 +267,8 @@ const App = () => (
|
||||
|
||||
Dropdowns can be placed inside button groups as long as the trigger is an `<sl-button>` element.
|
||||
|
||||
```html preview
|
||||
<sl-button-group>
|
||||
```html:preview
|
||||
<sl-button-group label="Example Button Group">
|
||||
<sl-button>Button</sl-button>
|
||||
<sl-button>Button</sl-button>
|
||||
<sl-dropdown>
|
||||
@@ -251,21 +282,21 @@ Dropdowns can be placed inside button groups as long as the trigger is an `<sl-b
|
||||
</sl-button-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import {
|
||||
SlButton,
|
||||
SlButtonGroup,
|
||||
SlDropdown,
|
||||
SlMenu,
|
||||
SlMenuItem,
|
||||
} from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
|
||||
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
|
||||
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||
|
||||
const App = () => (
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Example Button Group">
|
||||
<SlButton>Button</SlButton>
|
||||
<SlButton>Button</SlButton>
|
||||
<SlDropdown>
|
||||
<SlButton slot="trigger" caret>Dropdown</SlButton>
|
||||
<SlButton slot="trigger" caret>
|
||||
Dropdown
|
||||
</SlButton>
|
||||
<SlMenu>
|
||||
<SlMenuItem>Item 1</SlMenuItem>
|
||||
<SlMenuItem>Item 2</SlMenuItem>
|
||||
@@ -278,13 +309,15 @@ const App = () => (
|
||||
|
||||
### Split Buttons
|
||||
|
||||
Create a split button using a button and a dropdown.
|
||||
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>
|
||||
```html:preview
|
||||
<sl-button-group label="Example Button Group">
|
||||
<sl-button variant="primary">Save</sl-button>
|
||||
<sl-dropdown placement="bottom-end">
|
||||
<sl-button slot="trigger" variant="primary" caret></sl-button>
|
||||
<sl-button slot="trigger" variant="primary" caret>
|
||||
<sl-visually-hidden>More options</sl-visually-hidden>
|
||||
</sl-button>
|
||||
<sl-menu>
|
||||
<sl-menu-item>Save</sl-menu-item>
|
||||
<sl-menu-item>Save as…</sl-menu-item>
|
||||
@@ -294,17 +327,15 @@ Create a split button using a button and a dropdown.
|
||||
</sl-button-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import {
|
||||
SlButton,
|
||||
SlButtonGroup,
|
||||
SlDropdown,
|
||||
SlMenu,
|
||||
SlMenuItem,
|
||||
} from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
|
||||
import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';
|
||||
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||
|
||||
const App = () => (
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Example Button Group">
|
||||
<SlButton variant="primary">Save</SlButton>
|
||||
<SlDropdown placement="bottom-end">
|
||||
<SlButton slot="trigger" variant="primary" caret></SlButton>
|
||||
@@ -322,8 +353,8 @@ const App = () => (
|
||||
|
||||
Buttons can be wrapped in tooltips to provide more detail when the user interacts with them.
|
||||
|
||||
```html preview
|
||||
<sl-button-group>
|
||||
```html:preview
|
||||
<sl-button-group label="Alignment">
|
||||
<sl-tooltip content="I'm on the left">
|
||||
<sl-button>Left</sl-button>
|
||||
</sl-tooltip>
|
||||
@@ -338,16 +369,14 @@ Buttons can be wrapped in tooltips to provide more detail when the user interact
|
||||
</sl-button-group>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import {
|
||||
SlButton,
|
||||
SlButtonGroup,
|
||||
SlTooltip
|
||||
} from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
|
||||
import SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlButtonGroup>
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlTooltip content="I'm on the left">
|
||||
<SlButton>Left</SlButton>
|
||||
</SlTooltip>
|
||||
@@ -368,38 +397,38 @@ const App = () => (
|
||||
|
||||
Create interactive toolbars with button groups.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="button-group-toolbar">
|
||||
<sl-button-group label="History">
|
||||
<sl-tooltip content="Undo">
|
||||
<sl-button><sl-icon name="arrow-counterclockwise"></sl-icon></sl-button>
|
||||
<sl-button><sl-icon name="arrow-counterclockwise" label="Undo"></sl-icon></sl-button>
|
||||
</sl-tooltip>
|
||||
<sl-tooltip content="Redo">
|
||||
<sl-button><sl-icon name="arrow-clockwise"></sl-icon></sl-button>
|
||||
<sl-button><sl-icon name="arrow-clockwise" label="Redo"></sl-icon></sl-button>
|
||||
</sl-tooltip>
|
||||
</sl-button-group>
|
||||
|
||||
<sl-button-group label="Formatting">
|
||||
<sl-tooltip content="Bold">
|
||||
<sl-button><sl-icon name="type-bold"></sl-icon></sl-button>
|
||||
<sl-button><sl-icon name="type-bold" label="Bold"></sl-icon></sl-button>
|
||||
</sl-tooltip>
|
||||
<sl-tooltip content="Italic">
|
||||
<sl-button><sl-icon name="type-italic"></sl-icon></sl-button>
|
||||
<sl-button><sl-icon name="type-italic" label="Italic"></sl-icon></sl-button>
|
||||
</sl-tooltip>
|
||||
<sl-tooltip content="Underline">
|
||||
<sl-button><sl-icon name="type-underline"></sl-icon></sl-button>
|
||||
<sl-button><sl-icon name="type-underline" label="Underline"></sl-icon></sl-button>
|
||||
</sl-tooltip>
|
||||
</sl-button-group>
|
||||
|
||||
<sl-button-group label="Alignment">
|
||||
<sl-tooltip content="Align Left">
|
||||
<sl-button><sl-icon name="justify-left"></sl-icon></sl-button>
|
||||
<sl-button><sl-icon name="justify-left" label="Align Left"></sl-icon></sl-button>
|
||||
</sl-tooltip>
|
||||
<sl-tooltip content="Align Center">
|
||||
<sl-button><sl-icon name="justify"></sl-icon></sl-button>
|
||||
<sl-button><sl-icon name="justify" label="Align Center"></sl-icon></sl-button>
|
||||
</sl-tooltip>
|
||||
<sl-tooltip content="Align Right">
|
||||
<sl-button><sl-icon name="justify-right"></sl-icon></sl-button>
|
||||
<sl-button><sl-icon name="justify-right" label="Align Right"></sl-icon></sl-button>
|
||||
</sl-tooltip>
|
||||
</sl-button-group>
|
||||
</div>
|
||||
@@ -411,13 +440,11 @@ Create interactive toolbars with button groups.
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import {
|
||||
SlButton,
|
||||
SlButtonGroup,
|
||||
SlIcon,
|
||||
SlTooltip
|
||||
} from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';
|
||||
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
import SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';
|
||||
|
||||
const css = `
|
||||
.button-group-toolbar sl-button-group:not(:last-of-type) {
|
||||
@@ -430,34 +457,50 @@ const App = () => (
|
||||
<div className="button-group-toolbar">
|
||||
<SlButtonGroup label="History">
|
||||
<SlTooltip content="Undo">
|
||||
<SlButton><SlIcon name="arrow-counterclockwise"></SlIcon></SlButton>
|
||||
<SlButton>
|
||||
<SlIcon name="arrow-counterclockwise"></SlIcon>
|
||||
</SlButton>
|
||||
</SlTooltip>
|
||||
<SlTooltip content="Redo">
|
||||
<SlButton><SlIcon name="arrow-clockwise"></SlIcon></SlButton>
|
||||
<SlButton>
|
||||
<SlIcon name="arrow-clockwise"></SlIcon>
|
||||
</SlButton>
|
||||
</SlTooltip>
|
||||
</SlButtonGroup>
|
||||
|
||||
<SlButtonGroup label="Formatting">
|
||||
<SlTooltip content="Bold">
|
||||
<SlButton><SlIcon name="type-bold"></SlIcon></SlButton>
|
||||
<SlButton>
|
||||
<SlIcon name="type-bold"></SlIcon>
|
||||
</SlButton>
|
||||
</SlTooltip>
|
||||
<SlTooltip content="Italic">
|
||||
<SlButton><SlIcon name="type-italic"></SlIcon></SlButton>
|
||||
<SlButton>
|
||||
<SlIcon name="type-italic"></SlIcon>
|
||||
</SlButton>
|
||||
</SlTooltip>
|
||||
<SlTooltip content="Underline">
|
||||
<SlButton><SlIcon name="type-underline"></SlIcon></SlButton>
|
||||
<SlButton>
|
||||
<SlIcon name="type-underline"></SlIcon>
|
||||
</SlButton>
|
||||
</SlTooltip>
|
||||
</SlButtonGroup>
|
||||
|
||||
<SlButtonGroup label="Alignment">
|
||||
<SlTooltip content="Align Left">
|
||||
<SlButton><SlIcon name="justify-left"></SlIcon></SlButton>
|
||||
<SlButton>
|
||||
<SlIcon name="justify-left"></SlIcon>
|
||||
</SlButton>
|
||||
</SlTooltip>
|
||||
<SlTooltip content="Align Center">
|
||||
<SlButton><SlIcon name="justify"></SlIcon></SlButton>
|
||||
<SlButton>
|
||||
<SlIcon name="justify"></SlIcon>
|
||||
</SlButton>
|
||||
</SlTooltip>
|
||||
<SlTooltip content="Align Right">
|
||||
<SlButton><SlIcon name="justify-right"></SlIcon></SlButton>
|
||||
<SlButton>
|
||||
<SlIcon name="justify-right"></SlIcon>
|
||||
</SlButton>
|
||||
</SlTooltip>
|
||||
</SlButtonGroup>
|
||||
</div>
|
||||
@@ -466,5 +509,3 @@ const App = () => (
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-button-group]
|
||||
@@ -1,19 +1,18 @@
|
||||
# Button
|
||||
---
|
||||
meta:
|
||||
title: Button
|
||||
description: Buttons represent actions that are available to the user.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-button]
|
||||
|
||||
Buttons represent actions that are available to the user.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button>Button</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
|
||||
const App = () => (
|
||||
<SlButton>Button</SlButton>
|
||||
);
|
||||
const App = () => <SlButton>Button</SlButton>;
|
||||
```
|
||||
|
||||
## Examples
|
||||
@@ -22,7 +21,7 @@ const App = () => (
|
||||
|
||||
Use the `variant` attribute to set the button's variant.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button variant="default">Default</sl-button>
|
||||
<sl-button variant="primary">Primary</sl-button>
|
||||
<sl-button variant="success">Success</sl-button>
|
||||
@@ -31,8 +30,8 @@ Use the `variant` attribute to set the button's variant.
|
||||
<sl-button variant="danger">Danger</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
@@ -41,7 +40,7 @@ const App = () => (
|
||||
<SlButton variant="success">Success</SlButton>
|
||||
<SlButton variant="neutral">Neutral</SlButton>
|
||||
<SlButton variant="warning">Warning</SlButton>
|
||||
<SlButton variant="danger">Danger</SlButton>
|
||||
<SlButton variant="danger">Danger</SlButton>
|
||||
</>
|
||||
);
|
||||
```
|
||||
@@ -50,14 +49,14 @@ const App = () => (
|
||||
|
||||
Use the `size` attribute to change a button's size.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button size="small">Small</sl-button>
|
||||
<sl-button size="medium">Medium</sl-button>
|
||||
<sl-button size="large">Large</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
@@ -72,7 +71,7 @@ const App = () => (
|
||||
|
||||
Use the `outline` attribute to draw outlined buttons with transparent backgrounds.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button variant="default" outline>Default</sl-button>
|
||||
<sl-button variant="primary" outline>Primary</sl-button>
|
||||
<sl-button variant="success" outline>Success</sl-button>
|
||||
@@ -81,17 +80,29 @@ Use the `outline` attribute to draw outlined buttons with transparent background
|
||||
<sl-button variant="danger" outline>Danger</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlButton variant="default" outline>Default</SlButton>
|
||||
<SlButton variant="primary" outline>Primary</SlButton>
|
||||
<SlButton variant="success" outline>Success</SlButton>
|
||||
<SlButton variant="neutral" outline>Neutral</SlButton>
|
||||
<SlButton variant="warning" outline>Warning</SlButton>
|
||||
<SlButton variant="danger" outline>Danger</SlButton>
|
||||
<SlButton variant="default" outline>
|
||||
Default
|
||||
</SlButton>
|
||||
<SlButton variant="primary" outline>
|
||||
Primary
|
||||
</SlButton>
|
||||
<SlButton variant="success" outline>
|
||||
Success
|
||||
</SlButton>
|
||||
<SlButton variant="neutral" outline>
|
||||
Neutral
|
||||
</SlButton>
|
||||
<SlButton variant="warning" outline>
|
||||
Warning
|
||||
</SlButton>
|
||||
<SlButton variant="danger" outline>
|
||||
Danger
|
||||
</SlButton>
|
||||
</>
|
||||
);
|
||||
```
|
||||
@@ -100,42 +111,63 @@ const App = () => (
|
||||
|
||||
Use the `pill` attribute to give buttons rounded edges.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button size="small" pill>Small</sl-button>
|
||||
<sl-button size="medium" pill>Medium</sl-button>
|
||||
<sl-button size="large" pill>Large</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlButton size="small" pill>Small</SlButton>
|
||||
<SlButton size="medium" pill>Medium</SlButton>
|
||||
<SlButton size="large" pill>Large</SlButton>
|
||||
<SlButton size="small" pill>
|
||||
Small
|
||||
</SlButton>
|
||||
<SlButton size="medium" pill>
|
||||
Medium
|
||||
</SlButton>
|
||||
<SlButton size="large" pill>
|
||||
Large
|
||||
</SlButton>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Circle Buttons
|
||||
|
||||
Use the `circle` attribute to create circular icon buttons.
|
||||
Use the `circle` attribute to create circular icon buttons. When this attribute is set, the button expects a single `<sl-icon>` in the default slot.
|
||||
|
||||
```html preview
|
||||
<sl-button variant="default" size="small" circle><sl-icon name="gear"></sl-icon></sl-button>
|
||||
<sl-button variant="default" size="medium" circle><sl-icon name="gear"></sl-icon></sl-button>
|
||||
<sl-button variant="default" size="large" circle><sl-icon name="gear"></sl-icon></sl-button>
|
||||
```html:preview
|
||||
<sl-button variant="default" size="small" circle>
|
||||
<sl-icon name="gear" label="Settings"></sl-icon>
|
||||
</sl-button>
|
||||
|
||||
<sl-button variant="default" size="medium" circle>
|
||||
<sl-icon name="gear" label="Settings"></sl-icon>
|
||||
</sl-button>
|
||||
|
||||
<sl-button variant="default" size="large" circle>
|
||||
<sl-icon name="gear" label="Settings"></sl-icon>
|
||||
</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlButton variant="default" size="small" circle><SlIcon name="gear" /></SlButton>
|
||||
<SlButton variant="default" size="medium" circle><SlIcon name="gear" /></SlButton>
|
||||
<SlButton variant="default" size="large" circle><SlIcon name="gear" /></SlButton>
|
||||
<SlButton variant="default" size="small" circle>
|
||||
<SlIcon name="gear" />
|
||||
</SlButton>
|
||||
<SlButton variant="default" size="medium" circle>
|
||||
<SlIcon name="gear" />
|
||||
</SlButton>
|
||||
<SlButton variant="default" size="large" circle>
|
||||
<SlIcon name="gear" />
|
||||
</SlButton>
|
||||
</>
|
||||
);
|
||||
```
|
||||
@@ -144,77 +176,101 @@ const App = () => (
|
||||
|
||||
Use the `text` variant to create text buttons that share the same size as regular buttons but don't have backgrounds or borders.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button variant="text" size="small">Text</sl-button>
|
||||
<sl-button variant="text" size="medium">Text</sl-button>
|
||||
<sl-button variant="text" size="large">Text</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlButton variant="text" size="small">Text</SlButton>
|
||||
<SlButton variant="text" size="medium">Text</SlButton>
|
||||
<SlButton variant="text" size="large">Text</SlButton>
|
||||
<SlButton variant="text" size="small">
|
||||
Text
|
||||
</SlButton>
|
||||
<SlButton variant="text" size="medium">
|
||||
Text
|
||||
</SlButton>
|
||||
<SlButton variant="text" size="large">
|
||||
Text
|
||||
</SlButton>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Link Buttons
|
||||
|
||||
It's often helpful to have a button that works like a link. This is possible by setting the `href` attribute, which will make the component render an `<a>` under the hood. This gives you all the default link behavior the browser provides (e.g. <kbd>CMD/CTRL/SHIFT + CLICK</kbd>) and exposes the `target` and `download` attributes.
|
||||
It's often helpful to have a button that works like a link. This is possible by setting the `href` attribute, which will make the component render an `<a>` under the hood. This gives you all the default link behavior the browser provides (e.g. [[CMD/CTRL/SHIFT]] + [[CLICK]]) and exposes the `target` and `download` attributes.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button href="https://example.com/">Link</sl-button>
|
||||
<sl-button href="https://example.com/" target="_blank">New Window</sl-button>
|
||||
<sl-button href="/assets/images/wordmark.svg" download="shoelace.svg">Download</sl-button>
|
||||
<sl-button href="https://example.com/" disabled>Disabled</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlButton href="https://example.com/">Link</SlButton>
|
||||
<SlButton href="https://example.com/" target="_blank">New Window</SlButton>
|
||||
<SlButton href="/assets/images/wordmark.svg" download="shoelace.svg">Download</SlButton>
|
||||
<SlButton href="https://example.com/" disabled>Disabled</SlButton>
|
||||
<SlButton href="https://example.com/" target="_blank">
|
||||
New Window
|
||||
</SlButton>
|
||||
<SlButton href="/assets/images/wordmark.svg" download="shoelace.svg">
|
||||
Download
|
||||
</SlButton>
|
||||
<SlButton href="https://example.com/" disabled>
|
||||
Disabled
|
||||
</SlButton>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
?> When a `target` is set, the link will receive `rel="noreferrer noopener"` for [security reasons](https://mathiasbynens.github.io/rel-noopener/).
|
||||
:::tip
|
||||
When a `target` is set, the link will receive `rel="noreferrer noopener"` for [security reasons](https://mathiasbynens.github.io/rel-noopener/).
|
||||
:::
|
||||
|
||||
### Setting a Custom Width
|
||||
|
||||
As expected, buttons can be given a custom width by setting the `width` attribute. This is useful for making buttons span the full width of their container on smaller screens.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button variant="default" size="small" style="width: 100%; margin-bottom: 1rem;">Small</sl-button>
|
||||
<sl-button variant="default" size="medium" style="width: 100%; margin-bottom: 1rem;">Medium</sl-button>
|
||||
<sl-button variant="default" size="large" style="width: 100%;">Large</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlButton variant="default" size="small" style={{ width: '100%', marginBottom: '1rem' }}>Small</SlButton>
|
||||
<SlButton variant="default" size="medium" style={{ width: '100%', marginBottom: '1rem' }}>Medium</SlButton>
|
||||
<SlButton variant="default" size="large" style={{ width: '100%' }}>Large</SlButton>
|
||||
<SlButton variant="default" size="small" style={{ width: '100%', marginBottom: '1rem' }}>
|
||||
Small
|
||||
</SlButton>
|
||||
<SlButton variant="default" size="medium" style={{ width: '100%', marginBottom: '1rem' }}>
|
||||
Medium
|
||||
</SlButton>
|
||||
<SlButton variant="default" size="large" style={{ width: '100%' }}>
|
||||
Large
|
||||
</SlButton>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Prefix and Suffix Icons
|
||||
|
||||
Use the `prefix` and `suffix` slots to add icons.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button variant="default" size="small">
|
||||
<sl-icon slot="prefix" name="gear"></sl-icon>
|
||||
Settings
|
||||
@@ -231,7 +287,7 @@ Use the `prefix` and `suffix` slots to add icons.
|
||||
Open
|
||||
</sl-button>
|
||||
|
||||
<br><br>
|
||||
<br /><br />
|
||||
|
||||
<sl-button variant="default">
|
||||
<sl-icon slot="prefix" name="gear"></sl-icon>
|
||||
@@ -249,7 +305,7 @@ Use the `prefix` and `suffix` slots to add icons.
|
||||
Open
|
||||
</sl-button>
|
||||
|
||||
<br><br>
|
||||
<br /><br />
|
||||
|
||||
<sl-button variant="default" size="large">
|
||||
<sl-icon slot="prefix" name="gear"></sl-icon>
|
||||
@@ -268,8 +324,9 @@ Use the `prefix` and `suffix` slots to add icons.
|
||||
</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton, SlIcon } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
@@ -289,7 +346,8 @@ const App = () => (
|
||||
Open
|
||||
</SlButton>
|
||||
|
||||
<br /><br/ >
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButton variant="default">
|
||||
<SlIcon slot="prefix" name="gear"></SlIcon>
|
||||
@@ -307,7 +365,8 @@ const App = () => (
|
||||
Open
|
||||
</SlButton>
|
||||
|
||||
<br /><br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<SlButton variant="default" size="large">
|
||||
<SlIcon slot="prefix" name="gear"></SlIcon>
|
||||
@@ -332,20 +391,26 @@ const App = () => (
|
||||
|
||||
Use the `caret` attribute to add a dropdown indicator when a button will trigger a dropdown, menu, or popover.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button size="small" caret>Small</sl-button>
|
||||
<sl-button size="medium" caret>Medium</sl-button>
|
||||
<sl-button size="large" caret>Large</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlButton size="small" caret>Small</SlButton>
|
||||
<SlButton size="medium" caret>Medium</SlButton>
|
||||
<SlButton size="large" caret>Large</SlButton>
|
||||
<SlButton size="small" caret>
|
||||
Small
|
||||
</SlButton>
|
||||
<SlButton size="medium" caret>
|
||||
Medium
|
||||
</SlButton>
|
||||
<SlButton size="large" caret>
|
||||
Large
|
||||
</SlButton>
|
||||
</>
|
||||
);
|
||||
```
|
||||
@@ -354,7 +419,7 @@ const App = () => (
|
||||
|
||||
Use the `loading` attribute to make a button busy. The width will remain the same as before, preventing adjacent elements from moving around. Clicks will be suppressed until the loading state is removed.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button variant="default" loading>Default</sl-button>
|
||||
<sl-button variant="primary" loading>Primary</sl-button>
|
||||
<sl-button variant="success" loading>Success</sl-button>
|
||||
@@ -363,26 +428,38 @@ Use the `loading` attribute to make a button busy. The width will remain the sam
|
||||
<sl-button variant="danger" loading>Danger</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlButton variant="default" loading>Default</SlButton>
|
||||
<SlButton variant="primary" loading>Primary</SlButton>
|
||||
<SlButton variant="success" loading>Success</SlButton>
|
||||
<SlButton variant="neutral" loading>Neutral</SlButton>
|
||||
<SlButton variant="warning" loading>Warning</SlButton>
|
||||
<SlButton variant="danger" loading>Danger</SlButton>
|
||||
<SlButton variant="default" loading>
|
||||
Default
|
||||
</SlButton>
|
||||
<SlButton variant="primary" loading>
|
||||
Primary
|
||||
</SlButton>
|
||||
<SlButton variant="success" loading>
|
||||
Success
|
||||
</SlButton>
|
||||
<SlButton variant="neutral" loading>
|
||||
Neutral
|
||||
</SlButton>
|
||||
<SlButton variant="warning" loading>
|
||||
Warning
|
||||
</SlButton>
|
||||
<SlButton variant="danger" loading>
|
||||
Danger
|
||||
</SlButton>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` attribute to disable a button. Clicks will be suppressed until the disabled state is removed.
|
||||
Use the `disabled` attribute to disable a button.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button variant="default" disabled>Default</sl-button>
|
||||
<sl-button variant="primary" disabled>Primary</sl-button>
|
||||
<sl-button variant="success" disabled>Success</sl-button>
|
||||
@@ -391,17 +468,34 @@ Use the `disabled` attribute to disable a button. Clicks will be suppressed unti
|
||||
<sl-button variant="danger" disabled>Danger</sl-button>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlButton variant="default" disabled>Default</SlButton>
|
||||
<SlButton variant="primary" disabled>Primary</SlButton>
|
||||
<SlButton variant="success" disabled>Success</SlButton>
|
||||
<SlButton variant="neutral" disabled>Neutral</SlButton>
|
||||
<SlButton variant="warning" disabled>Warning</SlButton>
|
||||
<SlButton variant="danger" disabled>Danger</SlButton>
|
||||
<SlButton variant="default" disabled>
|
||||
Default
|
||||
</SlButton>
|
||||
|
||||
<SlButton variant="primary" disabled>
|
||||
Primary
|
||||
</SlButton>
|
||||
|
||||
<SlButton variant="success" disabled>
|
||||
Success
|
||||
</SlButton>
|
||||
|
||||
<SlButton variant="neutral" disabled>
|
||||
Neutral
|
||||
</SlButton>
|
||||
|
||||
<SlButton variant="warning" disabled>
|
||||
Warning
|
||||
</SlButton>
|
||||
|
||||
<SlButton variant="danger" disabled>
|
||||
Danger
|
||||
</SlButton>
|
||||
</>
|
||||
);
|
||||
```
|
||||
@@ -410,7 +504,7 @@ const App = () => (
|
||||
|
||||
This example demonstrates how to style buttons using a custom class. This is the recommended approach if you need to add additional variations. To customize an existing variation, modify the selector to target the button's `variant` attribute instead of a class (e.g. `sl-button[variant="primary"]`).
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-button class="pink">Pink Button</sl-button>
|
||||
|
||||
<style>
|
||||
@@ -418,7 +512,7 @@ This example demonstrates how to style buttons using a custom class. This is the
|
||||
/* Set design tokens for height and border width */
|
||||
--sl-input-height-medium: 48px;
|
||||
--sl-input-border-width: 4px;
|
||||
|
||||
|
||||
border-radius: 0;
|
||||
background-color: #ff1493;
|
||||
border-top-color: #ff7ac1;
|
||||
@@ -449,5 +543,3 @@ This example demonstrates how to style buttons using a custom class. This is the
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
[component-metadata:sl-button]
|
||||
@@ -1,19 +1,20 @@
|
||||
# Card
|
||||
---
|
||||
meta:
|
||||
title: Card
|
||||
description: Cards can be used to group related subjects in a container.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-card]
|
||||
|
||||
Cards can be used to group related subjects in a container.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-card class="card-overview">
|
||||
<img
|
||||
slot="image"
|
||||
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
|
||||
<img
|
||||
slot="image"
|
||||
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
|
||||
alt="A kitten sits patiently between a terracotta pot and decorative grasses."
|
||||
>
|
||||
/>
|
||||
|
||||
<strong>Mittens</strong><br>
|
||||
This kitten is as cute as he is playful. Bring him home today!<br>
|
||||
<strong>Mittens</strong><br />
|
||||
This kitten is as cute as he is playful. Bring him home today!<br />
|
||||
<small>6 weeks old</small>
|
||||
|
||||
<div slot="footer">
|
||||
@@ -31,20 +32,18 @@ Cards can be used to group related subjects in a container.
|
||||
color: var(--sl-color-neutral-500);
|
||||
}
|
||||
|
||||
.card-overview [slot="footer"] {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.card-overview [slot='footer'] {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import {
|
||||
SlButton,
|
||||
SlCard,
|
||||
SlRating
|
||||
} from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlCard from '@shoelace-style/shoelace/dist/react/card';
|
||||
import SlRating from '@shoelace-style/shoelace/dist/react/rating';
|
||||
|
||||
const css = `
|
||||
.card-overview {
|
||||
@@ -56,8 +55,8 @@ const css = `
|
||||
}
|
||||
|
||||
.card-overview [slot="footer"] {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
@@ -65,18 +64,20 @@ const css = `
|
||||
const App = () => (
|
||||
<>
|
||||
<SlCard className="card-overview">
|
||||
<img
|
||||
slot="image"
|
||||
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
|
||||
<img
|
||||
slot="image"
|
||||
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
|
||||
alt="A kitten sits patiently between a terracotta pot and decorative grasses."
|
||||
/>
|
||||
|
||||
<strong>Mittens</strong><br />
|
||||
This kitten is as cute as he is playful. Bring him home today!<br />
|
||||
<strong>Mittens</strong>
|
||||
<br />
|
||||
This kitten is as cute as he is playful. Bring him home today!
|
||||
<br />
|
||||
<small>6 weeks old</small>
|
||||
|
||||
<div slot="footer">
|
||||
<SlButton variant="primary" pill>More Info</SlButton>
|
||||
<SlButton variant="primary" pill>
|
||||
More Info
|
||||
</SlButton>
|
||||
<SlRating></SlRating>
|
||||
</div>
|
||||
</SlCard>
|
||||
@@ -88,11 +89,11 @@ const App = () => (
|
||||
|
||||
## Examples
|
||||
|
||||
## Basic Card
|
||||
### Basic Card
|
||||
|
||||
Basic cards aren't very exciting, but they can display any content you want them to.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-card class="card-basic">
|
||||
This is just a basic card. No image, no header, and no footer. Just your content.
|
||||
</sl-card>
|
||||
@@ -104,8 +105,8 @@ Basic cards aren't very exciting, but they can display any content you want them
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlCard } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlCard from '@shoelace-style/shoelace/dist/react/card';
|
||||
|
||||
const css = `
|
||||
.card-basic {
|
||||
@@ -124,16 +125,15 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
## Card with Header
|
||||
### Card with Header
|
||||
|
||||
Headers can be used to display titles and more.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-card class="card-header">
|
||||
<div slot="header">
|
||||
Header Title
|
||||
|
||||
<sl-icon-button name="gear"></sl-icon-button>
|
||||
<sl-icon-button name="gear" label="Settings"></sl-icon-button>
|
||||
</div>
|
||||
|
||||
This card has a header. You can put all sorts of things in it!
|
||||
@@ -144,9 +144,9 @@ Headers can be used to display titles and more.
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.card-header [slot="header"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.card-header [slot='header'] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@@ -160,8 +160,9 @@ Headers can be used to display titles and more.
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlCard, SlIconButton } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlCard from '@shoelace-style/shoelace/dist/react/card';
|
||||
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
|
||||
|
||||
const css = `
|
||||
.card-header {
|
||||
@@ -169,8 +170,8 @@ const css = `
|
||||
}
|
||||
|
||||
.card-header [slot="header"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@@ -188,10 +189,8 @@ const App = () => (
|
||||
<SlCard className="card-header">
|
||||
<div slot="header">
|
||||
Header Title
|
||||
|
||||
<SlIconButton name="gear"></SlIconButton>
|
||||
</div>
|
||||
|
||||
This card has a header. You can put all sorts of things in it!
|
||||
</SlCard>
|
||||
|
||||
@@ -200,17 +199,17 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
## Card with Footer
|
||||
### Card with Footer
|
||||
|
||||
Footers can be used to display actions, summaries, or other relevant content.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-card class="card-footer">
|
||||
This card has a footer. You can put all sorts of things in it!
|
||||
|
||||
<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>
|
||||
|
||||
@@ -219,20 +218,18 @@ Footers can be used to display actions, summaries, or other relevant content.
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.card-footer [slot="footer"] {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.card-footer [slot='footer'] {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import {
|
||||
SlButton,
|
||||
SlCard,
|
||||
SlRating
|
||||
} from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlCard from '@shoelace-style/shoelace/dist/react/card';
|
||||
import SlRating from '@shoelace-style/shoelace/dist/react/rating';
|
||||
|
||||
const css = `
|
||||
.card-footer {
|
||||
@@ -240,8 +237,8 @@ const css = `
|
||||
}
|
||||
|
||||
.card-footer [slot="footer"] {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
@@ -250,10 +247,11 @@ const App = () => (
|
||||
<>
|
||||
<SlCard className="card-footer">
|
||||
This card has a footer. You can put all sorts of things in it!
|
||||
|
||||
<div slot="footer">
|
||||
<SlRating></SlRating>
|
||||
<SlButton slot="footer" variant="primary">Preview</SlButton>
|
||||
<SlButton slot="footer" variant="primary">
|
||||
Preview
|
||||
</SlButton>
|
||||
</div>
|
||||
</SlCard>
|
||||
|
||||
@@ -262,17 +260,17 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
## Images
|
||||
### Images
|
||||
|
||||
Cards accept an `image` slot. The image is displayed atop the card and stretches to fit.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-card class="card-image">
|
||||
<img
|
||||
slot="image"
|
||||
src="https://images.unsplash.com/photo-1547191783-94d5f8f6d8b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=80"
|
||||
<img
|
||||
slot="image"
|
||||
src="https://images.unsplash.com/photo-1547191783-94d5f8f6d8b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=80"
|
||||
alt="A kitten walks towards camera on top of pallet."
|
||||
>
|
||||
/>
|
||||
This is a kitten, but not just any kitten. This kitten likes walking along pallets.
|
||||
</sl-card>
|
||||
|
||||
@@ -283,8 +281,8 @@ Cards accept an `image` slot. The image is displayed atop the card and stretches
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlCard } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlCard from '@shoelace-style/shoelace/dist/react/card';
|
||||
|
||||
const css = `
|
||||
.card-image {
|
||||
@@ -295,9 +293,9 @@ const css = `
|
||||
const App = () => (
|
||||
<>
|
||||
<SlCard className="card-image">
|
||||
<img
|
||||
slot="image"
|
||||
src="https://images.unsplash.com/photo-1547191783-94d5f8f6d8b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=80"
|
||||
<img
|
||||
slot="image"
|
||||
src="https://images.unsplash.com/photo-1547191783-94d5f8f6d8b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=80"
|
||||
alt="A kitten walks towards camera on top of pallet."
|
||||
/>
|
||||
This is a kitten, but not just any kitten. This kitten likes walking along pallets.
|
||||
@@ -307,5 +305,3 @@ const App = () => (
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-card]
|
||||
85
docs/pages/components/carousel-item.md
Normal file
@@ -0,0 +1,85 @@
|
||||
---
|
||||
meta:
|
||||
title: Carousel Item
|
||||
description: A carousel item represent a slide within a carousel.
|
||||
layout: component
|
||||
---
|
||||
|
||||
```html:preview
|
||||
<sl-carousel pagination>
|
||||
<sl-carousel-item>
|
||||
<img
|
||||
alt="The sun shines on the mountains and trees - Photo by Adam Kool on Unsplash"
|
||||
src="/assets/examples/carousel/mountains.jpg"
|
||||
/>
|
||||
</sl-carousel-item>
|
||||
<sl-carousel-item>
|
||||
<img
|
||||
alt="A waterfall in the middle of a forest - Photo by Thomas Kelly on Unsplash"
|
||||
src="/assets/examples/carousel/waterfall.jpg"
|
||||
/>
|
||||
</sl-carousel-item>
|
||||
<sl-carousel-item>
|
||||
<img
|
||||
alt="The sun is setting over a lavender field - Photo by Leonard Cotte on Unsplash"
|
||||
src="/assets/examples/carousel/sunset.jpg"
|
||||
/>
|
||||
</sl-carousel-item>
|
||||
<sl-carousel-item>
|
||||
<img
|
||||
alt="A field of grass with the sun setting in the background - Photo by Sapan Patel on Unsplash"
|
||||
src="/assets/examples/carousel/field.jpg"
|
||||
/>
|
||||
</sl-carousel-item>
|
||||
<sl-carousel-item>
|
||||
<img
|
||||
alt="A scenic view of a mountain with clouds rolling in - Photo by V2osk on Unsplash"
|
||||
src="/assets/examples/carousel/valley.jpg"
|
||||
/>
|
||||
</sl-carousel-item>
|
||||
</sl-carousel>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';
|
||||
import SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';
|
||||
|
||||
const App = () => (
|
||||
<SlCarousel pagination>
|
||||
<SlCarouselItem>
|
||||
<img
|
||||
alt="The sun shines on the mountains and trees - Photo by Adam Kool on Unsplash"
|
||||
src="/assets/examples/carousel/mountains.jpg"
|
||||
/>
|
||||
</SlCarouselItem>
|
||||
<SlCarouselItem>
|
||||
<img
|
||||
alt="A waterfall in the middle of a forest - Photo by Thomas Kelly on Unsplash"
|
||||
src="/assets/examples/carousel/waterfall.jpg"
|
||||
/>
|
||||
</SlCarouselItem>
|
||||
<SlCarouselItem>
|
||||
<img
|
||||
alt="The sun is setting over a lavender field - Photo by Leonard Cotte on Unsplash"
|
||||
src="/assets/examples/carousel/sunset.jpg"
|
||||
/>
|
||||
</SlCarouselItem>
|
||||
<SlCarouselItem>
|
||||
<img
|
||||
alt="A field of grass with the sun setting in the background - Photo by Sapan Patel on Unsplash"
|
||||
src="/assets/examples/carousel/field.jpg"
|
||||
/>
|
||||
</SlCarouselItem>
|
||||
<SlCarouselItem>
|
||||
<img
|
||||
alt="A scenic view of a mountain with clouds rolling in - Photo by V2osk on Unsplash"
|
||||
src="/assets/examples/carousel/valley.jpg"
|
||||
/>
|
||||
</SlCarouselItem>
|
||||
</SlCarousel>
|
||||
);
|
||||
```
|
||||
|
||||
:::tip
|
||||
Additional demonstrations can be found in the [carousel examples](/components/carousel).
|
||||
:::
|
||||
1259
docs/pages/components/carousel.md
Normal file
164
docs/pages/components/checkbox.md
Normal file
@@ -0,0 +1,164 @@
|
||||
---
|
||||
meta:
|
||||
title: Checkbox
|
||||
description: Checkboxes allow the user to toggle an option on or off.
|
||||
layout: component
|
||||
---
|
||||
|
||||
```html:preview
|
||||
<sl-checkbox>Checkbox</sl-checkbox>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
|
||||
|
||||
const App = () => <SlCheckbox>Checkbox</SlCheckbox>;
|
||||
```
|
||||
|
||||
:::tip
|
||||
This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
### Checked
|
||||
|
||||
Use the `checked` attribute to activate the checkbox.
|
||||
|
||||
```html:preview
|
||||
<sl-checkbox checked>Checked</sl-checkbox>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
|
||||
|
||||
const App = () => <SlCheckbox checked>Checked</SlCheckbox>;
|
||||
```
|
||||
|
||||
### Indeterminate
|
||||
|
||||
Use the `indeterminate` attribute to make the checkbox indeterminate.
|
||||
|
||||
```html:preview
|
||||
<sl-checkbox indeterminate>Indeterminate</sl-checkbox>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
|
||||
|
||||
const App = () => <SlCheckbox indeterminate>Indeterminate</SlCheckbox>;
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` attribute to disable the checkbox.
|
||||
|
||||
```html:preview
|
||||
<sl-checkbox disabled>Disabled</sl-checkbox>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
|
||||
|
||||
const App = () => <SlCheckbox disabled>Disabled</SlCheckbox>;
|
||||
```
|
||||
|
||||
### Sizes
|
||||
|
||||
Use the `size` attribute to change a checkbox's size.
|
||||
|
||||
```html:preview
|
||||
<sl-checkbox size="small">Small</sl-checkbox>
|
||||
<br />
|
||||
<sl-checkbox size="medium">Medium</sl-checkbox>
|
||||
<br />
|
||||
<sl-checkbox size="large">Large</sl-checkbox>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlCheckbox size="small">Small</SlCheckbox>
|
||||
<br />
|
||||
<SlCheckbox size="medium">Medium</SlCheckbox>
|
||||
<br />
|
||||
<SlCheckbox size="large">Large</SlCheckbox>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Custom Validity
|
||||
|
||||
Use the `setCustomValidity()` method to set a custom validation message. This will prevent the form from submitting and make the browser display the error message you provide. To clear the error, call this function with an empty string.
|
||||
|
||||
```html:preview
|
||||
<form class="custom-validity">
|
||||
<sl-checkbox>Check me</sl-checkbox>
|
||||
<br />
|
||||
<sl-button type="submit" variant="primary" style="margin-top: 1rem;">Submit</sl-button>
|
||||
</form>
|
||||
<script>
|
||||
const form = document.querySelector('.custom-validity');
|
||||
const checkbox = form.querySelector('sl-checkbox');
|
||||
const errorMessage = `Don't forget to check me!`;
|
||||
|
||||
// Set initial validity as soon as the element is defined
|
||||
customElements.whenDefined('sl-checkbox').then(async () => {
|
||||
await checkbox.updateComplete;
|
||||
checkbox.setCustomValidity(errorMessage);
|
||||
});
|
||||
|
||||
// Update validity on change
|
||||
checkbox.addEventListener('sl-change', () => {
|
||||
checkbox.setCustomValidity(checkbox.checked ? '' : errorMessage);
|
||||
});
|
||||
|
||||
// Handle submit
|
||||
form.addEventListener('submit', event => {
|
||||
event.preventDefault();
|
||||
alert('All fields are valid!');
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useEffect, useRef } from 'react';
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
|
||||
|
||||
const App = () => {
|
||||
const checkbox = useRef(null);
|
||||
const errorMessage = `Don't forget to check me!`;
|
||||
|
||||
function handleChange() {
|
||||
checkbox.current.setCustomValidity(checkbox.current.checked ? '' : errorMessage);
|
||||
}
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
alert('All fields are valid!');
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
checkbox.current.setCustomValidity(errorMessage);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<form class="custom-validity" onSubmit={handleSubmit}>
|
||||
<SlCheckbox ref={checkbox} onSlChange={handleChange}>
|
||||
Check me
|
||||
</SlCheckbox>
|
||||
<br />
|
||||
<SlButton type="submit" variant="primary" style={{ marginTop: '1rem' }}>
|
||||
Submit
|
||||
</SlButton>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
140
docs/pages/components/color-picker.md
Normal file
@@ -0,0 +1,140 @@
|
||||
---
|
||||
meta:
|
||||
title: Color Picker
|
||||
description: Color pickers allow the user to select a color.
|
||||
layout: component
|
||||
---
|
||||
|
||||
```html:preview
|
||||
<sl-color-picker label="Select a color"></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
||||
|
||||
const App = () => <SlColorPicker label="Select a color" />;
|
||||
```
|
||||
|
||||
:::tip
|
||||
This component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
### Initial Value
|
||||
|
||||
Use the `value` attribute to set an initial value for the color picker.
|
||||
|
||||
```html:preview
|
||||
<sl-color-picker value="#4a90e2" label="Select a color"></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
||||
|
||||
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, HSLA, or HSVA based on `format`.
|
||||
|
||||
```html:preview
|
||||
<sl-color-picker value="#f5a623ff" opacity label="Select a color"></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
||||
|
||||
const App = () => <SlColorPicker opacity label="Select a color" />;
|
||||
```
|
||||
|
||||
### Formats
|
||||
|
||||
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.
|
||||
|
||||
```html:preview
|
||||
<sl-color-picker format="hex" value="#4a90e2" label="Select a color"></sl-color-picker>
|
||||
<sl-color-picker format="rgb" value="rgb(80, 227, 194)" label="Select a color"></sl-color-picker>
|
||||
<sl-color-picker format="hsl" value="hsl(290, 87%, 47%)" label="Select a color"></sl-color-picker>
|
||||
<sl-color-picker format="hsv" value="hsv(55, 89%, 97%)" label="Select a color"></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
||||
|
||||
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/color-picker';
|
||||
|
||||
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.
|
||||
|
||||
```html:preview
|
||||
<sl-color-picker size="small" label="Select a color"></sl-color-picker>
|
||||
<sl-color-picker size="medium" label="Select a color"></sl-color-picker>
|
||||
<sl-color-picker size="large" label="Select a color"></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlColorPicker size="small" label="Select a color" />
|
||||
<SlColorPicker size="medium" label="Select a color" />
|
||||
<SlColorPicker size="large" label="Select a color" />
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Inline
|
||||
|
||||
The color picker can be rendered inline instead of in a dropdown using the `inline` attribute.
|
||||
|
||||
```html:preview
|
||||
<sl-color-picker inline label="Select a color"></sl-color-picker>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';
|
||||
|
||||
const App = () => <SlColorPicker inline label="Select a color" />;
|
||||
```
|
||||
258
docs/pages/components/copy-button.md
Normal file
@@ -0,0 +1,258 @@
|
||||
---
|
||||
meta:
|
||||
title: Copy Button
|
||||
description: Copies data to the clipboard when the user clicks the button.
|
||||
layout: component
|
||||
---
|
||||
|
||||
```html:preview
|
||||
<sl-copy-button value="Shoelace rocks!"></sl-copy-button>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
||||
|
||||
const App = () => (
|
||||
<SlCopyButton value="Shoelace rocks!" />
|
||||
);
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Custom Labels
|
||||
|
||||
Copy Buttons display feedback in a tooltip. You can customize the labels using the `copy-label`, `success-label`, and `error-label` attributes.
|
||||
|
||||
```html:preview
|
||||
<sl-copy-button
|
||||
value="Custom labels are easy"
|
||||
copy-label="Click to copy"
|
||||
success-label="You did it!"
|
||||
error-label="Whoops, your browser doesn't support this!"
|
||||
></sl-copy-button>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
||||
|
||||
const App = () => (
|
||||
<SlCopyButton
|
||||
value="Custom labels are easy"
|
||||
copy-label="Click to copy"
|
||||
success-label="You did it!"
|
||||
error-label="Whoops, your browser doesn't support this!"
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
### Custom Icons
|
||||
|
||||
Use the `copy-icon`, `success-icon`, and `error-icon` slots to customize the icons that get displayed for each state. You can use [`<sl-icon>`](/components/icon) or your own images.
|
||||
|
||||
```html:preview
|
||||
<sl-copy-button value="Copied from a custom button">
|
||||
<sl-icon slot="copy-icon" name="clipboard"></sl-icon>
|
||||
<sl-icon slot="success-icon" name="clipboard-check"></sl-icon>
|
||||
<sl-icon slot="error-icon" name="clipboard-x"></sl-icon>
|
||||
</sl-copy-button>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
||||
import { SlIcon } from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlCopyButton value="Copied from a custom button">
|
||||
<SlIcon slot="copy-icon" name="clipboard" />
|
||||
<SlIcon slot="success-icon" name="clipboard-check" />
|
||||
<SlIcon slot="error-icon" name="clipboard-x" />
|
||||
</SlCopyButton>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Copying Values From Other Elements
|
||||
|
||||
Normally, the data that gets copied will come from the component's `value` attribute, but you can copy data from any element within the same document by providing its `id` to the `from` attribute.
|
||||
|
||||
When using the `from` attribute, the element's [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) will be copied by default. Passing an attribute or property modifier will let you copy data from one of the element's attributes or properties instead.
|
||||
|
||||
To copy data from an attribute, use `from="id[attr]"` where `id` is the id of the target element and `attr` is the name of the attribute you'd like to copy. To copy data from a property, use `from="id.prop"` where `id` is the id of the target element and `prop` is the name of the property you'd like to copy.
|
||||
|
||||
```html:preview
|
||||
<!-- Copies the span's textContent -->
|
||||
<span id="my-phone">+1 (234) 456-7890</span>
|
||||
<sl-copy-button from="my-phone"></sl-copy-button>
|
||||
|
||||
<br><br>
|
||||
|
||||
<!-- Copies the input's "value" property -->
|
||||
<sl-input id="my-input" type="text" value="User input" style="display: inline-block; max-width: 300px;"></sl-input>
|
||||
<sl-copy-button from="my-input.value"></sl-copy-button>
|
||||
|
||||
<br><br>
|
||||
|
||||
<!-- Copies the link's "href" attribute -->
|
||||
<a id="my-link" href="https://shoelace.style/">Shoelace Website</a>
|
||||
<sl-copy-button from="my-link[href]"></sl-copy-button>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react/input';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
{/* Copies the span's textContent */}
|
||||
<span id="my-phone">+1 (234) 456-7890</span>
|
||||
<SlCopyButton from="my-phone" />
|
||||
|
||||
<br /><br />
|
||||
|
||||
{/* Copies the input's "value" property */}
|
||||
<SlInput id="my-input" type="text" />
|
||||
<SlCopyButton from="my-input.value" />
|
||||
|
||||
<br /><br />
|
||||
|
||||
{/* Copies the link's "href" attribute */}
|
||||
<a id="my-link" href="https://shoelace.style/">Shoelace Website</a>
|
||||
<SlCopyButton from="my-link[href]" />
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Handling Errors
|
||||
|
||||
A copy error will occur if the value is an empty string, if the `from` attribute points to an id that doesn't exist, or if the browser rejects the operation for any reason. When this happens, the `sl-error` event will be emitted.
|
||||
|
||||
This example demonstrates what happens when a copy error occurs. You can customize the error label and icon using the `error-label` attribute and the `error-icon` slot, respectively.
|
||||
|
||||
```html:preview
|
||||
<sl-copy-button from="i-do-not-exist"></sl-copy-button>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
||||
|
||||
const App = () => (
|
||||
<SlCopyButton from="i-do-not-exist" />
|
||||
);
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
Copy buttons can be disabled by adding the `disabled` attribute.
|
||||
|
||||
```html:preview
|
||||
<sl-copy-button value="You can't copy me" disabled></sl-copy-button>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
||||
|
||||
const App = () => (
|
||||
<SlCopyButton value="You can't copy me" disabled />
|
||||
);
|
||||
```
|
||||
|
||||
### Changing Feedback Duration
|
||||
|
||||
A success indicator is briefly shown after copying. You can customize the length of time the indicator is shown using the `feedback-duration` attribute.
|
||||
|
||||
```html:preview
|
||||
<sl-copy-button value="Shoelace rocks!" feedback-duration="250"></sl-copy-button>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
||||
|
||||
const App = () => (
|
||||
<SlCopyButton value="Shoelace rocks!" feedback-duration={250} />
|
||||
);
|
||||
```
|
||||
|
||||
### Custom Styles
|
||||
|
||||
You can customize the button to your liking with CSS.
|
||||
|
||||
```html:preview
|
||||
<sl-copy-button value="I'm so stylish" class="custom-styles">
|
||||
<sl-icon slot="copy-icon" name="asterisk"></sl-icon>
|
||||
<sl-icon slot="success-icon" name="check-lg"></sl-icon>
|
||||
<sl-icon slot="error-icon" name="x-lg"></sl-icon>
|
||||
</sl-copy-button>
|
||||
|
||||
<style>
|
||||
.custom-styles {
|
||||
--success-color: white;
|
||||
--error-color: white;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.custom-styles::part(button) {
|
||||
background-color: #ff1493;
|
||||
border: solid 4px #ff7ac1;
|
||||
border-right-color: #ad005c;
|
||||
border-bottom-color: #ad005c;
|
||||
border-radius: 0;
|
||||
transition: 100ms scale ease-in-out, 100ms translate ease-in-out;
|
||||
}
|
||||
|
||||
.custom-styles::part(button):hover {
|
||||
scale: 1.1;
|
||||
}
|
||||
|
||||
.custom-styles::part(button):active {
|
||||
translate: 0 2px;
|
||||
}
|
||||
|
||||
.custom-styles::part(button):focus-visible {
|
||||
outline: dashed 2px deeppink;
|
||||
outline-offset: 4px;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';
|
||||
|
||||
const css = `
|
||||
.custom-styles {
|
||||
--success-color: white;
|
||||
--error-color: white;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.custom-styles::part(button) {
|
||||
background-color: #ff1493;
|
||||
border: solid 4px #ff7ac1;
|
||||
border-right-color: #ad005c;
|
||||
border-bottom-color: #ad005c;
|
||||
border-radius: 0;
|
||||
transition: 100ms scale ease-in-out, 100ms translate ease-in-out;
|
||||
}
|
||||
|
||||
.custom-styles::part(button):hover {
|
||||
scale: 1.1;
|
||||
}
|
||||
|
||||
.custom-styles::part(button):active {
|
||||
translate: 0 2px;
|
||||
}
|
||||
|
||||
.custom-styles::part(button):focus-visible {
|
||||
outline: dashed 2px deeppink;
|
||||
outline-offset: 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<SlCopyButton value="I'm so stylish" className="custom-styles" />
|
||||
|
||||
<style>{css}</style>
|
||||
</>
|
||||
);
|
||||
```
|
||||
@@ -1,20 +1,21 @@
|
||||
---
|
||||
meta:
|
||||
title: Details
|
||||
description: Details show a brief summary and expand to show additional content.
|
||||
layout: component
|
||||
---
|
||||
|
||||
<!-- cspell:dictionaries lorem-ipsum -->
|
||||
|
||||
# Details
|
||||
|
||||
[component-header:sl-details]
|
||||
|
||||
Details show a brief summary and expand to show additional content.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-details summary="Toggle Me">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
</sl-details>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlDetails } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlDetails from '@shoelace-style/shoelace/dist/react/details';
|
||||
|
||||
const App = () => (
|
||||
<SlDetails summary="Toggle Me">
|
||||
@@ -30,15 +31,15 @@ const App = () => (
|
||||
|
||||
Use the `disable` attribute to prevent the details from expanding.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-details summary="Disabled" disabled>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
</sl-details>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlDetails } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlDetails from '@shoelace-style/shoelace/dist/react/details';
|
||||
|
||||
const App = () => (
|
||||
<SlDetails summary="Disabled" disabled>
|
||||
@@ -48,11 +49,58 @@ 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 from '@shoelace-style/shoelace/dist/react/details';
|
||||
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
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.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div class="details-group-example">
|
||||
<sl-details summary="First" open>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
@@ -85,5 +133,3 @@ Details are designed to function independently, but you can simulate a group or
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
[component-metadata:sl-details]
|
||||
@@ -1,12 +1,13 @@
|
||||
---
|
||||
meta:
|
||||
title: Dialog
|
||||
description: 'Dialogs, sometimes called "modals", appear above the page and require the user''s immediate attention.'
|
||||
layout: component
|
||||
---
|
||||
|
||||
<!-- cspell:dictionaries lorem-ipsum -->
|
||||
|
||||
# Dialog
|
||||
|
||||
[component-header:sl-dialog]
|
||||
|
||||
Dialogs, sometimes called "modals", appear above the page and require the user's immediate attention.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-dialog label="Dialog" class="dialog-overview">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||
@@ -24,9 +25,10 @@ Dialogs, sometimes called "modals", appear above the page and require the user's
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDialog } from '@shoelace-style/shoelace/dist/react';
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
||||
|
||||
const App = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -46,19 +48,13 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
## UX Tips
|
||||
|
||||
- Use a dialog when you immediately require the user's attention, e.g. confirming a destructive action.
|
||||
- Always provide an obvious way for the user to dismiss the dialog.
|
||||
- Don't nest dialogs. It almost always leads to a poor experience for the user.
|
||||
|
||||
## Examples
|
||||
|
||||
### Custom Width
|
||||
|
||||
Use the `--width` custom property to set the dialog's width.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-dialog label="Dialog" class="dialog-width" style="--width: 50vw;">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||
@@ -76,9 +72,12 @@ Use the `--width` custom property to set the dialog's width.
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDialog } from '@shoelace-style/shoelace/dist/react';
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
||||
|
||||
const App = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -98,11 +97,13 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Scrolling
|
||||
|
||||
By design, a dialog's height will never exceed that of the viewport. As such, dialogs will not scroll with the page ensuring the header and footer are always accessible to the user.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-dialog label="Dialog" class="dialog-scrolling">
|
||||
<div style="height: 150vh; border: dashed 2px var(--sl-color-neutral-200); padding: 0 1rem;">
|
||||
<p>Scroll down and give it a try! 👇</p>
|
||||
@@ -122,9 +123,12 @@ By design, a dialog's height will never exceed that of the viewport. As such, di
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDialog } from '@shoelace-style/shoelace/dist/react';
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
||||
|
||||
const App = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -153,15 +157,72 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Header Actions
|
||||
|
||||
The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/components/icon-button) if needed.
|
||||
|
||||
```html:preview
|
||||
<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 from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
||||
import SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';
|
||||
|
||||
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.
|
||||
By default, dialogs will close when the user clicks the close button, clicks the overlay, or presses the [[Escape]] key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur.
|
||||
|
||||
To keep the dialog open in such cases, you can cancel the `sl-request-close` event. When canceled, the dialog will remain open and pulse briefly to draw the user's attention to it.
|
||||
|
||||
You can use `event.detail.source` to determine what triggered the request to close. This example prevents the dialog from closing when the overlay is clicked, but allows the close button or <kbd>Escape</kbd> to dismiss it.
|
||||
You can use `event.detail.source` to determine what triggered the request to close. This example prevents the dialog from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-dialog label="Dialog" class="dialog-deny-close">
|
||||
This dialog will not close when you click on the overlay.
|
||||
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||
@@ -186,9 +247,10 @@ You can use `event.detail.source` to determine what triggered the request to clo
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import { SlButton, SlDialog } from '@shoelace-style/shoelace/dist/react';
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
||||
|
||||
const App = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -202,12 +264,7 @@ const App = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SlDialog
|
||||
label="Dialog"
|
||||
open={open}
|
||||
onSlRequestClose={handleRequestClose}
|
||||
onSlAfterHide={() => setOpen(false)}
|
||||
>
|
||||
<SlDialog label="Dialog" open={open} onSlRequestClose={handleRequestClose} onSlAfterHide={() => setOpen(false)}>
|
||||
This dialog will not close when you click on the overlay.
|
||||
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||
Close
|
||||
@@ -222,11 +279,11 @@ const App = () => {
|
||||
|
||||
### Customizing Initial Focus
|
||||
|
||||
By default, the dialog's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element within the dialog. To set focus on a different element, listen for and cancel the `sl-initial-focus` event.
|
||||
By default, the dialog's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the dialog. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-dialog label="Dialog" class="dialog-focus">
|
||||
<sl-input placeholder="I will have focus when the dialog is opened"></sl-input>
|
||||
<sl-input autofocus placeholder="I will have focus when the dialog is opened"></sl-input>
|
||||
<sl-button slot="footer" variant="primary">Close</sl-button>
|
||||
</sl-dialog>
|
||||
|
||||
@@ -240,31 +297,22 @@ By default, the dialog's panel will gain focus when opened. This allows a subseq
|
||||
|
||||
openButton.addEventListener('click', () => dialog.show());
|
||||
closeButton.addEventListener('click', () => dialog.hide());
|
||||
|
||||
dialog.addEventListener('sl-initial-focus', event => {
|
||||
event.preventDefault();
|
||||
input.focus({ preventScroll: true });
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { useRef, useState } from 'react';
|
||||
import { SlButton, SlDialog, SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlDialog from '@shoelace-style/shoelace/dist/react/dialog';
|
||||
import SlInput from '@shoelace-style/shoelace/dist/react/input';
|
||||
|
||||
const App = () => {
|
||||
const input = useRef(null);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
function handleInitialFocus(event) {
|
||||
event.preventDefault();
|
||||
input.current.focus();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SlDialog label="Dialog" open={open} onSlInitialFocus={handleInitialFocus} onSlAfterHide={() => setOpen(false)}>
|
||||
<SlInput ref={input} placeholder="I will have focus when the dialog is opened" />
|
||||
<SlDialog label="Dialog" open={open} onSlAfterHide={() => setOpen(false)}>
|
||||
<SlInput autofocus placeholder="I will have focus when the dialog is opened" />
|
||||
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
|
||||
Close
|
||||
</SlButton>
|
||||
@@ -276,4 +324,6 @@ const App = () => {
|
||||
};
|
||||
```
|
||||
|
||||
[component-metadata:sl-dialog]
|
||||
:::tip
|
||||
You can further customize initial focus behavior by canceling the `sl-initial-focus` event and setting focus yourself inside the event handler.
|
||||
:::
|
||||
@@ -1,59 +1,63 @@
|
||||
# Divider
|
||||
---
|
||||
meta:
|
||||
title: Divider
|
||||
description: Dividers are used to visually separate or group elements.
|
||||
layout: component
|
||||
---
|
||||
|
||||
[component-header:sl-divider]
|
||||
|
||||
Dividers are used to visually separate or group elements.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-divider></sl-divider>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlDivider } from '@shoelace-style/shoelace/dist/react';
|
||||
```jsx:react
|
||||
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||
|
||||
const App = () => (
|
||||
<SlDivider />
|
||||
);
|
||||
const App = () => <SlDivider />;
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Width
|
||||
|
||||
Use the `--width` custom property to change the width of the divider.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-divider style="--width: 4px;"></sl-divider>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlDivider } from '@shoelace-style/shoelace/dist/react';
|
||||
{% raw %}
|
||||
|
||||
const App = () => (
|
||||
<SlDivider style={{ '--width': '4px' }} />
|
||||
);
|
||||
```jsx:react
|
||||
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||
|
||||
const App = () => <SlDivider style={{ '--width': '4px' }} />;
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Color
|
||||
|
||||
Use the `--color` custom property to change the color of the divider.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<sl-divider style="--color: tomato;"></sl-divider>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlDivider } from '@shoelace-style/shoelace/dist/react';
|
||||
{% raw %}
|
||||
|
||||
const App = () => (
|
||||
<SlDivider style={{ '--color': 'tomato' }} />
|
||||
);
|
||||
```jsx:react
|
||||
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||
|
||||
const App = () => <SlDivider style={{ '--color': 'tomato' }} />;
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Spacing
|
||||
|
||||
Use the `--spacing` custom property to change the amount of space between the divider and it's neighboring elements.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div style="text-align: center;">
|
||||
Above
|
||||
<sl-divider style="--spacing: 2rem;"></sl-divider>
|
||||
@@ -61,8 +65,10 @@ Use the `--spacing` custom property to change the amount of space between the di
|
||||
</div>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlDivider } from '@shoelace-style/shoelace/dist/react';
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
@@ -73,11 +79,13 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Vertical
|
||||
|
||||
Add the `vertical` attribute to draw the divider in a vertical orientation. The divider will span the full height of its container. Vertical dividers work especially well inside of a flex container.
|
||||
|
||||
```html preview
|
||||
```html:preview
|
||||
<div style="display: flex; align-items: center; height: 2rem;">
|
||||
First
|
||||
<sl-divider vertical></sl-divider>
|
||||
@@ -87,14 +95,16 @@ Add the `vertical` attribute to draw the divider in a vertical orientation. The
|
||||
</div>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import { SlDivider } from '@shoelace-style/shoelace/dist/react';
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||
|
||||
const App = () => (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
height: '2rem'
|
||||
}}
|
||||
>
|
||||
@@ -107,12 +117,14 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Menu Dividers
|
||||
|
||||
Use dividers in [menus](/components/menu) to visually group menu items.
|
||||
|
||||
```html preview
|
||||
<sl-menu style="max-width: 200px; border: solid 1px var(--sl-panel-border-color); background: var(--sl-panel-background-color); border-radius: var(--sl-border-radius-medium);">
|
||||
```html:preview
|
||||
<sl-menu style="max-width: 200px;">
|
||||
<sl-menu-item value="1">Option 1</sl-menu-item>
|
||||
<sl-menu-item value="2">Option 2</sl-menu-item>
|
||||
<sl-menu-item value="3">Option 3</sl-menu-item>
|
||||
@@ -123,21 +135,15 @@ Use dividers in [menus](/components/menu) to visually group menu items.
|
||||
</sl-menu>
|
||||
```
|
||||
|
||||
```jsx react
|
||||
import {
|
||||
SlDivider,
|
||||
SlMenu,
|
||||
SlMenuItem
|
||||
} from '@shoelace-style/shoelace/dist/react';
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
import SlDivider from '@shoelace-style/shoelace/dist/react/divider';
|
||||
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
|
||||
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
|
||||
|
||||
const App = () => (
|
||||
<SlMenu
|
||||
style={{
|
||||
maxWidth: '200px',
|
||||
border: 'solid 1px var(--sl-panel-border-color)',
|
||||
borderRadius: 'var(--sl-border-radius-medium)'
|
||||
}}
|
||||
>
|
||||
<SlMenu style={{ maxWidth: '200px' }}>
|
||||
<SlMenuItem value="1">Option 1</SlMenuItem>
|
||||
<SlMenuItem value="2">Option 2</SlMenuItem>
|
||||
<SlMenuItem value="3">Option 3</SlMenuItem>
|
||||
@@ -149,4 +155,4 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
[component-metadata:sl-divider]
|
||||
{% endraw %}
|
||||