Compare commits
21 Commits
v0.1.0-alp
...
v0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4406cbaea0 | ||
|
|
11c6a2e917 | ||
|
|
8b5278437a | ||
|
|
c84fcfe3cb | ||
|
|
058c40a1ec | ||
|
|
6dad37a1b5 | ||
|
|
85d3bb9b82 | ||
|
|
dee5d33bcd | ||
|
|
a26745a3be | ||
|
|
9550ec28b8 | ||
|
|
845d6add1f | ||
|
|
06972942cd | ||
|
|
d33b6674f3 | ||
|
|
70be911f69 | ||
|
|
6f06e05a47 | ||
|
|
8a15bca8cd | ||
|
|
5aef4e6110 | ||
|
|
e1cead9531 | ||
|
|
c686c1a0c3 | ||
|
|
af87274bf4 | ||
|
|
e3e4514dcc |
11
.editorconfig
Normal file
@@ -0,0 +1,11 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
quote_type = single
|
||||
max_line_length = 100
|
||||
5
.eslintignore
Normal file
@@ -0,0 +1,5 @@
|
||||
dist
|
||||
build
|
||||
coverage
|
||||
lib
|
||||
tests
|
||||
@@ -1,8 +1,12 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": ["airbnb-base", "prettier"],
|
||||
"plugins": ["import", "prettier"],
|
||||
"rules": {
|
||||
"no-console": ["error", { "allow": ["warn", "error"] }],
|
||||
"no-console": "off",
|
||||
"no-param-reassign": "off",
|
||||
"no-shadow": "off",
|
||||
"no-use-before-define": "off",
|
||||
48
.github/workflows/font.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: Build Lucide
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
Build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone 'Lucide'
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install FontForge
|
||||
run: sudo apt-get install zlib1g-dev fontforge woff2
|
||||
|
||||
- name: Clone sfnt2woff-zopfli repo
|
||||
run: git clone https://github.com/bramstein/sfnt2woff-zopfli.git sfnt2woff-zopfli
|
||||
|
||||
- name: Install and move sfnt2woff-zopfli
|
||||
run: |
|
||||
cd sfnt2woff-zopfli
|
||||
make
|
||||
sudo mv sfnt2woff-zopfli /usr/local/bin/sfnt2woff
|
||||
|
||||
- name: Clone woff2
|
||||
run: git clone --recursive https://github.com/google/woff2.git
|
||||
|
||||
- name: Install woff2
|
||||
run: |
|
||||
cd woff2
|
||||
sudo make clean all
|
||||
sudo mv woff2_compress /usr/local/bin/ && sudo mv woff2_decompress /usr/local/bin/
|
||||
|
||||
- name: Install Font Custom dependency
|
||||
run: sudo gem install fontcustom
|
||||
|
||||
- name: Build 'Lucide'
|
||||
run: echo "Building Featherity font" && fontcustom compile ./icons -h -n Featherity -o build -F
|
||||
|
||||
- name: Zip 'Lucide'
|
||||
run: zip -r Featherity.zip build
|
||||
|
||||
- name: 'Upload to Artifacts'
|
||||
uses: actions/upload-artifact@v1.0.0
|
||||
with:
|
||||
name: Lucide
|
||||
path: build
|
||||
8
.gitignore
vendored
@@ -1,6 +1,12 @@
|
||||
.DS_Store
|
||||
.next
|
||||
.now
|
||||
node_modules
|
||||
dist
|
||||
build
|
||||
lib
|
||||
sandbox
|
||||
stash
|
||||
coverage
|
||||
coverage
|
||||
stats
|
||||
*.log
|
||||
|
||||
6
.npmignore
Normal file
@@ -0,0 +1,6 @@
|
||||
.github
|
||||
packages
|
||||
stats
|
||||
node_modules
|
||||
tests
|
||||
scripts
|
||||
@@ -21,4 +21,4 @@ Guidelines for pull requests:
|
||||
|
||||
Before creating an icon request, please search to see if someone has requested the icon already. If there is an open request, please add a :+1:.
|
||||
|
||||
If the icon has not already been requested, [create an issue](https://github.com/featherity/featherity/issues/new?title=Icon%20Request:) with a title of `Icon request: <icon name>` and add as much information as possible.
|
||||
If the icon has not already been requested, [create an issue](https://github.com/lucide-icons/lucide/issues/new?title=Icon%20Request:) with a title of `Icon request: <icon name>` and add as much information as possible.
|
||||
|
||||
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2020, Featherity Contributors
|
||||
Copyright (c) 2020, Lucide Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
|
||||
139
README.md
@@ -1,10 +1,14 @@
|
||||
# Featherity
|
||||
# Lucide
|
||||
|
||||

|
||||
[](https://www.npmjs.com/package/lucide)
|
||||
[](https://discord.gg/EH6nSts)
|
||||
|
||||
## What is Featherity?
|
||||
## What is Lucide?
|
||||
|
||||
Featherity is a fork of [Feather Icons](https://github.com/feathericons/feather), with icons sourced by the community.
|
||||
Lucide is a community-run fork of [Feather Icons](https://github.com/feathericons/feather), open for anyone to contribute icons.
|
||||
|
||||
Note that we are completely independent from Feather, so **icons submitted here won't get added to Feather Icons or its associated librairies**.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@@ -13,11 +17,127 @@ Featherity is a fork of [Feather Icons](https://github.com/feathericons/feather)
|
||||
* [Contributing](#contributing)
|
||||
* [License](#license)
|
||||
|
||||
## Installation
|
||||
|
||||
### Package Managers
|
||||
|
||||
``` bash
|
||||
npm install lucide
|
||||
#or
|
||||
yarn add lucide
|
||||
```
|
||||
|
||||
### CDN
|
||||
|
||||
``` html
|
||||
<!-- Development version -->
|
||||
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
|
||||
|
||||
<!-- Production version -->
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
At its core, Featherity is a collection of [SVG](https://svgontheweb.com/#svg) files. This means that you can use Feather icons in all the same ways you can use SVGs (e.g. `img`, `background-image`, `inline`, `object`, `embed`, `iframe`). Here's a helpful article detailing the many ways SVGs can be used on the web: [SVG on the Web – Implementation Options](https://svgontheweb.com/#implementation)
|
||||
At its core, Lucide is a collection of [SVG](https://svgontheweb.com/#svg) files. This means that you can use Feather icons in all the same ways you can use SVGs (e.g. `img`, `background-image`, `inline`, `object`, `embed`, `iframe`). Here's a helpful article detailing the many ways SVGs can be used on the web: [SVG on the Web – Implementation Options](https://svgontheweb.com/#implementation)
|
||||
|
||||
The following are additional ways you can use Featherity.
|
||||
The following are additional ways you can use Lucide.
|
||||
With the Javascript library you can easily incorporate the icon you want in your webpage.
|
||||
|
||||
### With unpkg
|
||||
|
||||
Here is a complete example with unpkg
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<body>
|
||||
<i icon-name="volume-2" class="my-class"></i>
|
||||
<i icon-name="x"></i>
|
||||
<i icon-name="menu"></i>
|
||||
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
### With ESModules
|
||||
|
||||
To reduce bundle size, lucide is build to be fully threeshakeble.
|
||||
The `createIcons` function will search for HTMLElements with the attribute `icon-name` and replace it with the svg from the given icon name.
|
||||
|
||||
```html
|
||||
<!-- Your HTML file -->
|
||||
<i icon-name="menu"></i>
|
||||
```
|
||||
|
||||
```js
|
||||
import { createIcons, icons } from 'lucide';
|
||||
|
||||
// Caustion, this will import all the icons and bundle them.
|
||||
createIcons({icons});
|
||||
|
||||
// Recommended way, to include only the icons you need.
|
||||
import { createIcons, Menu, ArrowRight, Globe } from 'lucide';
|
||||
|
||||
createIcons({
|
||||
icons: {
|
||||
Menu,
|
||||
ArrowRight,
|
||||
Globe,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Additional Options
|
||||
|
||||
In the `createIcons` function you can pass some extra parameters to adjust the `nameAttr` or add custom attributes like for example classes.
|
||||
|
||||
Here is a full example:
|
||||
|
||||
```js
|
||||
import { createIcons } from 'lucide';
|
||||
|
||||
createIcons({
|
||||
attrs: {
|
||||
class: ['my-custom-class', 'icon'],
|
||||
'stroke-width': 1,
|
||||
stroke: '#333',
|
||||
},
|
||||
nameAttr: 'icon-name', // atrribute for the icon name.
|
||||
});
|
||||
```
|
||||
|
||||
#### Threeshake the library, only use the icons you use
|
||||
|
||||
```js
|
||||
import { createIcons, Menu, ArrowRight, Globe } from 'lucide';
|
||||
|
||||
createIcons({
|
||||
icons: {
|
||||
Menu,
|
||||
ArrowRight,
|
||||
Globe,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Custom Element binding
|
||||
|
||||
```js
|
||||
import { createElement, Menu } from 'lucide';
|
||||
|
||||
const menuIcon = createElement(Menu); // Returns HTMLElement (svg)
|
||||
|
||||
// set custom attributes with browser native functions
|
||||
menuIcon.setAttribute('stroke', '#333');
|
||||
menuIcon.classList.add('my-icon-class');
|
||||
|
||||
// Append HTMLElement in webpage
|
||||
const myApp = document.getElementById('app');
|
||||
myApp.appendChild(menuIcon);
|
||||
```
|
||||
|
||||
### Figma
|
||||
|
||||
@@ -25,15 +145,14 @@ You can use the components from [this Figma file](https://www.figma.com/file/g0U
|
||||
|
||||
## Contributing
|
||||
|
||||
For more info on how to contribute please see the [contribution guidelines](https://github.com/featherity/featherity/blob/master/CONTRIBUTING.md).
|
||||
For more info on how to contribute please see the [contribution guidelines](https://github.com/lucide-icons/lucide/blob/master/CONTRIBUTING.md).
|
||||
|
||||
Caught a mistake or want to contribute to the documentation? [Edit this page on Github](https://github.com/featherity/featherity/blob/master/README.md)
|
||||
Caught a mistake or want to contribute to the documentation? [Edit this page on Github](https://github.com/lucide-icons/lucide/blob/master/README.md)
|
||||
|
||||
## Community
|
||||
|
||||
Do you want to join our community?
|
||||
Join us in [discord](https://discord.gg/EH6nSts)!
|
||||
Join the community on our [Discord](https://discord.gg/EH6nSts) server!
|
||||
|
||||
## License
|
||||
|
||||
Feather is licensed under the [ISC License](https://github.com/featherity/featherity/blob/master/LICENSE).
|
||||
Lucide is licensed under the [ISC License](https://github.com/lucide-icons/lucide/blob/master/LICENSE).
|
||||
|
||||
27
babel.config.js
Normal file
@@ -0,0 +1,27 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
[
|
||||
'@babel/env',
|
||||
{
|
||||
loose: true,
|
||||
modules: false,
|
||||
},
|
||||
],
|
||||
],
|
||||
env: {
|
||||
test: {
|
||||
presets: ['@babel/env'],
|
||||
plugins: ['@babel/plugin-transform-runtime'],
|
||||
},
|
||||
dev: {
|
||||
plugins: [
|
||||
[
|
||||
'transform-inline-environment-variables',
|
||||
{
|
||||
include: ['NODE_ENV'],
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
15
icons/bluetooth-connected.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M7 7L17 17L12 22V2L17 7L7 17" />
|
||||
<line x1="18" y1="12" y2="12" x2="21" />
|
||||
<line x1="3" y1="12" y2="12" x2="6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 336 B |
15
icons/bluetooth-off.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M17 17L12 22V12L7 17" />
|
||||
<path d="M2 2L22 22" />
|
||||
<path d="M14.5 9.5L17 7L12 2V6.5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 309 B |
15
icons/bluetooth-searching.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M7 7l10 10-5 5V2l5 5L7 17" />
|
||||
<path d="M20.83 14.83a4 4 0 000-5.66" />
|
||||
<path d="M18 12h.01"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 317 B |
@@ -9,5 +9,5 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="6.5 6.5 17.5 17.5 12 23 12 1 17.5 6.5 6.5 17.5" />
|
||||
<path d="M7 7L17 17L12 22V2L17 7L7 17" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 279 B After Width: | Height: | Size: 252 B |
17
icons/bot.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect x="3" y="11" width="18" height="10" rx="2" />
|
||||
<circle cx="12" cy="5" r="2" />
|
||||
<path d="M12 7v4" />
|
||||
<line x1="8" y1="16" x2="8" y2="16" />
|
||||
<line x1="16" y1="16" x2="16" y2="16" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 403 B |
24
icons/box-select.svg
Normal file
@@ -0,0 +1,24 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M4 2a2 2 0 00-2 2" />
|
||||
<line x1="8" y1="2" x2="10" y2="2" />
|
||||
<line x1="14" y1="2" x2="16" y2="2" />
|
||||
<path d="M4 22a2 2 0 01-2-2" />
|
||||
<line x1="22" y1="8" x2="22" y2="10" />
|
||||
<line x1="22" y1="14" x2="22" y2="16" />
|
||||
<path d="M22 20a2 2 0 01-2 2" />
|
||||
<line x1="14" y1="22" x2="16" y2="22" />
|
||||
<line x1="8" y1="22" x2="10" y2="22" />
|
||||
<path d="M20 2a2 2 0 012 2" />
|
||||
<line x1="2" y1="14" x2="2" y2="16" />
|
||||
<line x1="2" y1="8" x2="2" y2="10" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 675 B |
22
icons/calculator.svg
Normal file
@@ -0,0 +1,22 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect x="4" y="2" width="16" height="20" rx="2" />
|
||||
<line x1="8" x2="16" y1="6" y2="6" />
|
||||
<line x1="16" x2="16" y1="14" y2="18" />
|
||||
<path d="M16 10H16.01" />
|
||||
<path d="M12 10H12.01" />
|
||||
<path d="M8 10H8.01" />
|
||||
<path d="M12 14H12.01" />
|
||||
<path d="M8 14H8.01" />
|
||||
<path d="M12 18H12.01" />
|
||||
<path d="M8 18H8.01" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 534 B |
@@ -9,7 +9,7 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="6" r="2" />
|
||||
<circle cx="12" cy="6" r="1" />
|
||||
<line x1="5" y1="12" x2="19" y2="12" />
|
||||
<circle cx="12" cy="18" r="2" />
|
||||
<circle cx="12" cy="18" r="1" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 319 B After Width: | Height: | Size: 319 B |
15
icons/equal-not.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="5" y1="9" x2="19" y2="9" />
|
||||
<line x1="5" y1="15" x2="19" y2="15" />
|
||||
<line x1="19" y1="5" x2="5" y2="19" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 331 B |
14
icons/equal.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="5" y1="9" x2="19" y2="9" />
|
||||
<line x1="5" y1="15" x2="19" y2="15" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 290 B |
14
icons/gauge.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M12 15l3.5-3.5"/>
|
||||
<path d="M20.3 18c.4-1 .7-2.2.7-3.4C21 9.8 17 6 12 6s-9 3.8-9 8.6c0 1.2.3 2.4.7 3.4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 326 B |
@@ -9,5 +9,5 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
|
||||
<path d="M20.42 4.58a5.4 5.4 0 00-7.65 0l-.77.78-.77-.78a5.4 5.4 0 00-7.65 0C1.46 6.7 1.33 10.28 4 13l8 8 8-8c2.67-2.72 2.54-6.3.42-8.42z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 360 B After Width: | Height: | Size: 352 B |
16
icons/image-off.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M2 2l20 20" />
|
||||
<path d="M9 3h10a2 2 0 012 2v10M3.59 3.59A2 2 0 003 5v14c0 1.1.9 2 2 2h14a2 2 0 001.41-.59" />
|
||||
<path d="M9.56 9.56a1.5 1.5 0 01-2.12-2.12" />
|
||||
<path d="M21 15l-5-5M5 21l8-8" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 416 B |
16
icons/layout-dashboard.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect x="3" y="3" width="7" height="9" />
|
||||
<rect x="14" y="3" width="7" height="5" />
|
||||
<rect x="14" y="12" width="7" height="9" />
|
||||
<rect x="3" y="16" width="7" height="5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 388 B |
15
icons/lightbulb.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="9" y1="18" x2="15" y2="18" />
|
||||
<line x1="10" y1="22" x2="14" y2="22" />
|
||||
<path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0018 8 6 6 0 006 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 018.91 14" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 417 B |
13
icons/mouse-pointer-2.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M4 4l7.07 17 2.51-7.39L21 11.07Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 256 B |
15
icons/move-diagonal-2.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="5 11 5 5 11 5" />
|
||||
<polyline points="19 13 19 19 13 19" />
|
||||
<line x1="5" y1="5" x2="19" y2="19" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 329 B |
15
icons/move-diagonal.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="13 5 19 5 19 11" />
|
||||
<polyline points="11 19 5 19 5 13" />
|
||||
<line x1="19" y1="5" x2="5" y2="19" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 329 B |
16
icons/move-horizontal.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="18 8 22 12 18 16" />
|
||||
<polyline points="6 8 2 12 6 16" />
|
||||
<line x1="2" y1="12" x2="22" y2="12" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 330 B |
15
icons/move-vertical.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="8 18 12 22 16 18" />
|
||||
<polyline points="8 6 12 2 16 6" />
|
||||
<line x1="12" y1="2" x2="12" y2="22" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 329 B |
15
icons/separator-horizontal.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="3" y1="12" x2="21" y2="12" />
|
||||
<polyline points="8 8 12 4 16 8" />
|
||||
<polyline points="16 16 12 20 8 16" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 329 B |
15
icons/separator-vertical.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="12" y1="3" x2="12" y2="21" />
|
||||
<polyline points="8 8 4 12 8 16" />
|
||||
<polyline points="16 16 20 12 16 8" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 329 B |
13
icons/shirt.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M20.38 3.46L16 2a4 4 0 01-8 0L3.62 3.46a2 2 0 00-1.34 2.23l.58 3.47a1 1 0 00.99.84H6v10c0 1.1.9 2 2 2h8a2 2 0 002-2V10h2.15a1 1 0 00.99-.84l.58-3.47a2 2 0 00-1.34-2.23z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 391 B |
@@ -9,11 +9,11 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="1" y1="1" x2="23" y2="23" />
|
||||
<path d="M16.72 11.06A10.94 10.94 0 0 1 19 12.55" />
|
||||
<path d="M5 12.55a10.94 10.94 0 0 1 5.17-2.39" />
|
||||
<path d="M10.71 5.05A16 16 0 0 1 22.58 9" />
|
||||
<path d="M1.42 9a15.91 15.91 0 0 1 4.7-2.88" />
|
||||
<path d="M8.53 16.11a6 6 0 0 1 6.95 0" />
|
||||
<line x1="2" y1="2" x2="22" y2="22" />
|
||||
<path d="M8.5 16.5a5 5 0 017 0" />
|
||||
<path d="M2 8.82a15 15 0 014.17-2.65" />
|
||||
<path d="M10.66 5c4.01-.36 8.14.9 11.34 3.76" />
|
||||
<path d="M16.85 11.25a10 10 0 012.22 1.68" />
|
||||
<path d="M5 13a10 10 0 015.24-2.76" />
|
||||
<line x1="12" y1="20" x2="12.01" y2="20" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 543 B After Width: | Height: | Size: 515 B |
@@ -9,8 +9,8 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M5 12.55a11 11 0 0 1 14.08 0" />
|
||||
<path d="M1.42 9a16 16 0 0 1 21.16 0" />
|
||||
<path d="M8.53 16.11a6 6 0 0 1 6.95 0" />
|
||||
<path d="M5 13a10 10 0 0114 0" />
|
||||
<path d="M8.5 16.5a5 5 0 017 0" />
|
||||
<path d="M2 8.82a15 15 0 0120 0" />
|
||||
<line x1="12" y1="20" x2="12.01" y2="20" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 385 B After Width: | Height: | Size: 365 B |
14
jest.config.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const esModules = ['lodash-es'].join('|');
|
||||
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
roots: ['<rootDir>/src/', '<rootDir>/tests/'],
|
||||
moduleFileExtensions: ['js'],
|
||||
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
},
|
||||
transform: {
|
||||
'^.+\\.js$': 'babel-jest',
|
||||
},
|
||||
};
|
||||
65
package.json
@@ -1,9 +1,64 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "lucide",
|
||||
"description": "Lucide is a community-run fork of Feather Icons, open for anyone to contribute icons.",
|
||||
"version": "0.1.1",
|
||||
"license": "ISC",
|
||||
"amdName": "lucide",
|
||||
"homepage": "https://featherity.netlify.app",
|
||||
"url": "https://github.com/owner/project/issues",
|
||||
"repository": "github:lucide-icons/lucide",
|
||||
"source": "build/lucide.js",
|
||||
"main": "dist/cjs/lucide.js",
|
||||
"main:umd": "dist/umd/lucide.js",
|
||||
"module": "lib/lucide.js",
|
||||
"unpkg": "dist/umd/lucide.min.js",
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"react:compile": "yarn workspace react compile"
|
||||
"start": "babel-watch --watch src",
|
||||
"clean": "rimraf lib && rimraf dist && rimraf build",
|
||||
"build": "yarn clean && yarn build:move && yarn build:icons && yarn build:es && yarn build:esbrowser && yarn build:bundles",
|
||||
"build:move": "cp -av src build",
|
||||
"build:icons": "npx babel-node ./scripts/buildIcons.js --presets @babel/env",
|
||||
"build:es": "babel build -d lib --source-maps --ignore '**/*.test.js','**/__mocks__'",
|
||||
"build:esbrowser": "BROWSER_COMPAT=true yarn build:es -d dist/esm",
|
||||
"build:bundles": "BROWSER_COMPAT=true rollup -c rollup.config.js",
|
||||
"optimize": "npx babel-node ./scripts/optimizeSvgs.js --presets @babel/env",
|
||||
"test": "jest"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/react"
|
||||
]
|
||||
"devDependencies": {
|
||||
"@ampproject/rollup-plugin-closure-compiler": "^0.25.2",
|
||||
"@atomico/rollup-plugin-sizes": "^1.1.4",
|
||||
"@babel/cli": "^7.10.5",
|
||||
"@babel/core": "^7.11.1",
|
||||
"@babel/node": "^7.10.5",
|
||||
"@babel/preset-env": "^7.11.0",
|
||||
"@rollup/plugin-babel": "^5.0.0",
|
||||
"babel-jest": "^26.3.0",
|
||||
"babel-plugin-add-import-extension": "^1.4.3",
|
||||
"cheerio": "^1.0.0-rc.2",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-airbnb-base": "^12.1.0",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-plugin-import": "^2.5.0",
|
||||
"eslint-plugin-prettier": "^2.5.0",
|
||||
"html-minifier": "^3.5.8",
|
||||
"jest": "^26.4.2",
|
||||
"lodash": "^4.17.19",
|
||||
"prettier": "^1.8.2",
|
||||
"rollup": "^2.7.3",
|
||||
"rollup-plugin-commonjs": "^10.1.0",
|
||||
"rollup-plugin-license": "^2.0.0",
|
||||
"rollup-plugin-node-resolve": "^5.2.0",
|
||||
"rollup-plugin-replace": "^2.2.0",
|
||||
"rollup-plugin-terser": "^5.2.0",
|
||||
"rollup-plugin-visualizer": "^4.1.0",
|
||||
"svgo": "^1.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-runtime": "^7.11.5",
|
||||
"core-js": "3",
|
||||
"htmlparser2": "^4.1.0",
|
||||
"lodash-es": "^4.17.15",
|
||||
"prop-types": "^15.7.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"targets": {
|
||||
"browsers": ["last 2 versions"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"stage-2"
|
||||
]
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
dist
|
||||
coverage
|
||||
@@ -1,8 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`builds object correctly 1`] = `
|
||||
Object {
|
||||
"icon1": "<line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\"></line><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\"></line>",
|
||||
"icon2": "<circle cx=\\"12\\" cy=\\"12\\" r=\\"11\\"></circle>",
|
||||
}
|
||||
`;
|
||||
@@ -1,3 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`builds sprite correctly 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\"><defs><symbol id=\\"icon1\\" viewBox=\\"0 0 24 24\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\"></line><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\"></line></symbol><symbol id=\\"icon2\\" viewBox=\\"0 0 24 24\\"><circle cx=\\"12\\" cy=\\"12\\" r=\\"11\\"></circle></symbol></defs></svg>"`;
|
||||
@@ -1,26 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`processes SVG correctly 1`] = `
|
||||
"<svg
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
width=\\"24\\"
|
||||
height=\\"24\\"
|
||||
viewBox=\\"0 0 24 24\\"
|
||||
fill=\\"none\\"
|
||||
stroke=\\"currentColor\\"
|
||||
stroke-width=\\"2\\"
|
||||
stroke-linecap=\\"round\\"
|
||||
stroke-linejoin=\\"round\\"
|
||||
>
|
||||
<line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" />
|
||||
<line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" />
|
||||
</svg>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`rejects when passed unparsable SVG string 1`] = `
|
||||
[Error: Error in parsing SVG: Unclosed root tag
|
||||
Line: 0
|
||||
Column: 10
|
||||
Char: ]
|
||||
`;
|
||||
@@ -1,17 +0,0 @@
|
||||
/* eslint-env jest */
|
||||
import buildIconsObject from '../build-icons-object';
|
||||
|
||||
const SVG_FILES = {
|
||||
'icon1.svg':
|
||||
'<svg\n xmlns="http://www.w3.org/2000/svg"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n>\n <line x1="23" y1="1" x2="1" y2="23" />\n <line x1="1" y1="1" x2="23" y2="23" />\n</svg>',
|
||||
'icon2.svg':
|
||||
'<svg\n xmlns="http://www.w3.org/2000/svg"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n>\n <circle cx="12" cy="12" r="11" />\n</svg>',
|
||||
};
|
||||
|
||||
function getSvg(svgFile) {
|
||||
return SVG_FILES[svgFile];
|
||||
}
|
||||
|
||||
test('builds object correctly', () => {
|
||||
expect(buildIconsObject(Object.keys(SVG_FILES), getSvg)).toMatchSnapshot();
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
/* eslint-env jest */
|
||||
import buildSpriteString from '../build-sprite-string';
|
||||
|
||||
const icons = {
|
||||
icon1:
|
||||
'<line x1="23" y1="1" x2="1" y2="23"></line><line x1="1" y1="1" x2="23" y2="23"></line>',
|
||||
icon2: '<circle cx="12" cy="12" r="11"></circle>',
|
||||
};
|
||||
|
||||
test('builds sprite correctly', () => {
|
||||
expect(buildSpriteString(icons)).toMatchSnapshot();
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
/* eslint-env jest */
|
||||
import processSvg from '../process-svg';
|
||||
|
||||
test('processes SVG correctly', () => {
|
||||
const SVG =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>Title</title><line x1="23" y1="1" x2="1" y2="23" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><line x1="1" y1="1" x2="23" y2="23" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/></svg>';
|
||||
|
||||
expect(processSvg(SVG)).resolves.toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('rejects when passed unparsable SVG string', () => {
|
||||
const UNPARSABLE_SVG = '<svg></svg';
|
||||
|
||||
expect(processSvg(UNPARSABLE_SVG)).rejects.toMatchSnapshot();
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import buildIconsObject from './build-icons-object';
|
||||
|
||||
const IN_DIR = path.resolve(__dirname, '../icons');
|
||||
const OUT_FILE = path.resolve(__dirname, '../dist/icons.json');
|
||||
|
||||
console.log(`Building ${OUT_FILE}...`);
|
||||
|
||||
const svgFiles = fs
|
||||
.readdirSync(IN_DIR)
|
||||
.filter(file => path.extname(file) === '.svg');
|
||||
|
||||
const getSvg = svgFile => fs.readFileSync(path.join(IN_DIR, svgFile));
|
||||
|
||||
const icons = buildIconsObject(svgFiles, getSvg);
|
||||
|
||||
fs.writeFileSync(OUT_FILE, JSON.stringify(icons));
|
||||
@@ -1,28 +0,0 @@
|
||||
import DEFAULT_ATTRS from '../src/default-attrs.json';
|
||||
|
||||
/**
|
||||
* Build an SVG sprite string containing SVG symbols.
|
||||
* @param {Object} icons
|
||||
* @returns {string}
|
||||
*/
|
||||
function buildSpriteString(icons) {
|
||||
const symbols = Object.keys(icons)
|
||||
.map(icon => toSvgSymbol(icon, icons[icon]))
|
||||
.join('');
|
||||
|
||||
return `<svg xmlns="${DEFAULT_ATTRS.xmlns}"><defs>${symbols}</defs></svg>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an SVG symbol string.
|
||||
* @param {string} name - Icon name
|
||||
* @param {string} contents - SVG contents
|
||||
* @returns {string}
|
||||
*/
|
||||
function toSvgSymbol(name, contents) {
|
||||
return `<symbol id="${name}" viewBox="${DEFAULT_ATTRS.viewBox}">${
|
||||
contents
|
||||
}</symbol>`;
|
||||
}
|
||||
|
||||
export default buildSpriteString;
|
||||
@@ -1,10 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import icons from '../dist/icons.json';
|
||||
import buildSpriteString from './build-sprite-string';
|
||||
|
||||
const OUT_FILE = path.resolve(__dirname, '../dist/feather-sprite.svg');
|
||||
|
||||
console.log(`Building ${OUT_FILE}...`);
|
||||
|
||||
fs.writeFileSync(OUT_FILE, buildSpriteString(icons));
|
||||
@@ -1,13 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import icons from '../src/icons';
|
||||
|
||||
const OUT_DIR = path.resolve(__dirname, '../dist/icons');
|
||||
|
||||
console.log(`Building SVGs in ${OUT_DIR}...`);
|
||||
|
||||
Object.keys(icons).forEach(name => {
|
||||
const svg = icons[name].toSvg();
|
||||
|
||||
fs.writeFileSync(path.join(OUT_DIR, `${name}.svg`), svg);
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Process SVG files
|
||||
npx babel-node bin/process-svgs.js
|
||||
|
||||
# Create dist directory
|
||||
npx rimraf dist
|
||||
mkdir dist
|
||||
|
||||
# Build icons.json
|
||||
npx babel-node bin/build-icons-json.js
|
||||
|
||||
# Build SVG sprite
|
||||
npx babel-node bin/build-sprite.js
|
||||
|
||||
# Create dist/icons directory
|
||||
npx rimraf dist/icons
|
||||
mkdir dist/icons
|
||||
|
||||
# Build SVG icons
|
||||
npx babel-node bin/build-svgs.js
|
||||
|
||||
# Build JavaScript library
|
||||
npx webpack --output-filename feather.js --mode development
|
||||
npx webpack --output-filename feather.min.js --mode production
|
||||
@@ -1,18 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import processSvg from './process-svg';
|
||||
|
||||
const IN_DIR = path.resolve(__dirname, '../icons');
|
||||
|
||||
console.log(`Processing SVGs in ${IN_DIR}...`);
|
||||
|
||||
fs
|
||||
.readdirSync(IN_DIR)
|
||||
.filter(file => path.extname(file) === '.svg')
|
||||
.forEach(svgFile => {
|
||||
const svg = fs.readFileSync(path.join(IN_DIR, svgFile));
|
||||
processSvg(svg).then(svg =>
|
||||
fs.writeFileSync(path.join(IN_DIR, svgFile), svg),
|
||||
);
|
||||
});
|
||||
@@ -1,73 +0,0 @@
|
||||
import algolia from 'algoliasearch';
|
||||
import icons from '../dist/icons.json';
|
||||
import tags from '../src/tags.json';
|
||||
|
||||
const ALGOLIA_APP_ID = '5EEOG744D0';
|
||||
|
||||
if (
|
||||
process.env.TRAVIS_PULL_REQUEST === 'false' &&
|
||||
process.env.TRAVIS_BRANCH === 'master'
|
||||
) {
|
||||
syncAlgolia();
|
||||
} else {
|
||||
console.log('Skipped Algolia sync.');
|
||||
}
|
||||
|
||||
function syncAlgolia() {
|
||||
// ALGOLIA_ADMIN_KEY must be added as an environment variable in Travis CI
|
||||
const client = algolia(ALGOLIA_APP_ID, process.env.ALGOLIA_ADMIN_KEY);
|
||||
|
||||
console.log('Initializing target and temporary indexes...');
|
||||
const index = client.initIndex('icons');
|
||||
const indexTmp = client.initIndex('icons_tmp');
|
||||
|
||||
console.log(
|
||||
"Copying target index's settings, synonyms and rules into temporary index...",
|
||||
);
|
||||
scopedCopyIndex(client, index.indexName, indexTmp.indexName)
|
||||
.then(() => {
|
||||
const objects = Object.keys(icons).map(name => ({
|
||||
name,
|
||||
tags: tags[name] || [],
|
||||
}));
|
||||
|
||||
console.log('Adding objects to the temporary index...');
|
||||
return addObjects(indexTmp, objects);
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Moving temporary index to target index...');
|
||||
return moveIndex(client, indexTmp.indexName, index.indexName);
|
||||
});
|
||||
}
|
||||
|
||||
function scopedCopyIndex(
|
||||
client,
|
||||
indexNameSrc,
|
||||
indexNameDest,
|
||||
scope = ['settings', 'synonyms', 'rules'],
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
client.copyIndex(indexNameSrc, indexNameDest, scope, (error, contents) => {
|
||||
if (error) reject(error);
|
||||
resolve(contents);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function addObjects(index, objects) {
|
||||
return new Promise((resolve, reject) => {
|
||||
index.addObjects(objects, (error, contents) => {
|
||||
if (error) reject(error);
|
||||
resolve(contents);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function moveIndex(client, indexNameSrc, indexNameDest) {
|
||||
return new Promise((resolve, reject) => {
|
||||
client.moveIndex(indexNameSrc, indexNameDest, (error, contents) => {
|
||||
if (error) reject(error);
|
||||
resolve(contents);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"name": "feather-icons",
|
||||
"version": "0.0.0-development",
|
||||
"description": "Simply beautiful open source icons",
|
||||
"main": "dist/feather.js",
|
||||
"unpkg": "dist/feather.min.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"all": "npm-run-all --sequential build lint test:coverage",
|
||||
"build": "./bin/build.sh",
|
||||
"lint": "eslint .",
|
||||
"test": "jest --watch",
|
||||
"test:coverage": "jest --coverage",
|
||||
"cm": "git-cz",
|
||||
"precommit": "lint-staged",
|
||||
"commitmsg": "commitlint --edit"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "cz-conventional-changelog"
|
||||
}
|
||||
},
|
||||
"jest": {
|
||||
"collectCoverageFrom": [
|
||||
"src/**/*.js"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.5",
|
||||
"core-js": "^3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^5.2.5",
|
||||
"@commitlint/config-conventional": "^6.1.3",
|
||||
"algoliasearch": "^3.27.1",
|
||||
"babel-cli": "^6.24.1",
|
||||
"babel-loader": "^7.1.1",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-preset-stage-2": "^6.24.1",
|
||||
"cheerio": "^1.0.0-rc.2",
|
||||
"commitizen": "^2.9.6",
|
||||
"cz-conventional-changelog": "^2.1.0",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-airbnb-base": "^12.1.0",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-plugin-import": "^2.5.0",
|
||||
"eslint-plugin-prettier": "^2.5.0",
|
||||
"html-minifier": "^3.5.8",
|
||||
"husky": "^0.14.3",
|
||||
"jest": "^22.4.4",
|
||||
"lint-staged": "^6.0.0",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"prettier": "^1.8.2",
|
||||
"semantic-release": "^12.2.2",
|
||||
"svgo": "^0.7.2",
|
||||
"webpack": "^4.8.3",
|
||||
"webpack-cli": "^2.1.3"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/feathericons/feather.git"
|
||||
},
|
||||
"author": "Cole Bemis <cole@colebemis.com> (http://colebemis.com)",
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`constructs icon object correctly 1`] = `
|
||||
Icon {
|
||||
"attrs": Object {
|
||||
"class": "feather feather-test",
|
||||
"fill": "none",
|
||||
"height": 24,
|
||||
"stroke": "currentColor",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round",
|
||||
"stroke-width": 2,
|
||||
"viewBox": "0 0 24 24",
|
||||
"width": 24,
|
||||
"xmlns": "http://www.w3.org/2000/svg",
|
||||
},
|
||||
"contents": "<line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" />",
|
||||
"name": "test",
|
||||
"tags": Array [
|
||||
"hello",
|
||||
"world",
|
||||
"foo",
|
||||
"bar",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`constructs icon object correctly 2`] = `
|
||||
Icon {
|
||||
"attrs": Object {
|
||||
"class": "feather feather-test",
|
||||
"fill": "none",
|
||||
"height": 24,
|
||||
"stroke": "currentColor",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round",
|
||||
"stroke-width": 2,
|
||||
"viewBox": "0 0 24 24",
|
||||
"width": 24,
|
||||
"xmlns": "http://www.w3.org/2000/svg",
|
||||
},
|
||||
"contents": "<line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" />",
|
||||
"name": "test",
|
||||
"tags": Array [],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`toString() returns correct string 1`] = `"<line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" />"`;
|
||||
|
||||
exports[`toSvg() returns correct string 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-test\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;
|
||||
|
||||
exports[`toSvg() returns correct string 2`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"1\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-test\\" color=\\"red\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;
|
||||
|
||||
exports[`toSvg() returns correct string 3`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-test foo bar\\" color=\\"red\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;
|
||||
@@ -1,45 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`exports correct object 1`] = `
|
||||
Object {
|
||||
"icon1": Icon {
|
||||
"attrs": Object {
|
||||
"class": "feather feather-icon1",
|
||||
"fill": "none",
|
||||
"height": 24,
|
||||
"stroke": "currentColor",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round",
|
||||
"stroke-width": 2,
|
||||
"viewBox": "0 0 24 24",
|
||||
"width": 24,
|
||||
"xmlns": "http://www.w3.org/2000/svg",
|
||||
},
|
||||
"contents": "<line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" />",
|
||||
"name": "icon1",
|
||||
"tags": Array [
|
||||
"foo",
|
||||
"bar",
|
||||
"hello",
|
||||
"world",
|
||||
],
|
||||
},
|
||||
"icon2": Icon {
|
||||
"attrs": Object {
|
||||
"class": "feather feather-icon2",
|
||||
"fill": "none",
|
||||
"height": 24,
|
||||
"stroke": "currentColor",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round",
|
||||
"stroke-width": 2,
|
||||
"viewBox": "0 0 24 24",
|
||||
"width": 24,
|
||||
"xmlns": "http://www.w3.org/2000/svg",
|
||||
},
|
||||
"contents": "<circle cx=\\"12\\" cy=\\"12\\" r=\\"11\\" />",
|
||||
"name": "icon2",
|
||||
"tags": Array [],
|
||||
},
|
||||
}
|
||||
`;
|
||||
@@ -1,3 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`throws an error when run in node environment 1`] = `"\`feather.replace()\` only works in a browser environment."`;
|
||||
@@ -1,13 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`copies placeholder element attributes to <svg> tag 1`] = `"<i data-feather=\\"icon1\\" id=\\"test\\" class=\\"foo bar\\" stroke-width=\\"1\\"></i>"`;
|
||||
|
||||
exports[`copies placeholder element attributes to <svg> tag 2`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"1\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1 foo bar\\" id=\\"test\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\"></line><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\"></line></svg>"`;
|
||||
|
||||
exports[`replaces [data-feather] elements with SVG markup 1`] = `"<i data-feather=\\"icon1\\"></i><span data-feather=\\"icon2\\"></span>"`;
|
||||
|
||||
exports[`replaces [data-feather] elements with SVG markup 2`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\"></line><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\"></line></svg><svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon2\\"><circle cx=\\"12\\" cy=\\"12\\" r=\\"11\\"></circle></svg>"`;
|
||||
|
||||
exports[`sets attributes passed as parameters 1`] = `"<i data-feather=\\"icon1\\" id=\\"test\\" class=\\"foo bar\\" stroke-width=\\"1\\"></i>"`;
|
||||
|
||||
exports[`sets attributes passed as parameters 2`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"1\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1 foo bar hello\\" color=\\"salmon\\" id=\\"test\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\"></line><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\"></line></svg>"`;
|
||||
@@ -1,7 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`returns correct string 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;
|
||||
|
||||
exports[`returns correct string 2`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"1\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1\\" color=\\"red\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;
|
||||
|
||||
exports[`returns correct string 3`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1 foo bar\\" color=\\"red\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;
|
||||
@@ -1,28 +0,0 @@
|
||||
/* eslint-env jest */
|
||||
import Icon from '../icon';
|
||||
|
||||
const icon1 = new Icon(
|
||||
'test',
|
||||
'<line x1="23" y1="1" x2="1" y2="23" /><line x1="1" y1="1" x2="23" y2="23" />',
|
||||
['hello', 'world', 'foo', 'bar'],
|
||||
);
|
||||
|
||||
const icon2 = new Icon(
|
||||
'test',
|
||||
'<line x1="23" y1="1" x2="1" y2="23" /><line x1="1" y1="1" x2="23" y2="23" />',
|
||||
);
|
||||
|
||||
test('constructs icon object correctly', () => {
|
||||
expect(icon1).toMatchSnapshot();
|
||||
expect(icon2).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('toSvg() returns correct string', () => {
|
||||
expect(icon1.toSvg()).toMatchSnapshot();
|
||||
expect(icon1.toSvg({ 'stroke-width': 1, color: 'red' })).toMatchSnapshot();
|
||||
expect(icon1.toSvg({ class: 'foo bar', color: 'red' })).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('toString() returns correct string', () => {
|
||||
expect(icon1.toString()).toMatchSnapshot();
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
/* eslint-env jest */
|
||||
import icons from '../icons';
|
||||
|
||||
jest.mock('../../dist/icons.json', () => ({
|
||||
icon1:
|
||||
'<line x1="23" y1="1" x2="1" y2="23" /><line x1="1" y1="1" x2="23" y2="23" />',
|
||||
icon2: '<circle cx="12" cy="12" r="11" />',
|
||||
}));
|
||||
|
||||
jest.mock('../tags.json', () => ({
|
||||
icon1: ['foo', 'bar', 'hello', 'world'],
|
||||
}));
|
||||
|
||||
test('exports correct object', () => {
|
||||
expect(icons).toMatchSnapshot();
|
||||
});
|
||||
@@ -1,8 +0,0 @@
|
||||
/* eslint-env jest */
|
||||
import feather from '../index';
|
||||
|
||||
test('has correct properties', () => {
|
||||
expect(feather).toHaveProperty('icons');
|
||||
expect(feather).toHaveProperty('toSvg');
|
||||
expect(feather).toHaveProperty('replace');
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
/* eslint-env jest */
|
||||
import replace from '../replace';
|
||||
|
||||
test('throws an error when run in node environment', () => {
|
||||
expect(replace).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
/* eslint-env jest, browser */
|
||||
import replace from '../replace';
|
||||
|
||||
jest.mock('../../dist/icons.json', () => ({
|
||||
icon1:
|
||||
'<line x1="23" y1="1" x2="1" y2="23" /><line x1="1" y1="1" x2="23" y2="23" />',
|
||||
icon2: '<circle cx="12" cy="12" r="11" />',
|
||||
}));
|
||||
|
||||
test('replaces [data-feather] elements with SVG markup', () => {
|
||||
document.body.innerHTML =
|
||||
'<i data-feather="icon1"></i><span data-feather="icon2"></i>';
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
replace();
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('copies placeholder element attributes to <svg> tag', () => {
|
||||
document.body.innerHTML =
|
||||
'<i data-feather="icon1" id="test" class="foo bar" stroke-width="1"></i>';
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
replace();
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('sets attributes passed as parameters', () => {
|
||||
document.body.innerHTML =
|
||||
'<i data-feather="icon1" id="test" class="foo bar" stroke-width="1"></i>';
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
replace({ class: 'foo bar hello', 'stroke-width': 1.5, color: 'salmon' });
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
/* eslint-env jest */
|
||||
import toSvg from '../to-svg';
|
||||
|
||||
jest.mock('../../dist/icons.json', () => ({
|
||||
icon1:
|
||||
'<line x1="23" y1="1" x2="1" y2="23" /><line x1="1" y1="1" x2="23" y2="23" />',
|
||||
}));
|
||||
|
||||
test('returns correct string', () => {
|
||||
expect(toSvg('icon1')).toMatchSnapshot();
|
||||
expect(toSvg('icon1', { 'stroke-width': 1, color: 'red' })).toMatchSnapshot();
|
||||
expect(toSvg('icon1', { class: 'foo bar', color: 'red' })).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('throws error when `name` parameter is undefined', () => {
|
||||
expect(() => toSvg()).toThrow();
|
||||
});
|
||||
|
||||
test('throws error when passed unknown icon name', () => {
|
||||
expect(() => toSvg('foo')).toThrow();
|
||||
});
|
||||
@@ -1,55 +0,0 @@
|
||||
import classnames from 'classnames/dedupe';
|
||||
|
||||
import DEFAULT_ATTRS from './default-attrs.json';
|
||||
|
||||
class Icon {
|
||||
constructor(name, contents, tags = []) {
|
||||
this.name = name;
|
||||
this.contents = contents;
|
||||
this.tags = tags;
|
||||
this.attrs = {
|
||||
...DEFAULT_ATTRS,
|
||||
...{ class: `feather feather-${name}` },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an SVG string.
|
||||
* @param {Object} attrs
|
||||
* @returns {string}
|
||||
*/
|
||||
toSvg(attrs = {}) {
|
||||
const combinedAttrs = {
|
||||
...this.attrs,
|
||||
...attrs,
|
||||
...{ class: classnames(this.attrs.class, attrs.class) },
|
||||
};
|
||||
|
||||
return `<svg ${attrsToString(combinedAttrs)}>${this.contents}</svg>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return string representation of an `Icon`.
|
||||
*
|
||||
* Added for backward compatibility. If old code expects `feather.icons.<name>`
|
||||
* to be a string, `toString()` will get implicitly called.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
toString() {
|
||||
return this.contents;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert attributes object to string of HTML attributes.
|
||||
* @param {Object} attrs
|
||||
* @returns {string}
|
||||
*/
|
||||
function attrsToString(attrs) {
|
||||
return Object.keys(attrs)
|
||||
.map(key => `${key}="${attrs[key]}"`)
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
export default Icon;
|
||||
@@ -1,10 +0,0 @@
|
||||
import Icon from './icon';
|
||||
import icons from '../dist/icons.json';
|
||||
import tags from './tags.json';
|
||||
|
||||
export default Object.keys(icons)
|
||||
.map(key => new Icon(key, icons[key], tags[key]))
|
||||
.reduce((object, icon) => {
|
||||
object[icon.name] = icon;
|
||||
return object;
|
||||
}, {});
|
||||
@@ -1,5 +0,0 @@
|
||||
import icons from './icons';
|
||||
import toSvg from './to-svg';
|
||||
import replace from './replace';
|
||||
|
||||
module.exports = { icons, toSvg, replace };
|
||||
@@ -1,60 +0,0 @@
|
||||
/* eslint-env browser */
|
||||
import classnames from 'classnames/dedupe';
|
||||
|
||||
import icons from './icons';
|
||||
|
||||
/**
|
||||
* Replace all HTML elements that have a `data-feather` attribute with SVG markup
|
||||
* corresponding to the element's `data-feather` attribute value.
|
||||
* @param {Object} attrs
|
||||
*/
|
||||
function replace(attrs = {}) {
|
||||
if (typeof document === 'undefined') {
|
||||
throw new Error('`feather.replace()` only works in a browser environment.');
|
||||
}
|
||||
|
||||
const elementsToReplace = document.querySelectorAll('[data-feather]');
|
||||
|
||||
Array.from(elementsToReplace).forEach(element =>
|
||||
replaceElement(element, attrs),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a single HTML element with SVG markup
|
||||
* corresponding to the element's `data-feather` attribute value.
|
||||
* @param {HTMLElement} element
|
||||
* @param {Object} attrs
|
||||
*/
|
||||
function replaceElement(element, attrs = {}) {
|
||||
const elementAttrs = getAttrs(element);
|
||||
const name = elementAttrs['data-feather'];
|
||||
delete elementAttrs['data-feather'];
|
||||
|
||||
const svgString = icons[name].toSvg({
|
||||
...attrs,
|
||||
...elementAttrs,
|
||||
...{ class: classnames(attrs.class, elementAttrs.class) },
|
||||
});
|
||||
const svgDocument = new DOMParser().parseFromString(
|
||||
svgString,
|
||||
'image/svg+xml',
|
||||
);
|
||||
const svgElement = svgDocument.querySelector('svg');
|
||||
|
||||
element.parentNode.replaceChild(svgElement, element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attributes of an HTML element.
|
||||
* @param {HTMLElement} element
|
||||
* @returns {Object}
|
||||
*/
|
||||
function getAttrs(element) {
|
||||
return Array.from(element.attributes).reduce((attrs, attr) => {
|
||||
attrs[attr.name] = attr.value;
|
||||
return attrs;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export default replace;
|
||||
@@ -1,30 +0,0 @@
|
||||
import icons from './icons';
|
||||
|
||||
/**
|
||||
* Create an SVG string.
|
||||
* @deprecated
|
||||
* @param {string} name
|
||||
* @param {Object} attrs
|
||||
* @returns {string}
|
||||
*/
|
||||
function toSvg(name, attrs = {}) {
|
||||
console.warn(
|
||||
'feather.toSvg() is deprecated. Please use feather.icons[name].toSvg() instead.',
|
||||
);
|
||||
|
||||
if (!name) {
|
||||
throw new Error('The required `key` (icon name) parameter is missing.');
|
||||
}
|
||||
|
||||
if (!icons[name]) {
|
||||
throw new Error(
|
||||
`No icon matching '${
|
||||
name
|
||||
}'. See the complete list of icons at https://feathericons.com`,
|
||||
);
|
||||
}
|
||||
|
||||
return icons[name].toSvg(attrs);
|
||||
}
|
||||
|
||||
export default toSvg;
|
||||
@@ -1,23 +0,0 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: ['core-js/es/array/from', path.resolve(__dirname, 'src/index.js')],
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
libraryTarget: 'umd',
|
||||
library: 'feather',
|
||||
// Prevents webpack from referencing `window` in the UMD build
|
||||
// Source: https://git.io/vppgU
|
||||
globalObject: "typeof self !== 'undefined' ? self : this",
|
||||
},
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-featherity",
|
||||
"name": "lucide-react",
|
||||
"version": "1.0.0",
|
||||
"description": "React component for Featherity icons",
|
||||
"description": "React component for lucide icons",
|
||||
"main": "src/index.js",
|
||||
"typings": "src/index.d.ts",
|
||||
"author": "John Letey",
|
||||
|
||||
1
packages/site/.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
.next
|
||||
2
packages/site/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
32
packages/site/package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "site",
|
||||
"version": "1.0.0",
|
||||
"author": "John Letey",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"export": "next export",
|
||||
"deploy": "yarn build && yarn export"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/core": "^0.8.0",
|
||||
"@emotion/core": "^10.0.28",
|
||||
"@emotion/styled": "^10.0.27",
|
||||
"downloadjs": "^1.4.7",
|
||||
"emotion-theming": "^10.0.27",
|
||||
"fuse.js": "^6.0.4",
|
||||
"jszip": "^3.4.0",
|
||||
"next": "^9.4.4",
|
||||
"query-string": "^6.13.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"use-query-params": "^1.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.11",
|
||||
"@types/react": "^16.9.35",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"typescript": "^3.9.5"
|
||||
}
|
||||
}
|
||||
58
packages/site/src/components/Layout.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Box, Divider, Flex, Text, Link, Icon, useColorMode } from "@chakra-ui/core";
|
||||
import { StringParam, useQueryParam } from "use-query-params";
|
||||
import { useKeyBindings } from "../lib/key";
|
||||
|
||||
const Layout = ({ children }) => {
|
||||
const [, setQuery] = useQueryParam("query", StringParam);
|
||||
const { colorMode, toggleColorMode } = useColorMode();
|
||||
|
||||
useKeyBindings({
|
||||
Escape: {
|
||||
fn: () => setQuery(""),
|
||||
},
|
||||
KeyT: {
|
||||
fn: () => toggleColorMode(),
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Box h="100vh">
|
||||
<Flex mb={16} w="full">
|
||||
<Flex
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
pt={4}
|
||||
pb={4}
|
||||
maxW="1250px"
|
||||
margin="0 auto"
|
||||
w="full"
|
||||
px={8}
|
||||
>
|
||||
<Flex justifyContent="center" alignItems="center">
|
||||
<Text
|
||||
fontSize="4xl"
|
||||
onClick={() => setQuery("")}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
Featherity
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex justifyContent="center" alignItems="center">
|
||||
<Link href="https://github.com/lucide-icons/lucide" isExternal style={{ fontSize: "18px", marginRight: '24px' }}>
|
||||
Github
|
||||
</Link>
|
||||
<div onClick={toggleColorMode} style={{ cursor: "pointer" }}>
|
||||
<Icon name={colorMode == "light" ? "moon" : "sun"} size="24px" />
|
||||
</div>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex margin="0 auto" direction="column" maxW="1250px" px={8}>
|
||||
{children}
|
||||
<Divider marginTop={10} marginBottom={10} />
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
32
packages/site/src/lib/icons.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import tags from '../../../../tags.json';
|
||||
|
||||
const directory = path.join(process.cwd(), "../../icons");
|
||||
|
||||
export function getAllNames() {
|
||||
const fileNames = fs.readdirSync(directory);
|
||||
|
||||
return fileNames.map((fileName) => {
|
||||
return fileName.replace(/\.svg$/, "");
|
||||
});
|
||||
}
|
||||
|
||||
export function getData(name) {
|
||||
const fullPath = path.join(directory, `${name}.svg`);
|
||||
const fileContents = fs.readFileSync(fullPath, "utf8");
|
||||
|
||||
return {
|
||||
name,
|
||||
tags: tags[name] || [],
|
||||
src: fileContents,
|
||||
};
|
||||
}
|
||||
|
||||
export function getAllData() {
|
||||
const names = getAllNames();
|
||||
|
||||
return names.map((name) => {
|
||||
return getData(name);
|
||||
});
|
||||
}
|
||||
34
packages/site/src/lib/key.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const isCtrl = (e) => e.metaKey || e.ctrlKey;
|
||||
|
||||
// https://keycode.info
|
||||
export const useKeyBindings = (
|
||||
initialKeyBindings = {},
|
||||
eventListener = "keydown"
|
||||
) => {
|
||||
const [keyBindings] = useState(initialKeyBindings);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener(
|
||||
eventListener,
|
||||
(event) => {
|
||||
const { code } = event;
|
||||
const keyBinding = keyBindings[code];
|
||||
if (keyBinding === undefined) return;
|
||||
const condition = keyBinding.ctrl ? isCtrl(event) : true;
|
||||
if (!condition) return;
|
||||
if (event.target.type != "text" || code == "Escape") {
|
||||
event.preventDefault();
|
||||
keyBinding.fn(event);
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
return () =>
|
||||
Object.keys(keyBindings).forEach((keyBinding) =>
|
||||
document.removeEventListener(eventListener, keyBindings[keyBinding])
|
||||
);
|
||||
}, []);
|
||||
};
|
||||
23
packages/site/src/lib/search.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import Fuse from "fuse.js";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function useSearch(icons, query) {
|
||||
const fuse = new Fuse(Object.values(icons), {
|
||||
threshold: 0.2,
|
||||
keys: ["name", "tags"],
|
||||
});
|
||||
|
||||
const [results, setResults] = useState(Object.values(icons));
|
||||
|
||||
useEffect(() => {
|
||||
if (query.trim()) {
|
||||
setResults(fuse.search(query.trim()));
|
||||
} else {
|
||||
setResults(Object.values(icons));
|
||||
}
|
||||
}, [query]);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
export default useSearch;
|
||||
74
packages/site/src/lib/theme.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { theme as chakraTheme } from "@chakra-ui/core";
|
||||
|
||||
const theme = {
|
||||
...chakraTheme,
|
||||
fonts: {
|
||||
...chakraTheme.fonts,
|
||||
body: `Jost,-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"`,
|
||||
},
|
||||
icons: {
|
||||
...chakraTheme.icons,
|
||||
sun: {
|
||||
path: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="5" />
|
||||
<line x1="12" y1="1" x2="12" y2="3" />
|
||||
<line x1="12" y1="21" x2="12" y2="23" />
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
||||
<line x1="1" y1="12" x2="3" y2="12" />
|
||||
<line x1="21" y1="12" x2="23" y2="12" />
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
moon: {
|
||||
path: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
search: {
|
||||
path: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default theme;
|
||||
47
packages/site/src/pages/_app.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { CSSReset, ThemeProvider, ColorModeProvider } from "@chakra-ui/core";
|
||||
import { useRouter } from "next/router";
|
||||
import { QueryParamProvider } from "use-query-params";
|
||||
import customTheme from "../lib/theme";
|
||||
import Head from "next/head";
|
||||
|
||||
const QueryProvider = ({ children }) => {
|
||||
const router = useRouter();
|
||||
|
||||
const history = {
|
||||
push: ({ search }: Location) =>
|
||||
router.push({ search, pathname: router.pathname }),
|
||||
|
||||
replace: ({ search }: Location) =>
|
||||
router.replace({ search, pathname: router.pathname }),
|
||||
};
|
||||
|
||||
const location = {
|
||||
search: router.asPath.replace(/[^?]+/u, ""),
|
||||
} as Location;
|
||||
|
||||
return (
|
||||
<QueryParamProvider history={history} location={location}>
|
||||
{children}
|
||||
</QueryParamProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const App = ({ Component, pageProps }) => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Featherity</title>
|
||||
</Head>
|
||||
<QueryProvider>
|
||||
<ThemeProvider theme={customTheme}>
|
||||
<ColorModeProvider>
|
||||
<CSSReset />
|
||||
<Component {...pageProps} />
|
||||
</ColorModeProvider>
|
||||
</ThemeProvider>
|
||||
</QueryProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
31
packages/site/src/pages/_document.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import Document, { Head, Html, Main, NextScript } from "next/document";
|
||||
|
||||
class MyDocument extends Document {
|
||||
render() {
|
||||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
<link
|
||||
href="https://indestructibletype.com/fonts/Jost.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</Head>
|
||||
<style jsx global>{`
|
||||
* {
|
||||
-webkit-transition: none !important;
|
||||
-moz-transition: none !important;
|
||||
-o-transition: none !important;
|
||||
-ms-transition: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
`}</style>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDocument;
|
||||
143
packages/site/src/pages/index.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
Grid,
|
||||
Icon,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputLeftElement,
|
||||
Stack,
|
||||
Text,
|
||||
useToast,
|
||||
} from "@chakra-ui/core";
|
||||
import copy from "copy-to-clipboard";
|
||||
import download from "downloadjs";
|
||||
import JSZip from "jszip";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { StringParam, useQueryParam } from "use-query-params";
|
||||
import Layout from "../components/Layout";
|
||||
import { getAllData } from "../lib/icons";
|
||||
import useSearch from "../lib/search";
|
||||
|
||||
function generateZip(icons) {
|
||||
const zip = new JSZip();
|
||||
Object.values(icons).forEach((icon) =>
|
||||
// @ts-ignore
|
||||
zip.file(`${icon.name}.svg`, icon.src)
|
||||
);
|
||||
return zip.generateAsync({ type: "blob" });
|
||||
}
|
||||
|
||||
const IndexPage = ({ data }) => {
|
||||
const [query, setQuery] = useQueryParam("query", StringParam);
|
||||
const results = useSearch(data, query || "");
|
||||
const toast = useToast();
|
||||
|
||||
const inputElement = useRef(null);
|
||||
function handleKeyDown(event) {
|
||||
if (event.key === "/" && inputElement.current !== document.activeElement) {
|
||||
event.preventDefault();
|
||||
inputElement.current.focus();
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Flex direction="column" align="center" justify="center">
|
||||
<Text fontSize="3xl" as="b">
|
||||
Simply beautiful open source icons, community-sourced
|
||||
</Text>
|
||||
<Stack isInline marginTop={3} marginBottom={10}>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const zip = await generateZip(data);
|
||||
download(zip, "feather.zip");
|
||||
}}
|
||||
>
|
||||
Download all
|
||||
</Button>
|
||||
</Stack>
|
||||
</Flex>
|
||||
<InputGroup position="sticky" top={2} zIndex={1}>
|
||||
<InputLeftElement children={<Icon name="search" />} />
|
||||
<Input
|
||||
ref={inputElement}
|
||||
placeholder={`Search ${
|
||||
Object.keys(data).length
|
||||
} icons (Press "/" to focus)`}
|
||||
value={query}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
marginBottom={5}
|
||||
/>
|
||||
</InputGroup>
|
||||
{results.length > 0 ? (
|
||||
<Grid
|
||||
templateColumns={`repeat(auto-fill, minmax(160px, 1fr))`}
|
||||
gap={5}
|
||||
>
|
||||
{results.map((icon) => {
|
||||
// @ts-ignore
|
||||
const actualIcon = icon.item ? icon.item : icon;
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
borderWidth="1px"
|
||||
rounded="lg"
|
||||
padding={16}
|
||||
onClick={(event) => {
|
||||
if (event.shiftKey) {
|
||||
copy(actualIcon.src);
|
||||
toast({
|
||||
title: "Copied!",
|
||||
description: `Icon "${actualIcon.name}" copied to clipboard.`,
|
||||
status: "success",
|
||||
duration: 1500,
|
||||
});
|
||||
} else {
|
||||
download(
|
||||
actualIcon.src,
|
||||
`${actualIcon.name}.svg`,
|
||||
"image/svg+xml"
|
||||
);
|
||||
}
|
||||
}}
|
||||
key={actualIcon.name}
|
||||
alignItems="center"
|
||||
>
|
||||
<Flex direction="column" align="center" justify="center">
|
||||
<div dangerouslySetInnerHTML={{ __html: actualIcon.src }} />
|
||||
<Text marginTop={5}>{actualIcon.name}</Text>
|
||||
</Flex>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
) : (
|
||||
<Text
|
||||
fontSize="2xl"
|
||||
fontWeight="bold"
|
||||
textAlign="center"
|
||||
style={{ wordBreak: "break-word" }}
|
||||
>
|
||||
No results found for "{query}"
|
||||
</Text>
|
||||
)}
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export async function getStaticProps() {
|
||||
let data = getAllData();
|
||||
return {
|
||||
props: {
|
||||
data,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default IndexPage;
|
||||
19
packages/site/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
77
rollup.config.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import babel from '@rollup/plugin-babel';
|
||||
import bundleSize from '@atomico/rollup-plugin-sizes';
|
||||
import compiler from '@ampproject/rollup-plugin-closure-compiler';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
import visualizer from 'rollup-plugin-visualizer';
|
||||
import license from 'rollup-plugin-license';
|
||||
import replace from 'rollup-plugin-replace';
|
||||
import resolve from 'rollup-plugin-node-resolve';
|
||||
import commonJS from 'rollup-plugin-commonjs';
|
||||
import pkg from './package.json';
|
||||
|
||||
const outputFileName = pkg.name;
|
||||
|
||||
const inputs = ['build/lucide.js'];
|
||||
const bundles = [
|
||||
{
|
||||
inputs,
|
||||
format: 'umd',
|
||||
dir: 'dist',
|
||||
minify: true,
|
||||
},
|
||||
{
|
||||
inputs,
|
||||
format: 'umd',
|
||||
dir: 'dist',
|
||||
},
|
||||
{
|
||||
inputs,
|
||||
format: 'cjs',
|
||||
dir: 'dist',
|
||||
},
|
||||
];
|
||||
|
||||
const configs = bundles
|
||||
.map(({ inputs, dir, format, minify }) =>
|
||||
inputs.map(input => ({
|
||||
input,
|
||||
external: ['lodash/camelCase', 'lodash/upperFirst'],
|
||||
plugins: [
|
||||
replace({
|
||||
'icons = {}': 'icons = allIcons',
|
||||
delimiters: ['', ''],
|
||||
}),
|
||||
babel({
|
||||
babelHelpers: 'bundled',
|
||||
}),
|
||||
// The two minifiers together seem to procude a smaller bundle 🤷♂️
|
||||
minify && compiler(),
|
||||
minify && terser(),
|
||||
license({
|
||||
banner: `${pkg.name} v${pkg.version} - ${pkg.license}`,
|
||||
}),
|
||||
bundleSize(),
|
||||
resolve(),
|
||||
commonJS({
|
||||
include: 'node_modules/**',
|
||||
}),
|
||||
visualizer({
|
||||
sourcemap: true,
|
||||
filename: `stats/${outputFileName}${minify ? '-min' : ''}.html`,
|
||||
}),
|
||||
].filter(Boolean),
|
||||
output: {
|
||||
name: 'lucide',
|
||||
file: `${dir}/${format}/${outputFileName}${minify ? '.min' : ''}.js`,
|
||||
format,
|
||||
sourcemap: true,
|
||||
globals: {
|
||||
'lodash/camelCase': 'camelCase',
|
||||
'lodash/upperFirst': 'upperFirst',
|
||||
},
|
||||
},
|
||||
})),
|
||||
)
|
||||
.flat();
|
||||
|
||||
export default configs;
|
||||
23
scripts/build/generateExportsFile.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import path from 'path';
|
||||
|
||||
import { generateComponentName, resetFile, appendFile } from '../helpers';
|
||||
|
||||
export default function(inputEntry, outputDirectory, componentGetter, iconNodes) {
|
||||
const fileName = path.basename(inputEntry);
|
||||
|
||||
// Reset file
|
||||
resetFile(fileName, outputDirectory);
|
||||
|
||||
const icons = Object.keys(iconNodes);
|
||||
|
||||
// Generate Import for Icon VNodes
|
||||
icons.forEach(iconName => {
|
||||
const componentName = generateComponentName(iconName);
|
||||
const importString = `export { default as ${componentName} } from './${iconName}';\n`;
|
||||
appendFile(importString, fileName, outputDirectory);
|
||||
});
|
||||
|
||||
appendFile('\n', fileName, outputDirectory);
|
||||
|
||||
console.log(`Successfully generated ${fileName} file`);
|
||||
}
|
||||
27
scripts/build/generateIconFiles.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import prettier from 'prettier';
|
||||
import { generateComponentName } from '../helpers';
|
||||
|
||||
export default function(iconNode, outputDirectory, template) {
|
||||
const icons = Object.keys(iconNode);
|
||||
const iconsDistDirectory = path.join(outputDirectory, `icons`);
|
||||
|
||||
if (!fs.existsSync(iconsDistDirectory)) {
|
||||
fs.mkdirSync(iconsDistDirectory);
|
||||
}
|
||||
|
||||
icons.forEach(icon => {
|
||||
const location = path.join(iconsDistDirectory, `${icon}.js`);
|
||||
const componentName = generateComponentName(icon);
|
||||
|
||||
const node = JSON.stringify(iconNode[icon]);
|
||||
|
||||
const elementTemplate = template({ componentName, node });
|
||||
|
||||
fs.writeFileSync(location, prettier.format(elementTemplate, { parser: 'babel' }), 'utf-8');
|
||||
|
||||
console.log('Successfully built', componentName);
|
||||
});
|
||||
}
|
||||
42
scripts/buildIcons.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import renderIconsObject from './render/renderIconsObject';
|
||||
import renderIconNodes from './render/renderIconNodes';
|
||||
import generateIconFiles from './build/generateIconFiles';
|
||||
import generateExportsFile from './build/generateExportsFile';
|
||||
|
||||
import { readSvgDirectory } from './helpers';
|
||||
|
||||
const ICONS_DIR = path.resolve(__dirname, '../icons');
|
||||
const OUTPUT_DIR = path.resolve(__dirname, '../build');
|
||||
const SRC_DIR = path.resolve(__dirname, '../src');
|
||||
|
||||
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||
fs.mkdirSync(OUTPUT_DIR);
|
||||
}
|
||||
|
||||
const svgFiles = readSvgDirectory(ICONS_DIR);
|
||||
|
||||
const icons = renderIconsObject(svgFiles, ICONS_DIR);
|
||||
|
||||
const iconVNodes = renderIconNodes(icons);
|
||||
|
||||
// Generates iconsNodes files for each icon
|
||||
generateIconFiles(
|
||||
iconVNodes,
|
||||
OUTPUT_DIR,
|
||||
({ componentName, node }) => `
|
||||
const ${componentName} = ${node};
|
||||
|
||||
export default ${componentName};
|
||||
`,
|
||||
);
|
||||
|
||||
// Generates entry files for the compiler filled with icons exports
|
||||
generateExportsFile(
|
||||
path.join(SRC_DIR, 'icons/index.js'),
|
||||
path.join(OUTPUT_DIR, 'icons'),
|
||||
'getElement',
|
||||
iconVNodes,
|
||||
);
|
||||
74
scripts/helpers.js
Normal file
@@ -0,0 +1,74 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { upperFirst, camelCase } from 'lodash/string';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Generates a componentName of a String.
|
||||
*
|
||||
* @param {string} iconName
|
||||
*/
|
||||
export const generateComponentName = iconName =>
|
||||
iconName === 'github' ? 'GitHub' : upperFirst(camelCase(iconName));
|
||||
|
||||
/**
|
||||
* Resets the file contents.
|
||||
*
|
||||
* @param {string} fileName
|
||||
* @param {string} outputDirectory
|
||||
*/
|
||||
export const resetFile = (fileName, outputDirectory) =>
|
||||
fs.writeFileSync(path.join(outputDirectory, fileName), '', 'utf-8');
|
||||
|
||||
/**
|
||||
* Reads the file contents.
|
||||
*
|
||||
* @param {string} path
|
||||
*/
|
||||
export const readFile = entry => fs.readFileSync(path.resolve(__dirname, '../', entry), 'utf-8');
|
||||
|
||||
/**
|
||||
* append content to a file
|
||||
*
|
||||
* @param {string} content
|
||||
* @param {string} fileName
|
||||
* @param {string} outputDirectory
|
||||
*/
|
||||
export const appendFile = (content, fileName, outputDirectory) =>
|
||||
fs.appendFileSync(path.join(outputDirectory, fileName), content, 'utf-8');
|
||||
|
||||
/**
|
||||
* writes content to a file
|
||||
*
|
||||
* @param {string} content
|
||||
* @param {string} fileName
|
||||
* @param {string} outputDirectory
|
||||
*/
|
||||
export const writeFile = (content, fileName, outputDirectory) =>
|
||||
fs.writeFileSync(path.join(outputDirectory, fileName), content, 'utf-8');
|
||||
|
||||
/**
|
||||
* reads the icon directory
|
||||
*
|
||||
* @param {string} directory
|
||||
*/
|
||||
export const readSvgDirectory = directory =>
|
||||
fs.readdirSync(directory).filter(file => path.extname(file) === '.svg');
|
||||
|
||||
/**
|
||||
* Read svg from directory
|
||||
*
|
||||
* @param {string} fileName
|
||||
* @param {string} directory
|
||||
*/
|
||||
export const readSvg = (fileName, directory) => fs.readFileSync(path.join(directory, fileName));
|
||||
|
||||
/**
|
||||
* writes content to a file
|
||||
*
|
||||
* @param {string} fileName
|
||||
* @param {string} outputDirectory
|
||||
* @param {string} content
|
||||
*/
|
||||
export const writeSvgFile = (fileName, outputDirectory, content) =>
|
||||
fs.appendFileSync(path.join(outputDirectory, fileName), content, 'utf-8');
|
||||
15
scripts/optimizeSvgs.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import processSvg from './render/processSvg';
|
||||
import { readSvgDirectory, writeSvgFile } from './helpers';
|
||||
|
||||
const ICONS_DIR = path.resolve(__dirname, '../icons');
|
||||
|
||||
console.log(`Optimizing SVGs...`);
|
||||
|
||||
const svgFiles = readSvgDirectory(ICONS_DIR);
|
||||
|
||||
svgFiles.forEach(svgFile => {
|
||||
const content = fs.readFileSync(path.join(ICONS_DIR, svgFile));
|
||||
processSvg(content).then(svg => writeSvgFile(svg, ICONS_DIR, content));
|
||||
});
|
||||
@@ -1,8 +1,9 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import Svgo from 'svgo';
|
||||
import cheerio from 'cheerio';
|
||||
import { format } from 'prettier';
|
||||
|
||||
import DEFAULT_ATTRS from '../src/default-attrs.json';
|
||||
import DEFAULT_ATTRS from './default-attrs.json';
|
||||
|
||||
/**
|
||||
* Process SVG string.
|
||||
@@ -48,9 +49,7 @@ function optimize(svg) {
|
||||
function setAttrs(svg) {
|
||||
const $ = cheerio.load(svg);
|
||||
|
||||
Object.keys(DEFAULT_ATTRS).forEach(key =>
|
||||
$('svg').attr(key, DEFAULT_ATTRS[key]),
|
||||
);
|
||||
Object.keys(DEFAULT_ATTRS).forEach(key => $('svg').attr(key, DEFAULT_ATTRS[key]));
|
||||
|
||||
return $('body').html();
|
||||
}
|
||||
29
scripts/render/renderIconNodes.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { parseDOM } from 'htmlparser2';
|
||||
import DEFAULT_ATTRS from './default-attrs.json';
|
||||
|
||||
export default iconsObject => {
|
||||
const iconNodes = {};
|
||||
|
||||
Object.keys(iconsObject).forEach(icon => {
|
||||
const svgString = iconsObject[icon];
|
||||
const dom = parseDOM(svgString);
|
||||
|
||||
const children = dom.map(element => [
|
||||
element.name,
|
||||
{
|
||||
...element.attribs,
|
||||
},
|
||||
]);
|
||||
|
||||
iconNodes[icon] = [
|
||||
'svg',
|
||||
{
|
||||
...DEFAULT_ATTRS,
|
||||
},
|
||||
children,
|
||||
];
|
||||
});
|
||||
|
||||
return iconNodes;
|
||||
};
|
||||
@@ -1,26 +1,8 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import path from 'path';
|
||||
import cheerio from 'cheerio';
|
||||
import { minify } from 'html-minifier';
|
||||
|
||||
/**
|
||||
* Build an object in the format: `{ <name>: <contents> }`.
|
||||
* @param {string[]} svgFiles - A list of filenames.
|
||||
* @param {Function} getSvg - A function that returns the contents of an SVG file given a filename.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function buildIconsObject(svgFiles, getSvg) {
|
||||
return svgFiles
|
||||
.map(svgFile => {
|
||||
const name = path.basename(svgFile, '.svg');
|
||||
const svg = getSvg(svgFile);
|
||||
const contents = getSvgContents(svg);
|
||||
return { name, contents };
|
||||
})
|
||||
.reduce((icons, icon) => {
|
||||
icons[icon.name] = icon.contents;
|
||||
return icons;
|
||||
}, {});
|
||||
}
|
||||
import { readSvg } from '../helpers';
|
||||
|
||||
/**
|
||||
* Get contents between opening and closing `<svg>` tags.
|
||||
@@ -29,7 +11,25 @@ function buildIconsObject(svgFiles, getSvg) {
|
||||
*/
|
||||
function getSvgContents(svg) {
|
||||
const $ = cheerio.load(svg);
|
||||
|
||||
return minify($('svg').html(), { collapseWhitespace: true });
|
||||
}
|
||||
|
||||
export default buildIconsObject;
|
||||
/**
|
||||
* Build an object in the format: `{ <name>: <contents> }`.
|
||||
* @param {string[]} svgFiles - A list of filenames.
|
||||
* @param {Function} getSvg - A function that returns the contents of an SVG file given a filename.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export default (svgFiles, iconsDirectory) =>
|
||||
svgFiles
|
||||
.map(svgFile => {
|
||||
const name = path.basename(svgFile, '.svg');
|
||||
const svg = readSvg(svgFile, iconsDirectory);
|
||||
const contents = getSvgContents(svg);
|
||||
return { name, contents };
|
||||
})
|
||||
.reduce((icons, icon) => {
|
||||
icons[icon.name] = icon.contents;
|
||||
return icons;
|
||||
}, {});
|
||||
19
src/createElement.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const createElement = (tag, attrs, children = []) => {
|
||||
const element = document.createElementNS('http://www.w3.org/2000/svg', tag);
|
||||
|
||||
Object.keys(attrs).forEach(name => {
|
||||
element.setAttribute(name, attrs[name]);
|
||||
});
|
||||
|
||||
if (children.length) {
|
||||
children = children.forEach(child => {
|
||||
const childElement = createElement(...child);
|
||||
|
||||
element.appendChild(childElement);
|
||||
});
|
||||
}
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
export default ([tag, attrs, children]) => createElement(tag, attrs, children);
|
||||
5
src/icons/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
Icons exports.
|
||||
|
||||
Will be generated
|
||||
*/
|
||||
34
src/lucide.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import replaceElement from './replaceElement';
|
||||
import * as allIcons from './icons/index';
|
||||
|
||||
/*
|
||||
Create icons
|
||||
*/
|
||||
export const createIcons = ({ icons = {}, nameAttr = 'icon-name', attrs = {} } = {}) => {
|
||||
if (!Object.values(icons).length) {
|
||||
throw new Error(
|
||||
"Please provide an icons object.\nIf you want to use all the icons you can import it like:\n `import { createIcons, icons } from 'lucide';\nlucide.createIcons({icons});`",
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof document === 'undefined') {
|
||||
throw new Error('`createIcons()` only works in a browser environment.');
|
||||
}
|
||||
|
||||
const elementsToReplace = document.querySelectorAll(`[${nameAttr}]`);
|
||||
|
||||
Array.from(elementsToReplace).forEach(element =>
|
||||
replaceElement(element, { nameAttr, icons, attrs }),
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
Create Element function export.
|
||||
*/
|
||||
export { default as createElement } from './createElement';
|
||||
|
||||
/*
|
||||
Icons exports.
|
||||
*/
|
||||
export { allIcons as icons };
|
||||
export * from './icons/index';
|
||||
83
src/replaceElement.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import { camelCase, upperFirst } from 'lodash-es';
|
||||
import createElement from './createElement';
|
||||
|
||||
/**
|
||||
* Get the attributes of an HTML element.
|
||||
* @param {HTMLElement} element
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const getAttrs = element =>
|
||||
Array.from(element.attributes).reduce((attrs, attr) => {
|
||||
attrs[attr.name] = attr.value;
|
||||
return attrs;
|
||||
}, {});
|
||||
|
||||
/**
|
||||
* Gets the classNames of an attributes Object.
|
||||
* @param {Object} attrs
|
||||
* @returns {Array}
|
||||
*/
|
||||
export const getClassNames = attrs => {
|
||||
if (!attrs || !attrs.class) return '';
|
||||
if (attrs.class && typeof attrs.class === 'string') {
|
||||
return attrs.class.split(' ');
|
||||
}
|
||||
if (attrs.class && Array.isArray(attrs.class)) {
|
||||
return attrs.class;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Combines the classNames of array of classNames to a String
|
||||
* @param {Array} arrayOfClassnames
|
||||
* @returns {String}
|
||||
*/
|
||||
export const combineClassNames = arrayOfClassnames => {
|
||||
const classNameArray = arrayOfClassnames.flatMap(getClassNames);
|
||||
|
||||
return classNameArray
|
||||
.map(classItem => classItem.trim())
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
};
|
||||
|
||||
/**
|
||||
* ReplaceElement, replaces the given element with the created icon.
|
||||
* @param {HTMLElement} element
|
||||
* @param {Object: {String, Array, Object}} options: { nameAttr, icons, attrs }
|
||||
* @returns {Function}
|
||||
*/
|
||||
export default (element, { nameAttr, icons, attrs }) => {
|
||||
const iconName = element.getAttribute(nameAttr);
|
||||
const ComponentName = upperFirst(camelCase(iconName));
|
||||
|
||||
const iconNode = icons[ComponentName];
|
||||
|
||||
if (!iconNode) {
|
||||
return console.warn(
|
||||
`${element.outerHTML} icon name was not found in the provided icons object.`,
|
||||
);
|
||||
}
|
||||
|
||||
const elementAttrs = getAttrs(element);
|
||||
|
||||
const [, iconAttrs] = iconNode;
|
||||
|
||||
const allAttrs = {
|
||||
...iconAttrs,
|
||||
...attrs,
|
||||
};
|
||||
|
||||
iconNode[1] = { ...allAttrs };
|
||||
|
||||
const classNames = combineClassNames([iconAttrs, elementAttrs, attrs]);
|
||||
|
||||
if (classNames) {
|
||||
iconNode[1].class = classNames;
|
||||
}
|
||||
|
||||
const svgElement = createElement(iconNode);
|
||||
|
||||
return element.parentNode.replaceChild(svgElement, element);
|
||||
};
|
||||
3
tests/__snapshots__/lucide.spec.js.snap
Normal file
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`createIcons should read elements from DOM and replace it with icons 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\"><polygon points=\\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\\"></polygon><path d=\\"M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07\\"></path></svg>"`;
|
||||
3
tests/__snapshots__/replaceElement.spec.js.snap
Normal file
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`combineClassNames should retuns a string of classNames 1`] = `"item1 item2 item3 item4 item5 item6 item7 item8 item9"`;
|
||||