Make icons searchable!

This commit is contained in:
Cory LaViska
2020-06-19 10:58:52 -04:00
parent 4b47fe78b6
commit acdbbdea83
6 changed files with 1247 additions and 40 deletions

View File

@@ -2,30 +2,18 @@
[component-header:sl-icon]
Icons...
Icons are symbols that can be used to represent or provide context to various options and actions within an application.
The icons that come bundled with Shoelace are courtesy of the [Bootstrap Icons](https://icons.getbootstrap.com/) project.
Shoelace comes bundled with over 600 icons courtesy of the [Bootstrap Icons](https://icons.getbootstrap.com/) project. However, you can also use your own SVG icons with the `src` attribute.
```html preview
<sl-icon name="exclamation-triangle"></sl-icon>
<sl-icon name="archive"></sl-icon>
<sl-icon name="battery-charging"></sl-icon>
<sl-icon name="bell"></sl-icon>
<sl-icon name="clock"></sl-icon>
<sl-icon name="download"></sl-icon>
<sl-icon name="file-earmark"></sl-icon>
<sl-icon name="flag"></sl-icon>
<sl-icon name="heart"></sl-icon>
<sl-icon name="image"></sl-icon>
<sl-icon name="lightning"></sl-icon>
<sl-icon name="mic"></sl-icon>
<sl-icon name="search"></sl-icon>
<sl-icon name="star"></sl-icon>
<sl-icon name="trash"></sl-icon>
<sl-icon name="x-circle"></sl-icon>
```
Hover to see the respective icon's `name` or click to copy the HTML tag to the clipboard.
?> For a searchable list of 600+ icons, please refer to the [Bootstrap Icons website](https://icons.getbootstrap.com/).
<div class="icon-search">
<sl-input placeholder="Search Icons" clearable class="icon-search"></sl-input>
<div class="icon-loader"><sl-spinner size="48"></sl-spinner></div>
<div class="icon-list" hidden></div>
<input type="text" class="icon-copy-input">
</div>
[component-metadata:sl-icon]
@@ -54,4 +42,99 @@ Icon sizes are determined by the current font size.
<sl-icon name="trash"></sl-icon>
<sl-icon name="x-circle"></sl-icon>
</div>
```
```
<script>
fetch('/dist/shoelace/icons/icons.json')
.then(res => res.json())
.then(icons => {
const container = document.querySelector('.icon-search');
const input = container.querySelector('sl-input');
const copyInput = container.querySelector('.icon-copy-input');
const loader = container.querySelector('.icon-loader');
const list = container.querySelector('.icon-list');
const queue = [];
icons.map(i => {
const icon = document.createElement('sl-icon');
icon.setAttribute('data-name', i.name);
icon.setAttribute('data-terms', [...i.tags || [], i.categories || [], i.title].join(' '));
icon.name = i.name;
const tooltip = document.createElement('sl-tooltip');
tooltip.content = i.name;
tooltip.appendChild(icon);
list.appendChild(tooltip);
queue.push(new Promise((resolve, reject) => {
icon.addEventListener('slLoad', () => resolve());
icon.addEventListener('slError', () => reoslve());
}));
icon.addEventListener('click', () => {
copyInput.value = `<sl-icon name="${i.name}"></sl-icon>`;
copyInput.select();
document.execCommand('copy');
tooltip.content = 'Copied!';
setTimeout(() => tooltip.content = i.name, 1000);
});
});
Promise.all(queue).then(() => {
list.hidden = false;
loader.hidden = true;
});
input.addEventListener('slInput', () => {
[...list.querySelectorAll('sl-icon')].map(slIcon => {
if (input.value === '') {
slIcon.hidden = false;
} else {
const terms = slIcon.getAttribute('data-terms').toLowerCase();
const filter = input.value.toLowerCase();
slIcon.hidden = terms.indexOf(filter) < 0;
}
});
});
});
</script>
<style>
.icon-loader {
display: flex;
align-items: center;
justify-content: center;
min-height: 30vh;
}
.icon-list {
display: flex;
flex-wrap: wrap;
margin-top: 1rem;
}
.icon-loader[hidden],
.icon-list[hidden] {
display: none;
}
.icon-list sl-icon {
font-size: 32px;
border-radius: var(--sl-border-radius-medium);
padding: .5em;
transition: var(--sl-transition-medium) all;
cursor: pointer;
}
.icon-list sl-icon:hover {
background-color: var(--sl-color-primary-95);
color: var(--sl-color-primary-50);
}
.icon-copy-input {
position: absolute;
opacity: 0;
pointer-events: none;
}
</style>

61
make-icons.js Normal file
View File

@@ -0,0 +1,61 @@
const Promise = require('bluebird');
const promisify = require('util').promisify;
const chalk = require('chalk');
const copy = require('recursive-copy');
const del = require('del');
const download = require('download');
const fm = require('front-matter');
const fs = require('fs').promises;
const glob = promisify(require('glob'));
const latestVersion = require('latest-version');
const path = require('path');
let numIcons = 0;
(async () => {
try {
// Determine the latest version of Bootstrap icons
const version = await latestVersion('bootstrap-icons');
const srcPath = `./temp/icons-${version}`;
const url = `https://github.com/twbs/icons/archive/v${version}.zip`;
// Download the source from GitHub (since not everything is published to NPM)
console.log(chalk.cyan(`\nDownloading and extracting ${version}... 📦\n`));
await del(['./src/components/icon/icons', './temp']);
await download(url, './temp', { extract: true });
// Copy icons
console.log(chalk.cyan(`Copying icons and license... 🚛\n`));
await Promise.all([
copy(`${srcPath}/icons`, './src/components/icon/icons'),
copy(`${srcPath}/LICENSE.md`, './src/components/icon/icons/LICENSE.md')
]);
// Generate metadata
console.log(chalk.cyan(`Generating icon metadata... 🏷\n`));
const files = await glob(`${srcPath}/docs/content/icons/**/*.md`);
const metadata = await Promise.map(files, async file => {
const name = path.basename(file, path.extname(file));
const data = fm(await fs.readFile(file, 'utf8')).attributes;
numIcons++;
return {
name,
title: data.title,
categories: data.categories,
tags: data.tags
};
});
await fs.writeFile('./src/components/icon/icons/icons.json', JSON.stringify(metadata, null, 2), 'utf8');
// More cleanup
console.log(chalk.cyan(`Cleaning up... 🧹\n`));
await del('./temp');
console.log(chalk.green(`Successfully processed ${numIcons} icons! ✨\n`));
} catch (err) {
console.error(err);
}
})();

1074
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -24,25 +24,30 @@
"generate": "stencil generate",
"serve": "node dev-server.js",
"prebuild": "npm run lint",
"postbuild": "node postbuild.js",
"postinstall": "node postinstall.js",
"postbuild": "node make-dist.js",
"postinstall": "node make-icons.js",
"version": "npm run build"
},
"devDependencies": {
"@stencil/eslint-plugin": "^0.3.1",
"@typescript-eslint/eslint-plugin": "^2.28.0",
"@typescript-eslint/parser": "^2.28.0",
"bluebird": "^3.7.2",
"bootstrap-icons": "^1.0.0-alpha4",
"browser-sync": "^2.26.7",
"chalk": "^4.0.0",
"concurrently": "^5.1.0",
"del": "^5.1.0",
"download": "^8.0.0",
"eslint": "^6.8.0",
"eslint-plugin-react": "^7.19.0",
"express": "^4.17.1",
"front-matter": "^4.0.2",
"glob": "^7.1.6",
"http-proxy": "^1.18.1",
"http-proxy-middleware": "^1.0.4",
"husky": "^4.2.5",
"latest-version": "^5.1.0",
"recursive-copy": "^2.0.10",
"through2": "^3.0.1",
"workbox-build": "4.3.1"

View File

@@ -1,16 +0,0 @@
const chalk = require('chalk');
const copy = require('recursive-copy');
const del = require('del');
(async () => {
try {
// Copy Bootstrap Icons to src/components/icon/icons since local assets can't be linked from node_modules
console.log(chalk.cyan('Copying icons 📦\n'));
await del('./src/components/icon/icons');
await copy('./node_modules/bootstrap-icons/icons', './src/components/icon/icons');
await copy('./node_modules/bootstrap-icons/LICENSE.md', './src/components/icon/icons/LICENSE.md');
} catch (err) {
console.error(err);
}
})();