Lucide 0.15.0 (#272)

* add configs

* Add vue components

* Add documentation

* add alpha release version

* improve npm ignore files

* add tests

* Make style and class attrs work

* 📦 bump version

* Add Icon suffix for component names

* bump version

* Add icon component example

* remove space

* add new build strategy

* Write a better intro

* add other node design

* fix

* add new default template

* add tempalte

* improve code

* small improvements

* small improvements

* move files

* Connect lucide with lucide-react

* Add support for vue

* Add licenses to packages

* Fix tests

* refactor build scripts

* Minor code fixes

* update homepage readme

* Update footer text

* Add a better introduction to packages

* Split up in tempaltes

* Add new types build file

* Setup workflow file

* update readme

* update

* Fix build

* remove debug code

* Add check if svgs have duplicated children

* Add check if their are no children

* small fixes

* last fixes in the build

* Move script to packages folder

* Fix tests and add types for lucide

* Add rule to package.json

* add types in build

* add npm ignore

* update package.jsons
This commit is contained in:
Eric Fennis
2021-03-23 19:26:50 +01:00
committed by GitHub
parent 9d101a5275
commit b4e4f002f2
81 changed files with 5804 additions and 1655 deletions

View File

@@ -20,13 +20,9 @@ jobs:
- name: Install FontForge - name: Install FontForge
run: sudo apt-get install zlib1g-dev fontforge run: sudo apt-get install zlib1g-dev fontforge
- name: Install NodeJS and Yarn
run: sudo apt-get install nodejs yarn
- name: Clone sfnt2woff-zopfli repo - name: Clone sfnt2woff-zopfli repo
run: git clone https://github.com/bramstein/sfnt2woff-zopfli.git sfnt2woff-zopfli run: git clone https://github.com/bramstein/sfnt2woff-zopfli.git sfnt2woff-zopfli
- name: Install and move sfnt2woff-zopfli - name: Install and move sfnt2woff-zopfli
run: | run: |
cd sfnt2woff-zopfli cd sfnt2woff-zopfli
@@ -36,7 +32,6 @@ jobs:
- name: Clone woff2 - name: Clone woff2
run: git clone --recursive https://github.com/google/woff2.git run: git clone --recursive https://github.com/google/woff2.git
- name: Install woff2 - name: Install woff2
run: | run: |
cd woff2 cd woff2
@@ -47,16 +42,14 @@ jobs:
run: sudo gem install fontcustom run: sudo gem install fontcustom
- name: Install "outline-stroke" - name: Install "outline-stroke"
run: sudo yarn add svg-outline-stroke svgson run: sudo yarn add svg-outline-stroke -W
- name: "Outline SVG" - name: "Outline SVG"
run: mkdir converted_icons && node scripts/outline_svg.js run: mkdir converted_icons && node scripts/outline_svg.js
- name: Build 'Lucide' - name: Build 'Lucide'
run: echo "Building Lucide font" && fontcustom compile ./converted_icons -h -n Lucide -o build -F run: echo "Building Lucide font" && fontcustom compile ./converted_icons -h -n Lucide -o build -F
- name: Zip 'Lucide' - name: Zip 'Lucide'
run: zip -r Lucide.zip build run: zip -r Lucide.zip build

View File

@@ -1,4 +1,4 @@
name: Release to NPM name: Release Packages
on: on:
push: push:
@@ -6,94 +6,150 @@ on:
- 'v*' - 'v*'
jobs: jobs:
build-and-deploy: pre-build:
if: github.repository == 'lucide-icons/lucide' if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs:
env: VERSION: ${{ steps.get_version.outputs.VERSION }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} YARN_CACHE_DIR: ${{ steps.get_version.outputs.YARN_CACHE_DIR }}
steps: steps:
- name: Get the version - name: Get the version
id: get_version id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/\v} run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/\v}
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=YARN_CACHE_DIR::$(yarn cache dir)"
lucide:
if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest
needs: pre-build
steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v2
with: with:
clean: true node-version: '12.x'
- name: Set Auth Token - uses: actions/cache@v2
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }} id: yarn-cache
with:
path: ${{ needs.pre-build.outputs.YARN_CACHE_DIR }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
# Build lucide - name: Install dependencies
- name: Install Dependencies Lucide if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn --pure-lockfile run: yarn --pure-lockfile
- name: Build lucide package - name: Set new version
run: yarn build run: yarn workspace lucide version --new-version ${{ needs.pre-build.outputs.VERSION }} --no-git-tag-version
- name: Test lucide package - name: Build
run: yarn test run: yarn workspace lucide build
# Build lucide-react - name: Test
- name: Install Dependencies lucide-react run: yarn workspace lucide test
run: yarn --pure-lockfile
working-directory: packages/lucide-react
- name: Build lucide-react - name: Publish
run: yarn build run: yarn workspace lucide publish
working-directory: packages/lucide-react env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Test lucide-react
run: yarn test
working-directory: packages/lucide-react
# Build lucide-vue
- name: Install Dependencies lucide-vue
run: yarn --pure-lockfile
working-directory: packages/lucide-vue
- name: Build lucide-vue
run: yarn build
working-directory: packages/lucide-vue
- name: Test lucide-vue
run: yarn test
working-directory: packages/lucide-vue
# Publish lucide
- name: Set package.json version lucide
run: yarn version --new-version ${{ steps.get_version.outputs.VERSION }} --no-git-tag-version
- name: publish lucide
run: yarn publish
# Publish lucide-react
- name: Set package.json version lucide-react
run: yarn version --new-version ${{ steps.get_version.outputs.VERSION }} --no-git-tag-version
working-directory: packages/lucide-react
- name: publish lucide-react
run: yarn publish
working-directory: packages/lucide-react
# Publish lucide-vue
- name: Set package.json version lucide-vue
run: yarn version --new-version ${{ steps.get_version.outputs.VERSION }} --no-git-tag-version
working-directory: packages/lucide-vue
- name: publish lucide-vue
run: yarn publish
working-directory: packages/lucide-vue
- name: Commit package.json - name: Commit package.json
run: | run: |
git add package.json git add packages/lucide/package.json
git add packages/lucide-react/package.json
git add packages/lucide-vue/package.json
git -c user.name="Lucide Bot" -c user.email="lucide-bot@users.noreply.github.com" \ git -c user.name="Lucide Bot" -c user.email="lucide-bot@users.noreply.github.com" \
commit -m ":package: Bump version to ${{ steps.get_version.outputs.VERSION }}" --no-verify --quiet commit -m ":package: Bump version lucide to ${{ needs.pre-build.outputs.VERSION }}" --no-verify --quiet
git remote set-url --push origin https://lucide-bot:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY.git git remote set-url --push origin https://lucide-bot:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY.git
git push origin HEAD:master git push origin HEAD:master
lucide-react:
if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest
needs: pre-build
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '12.x'
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ needs.pre-build.outputs.YARN_CACHE_DIR }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn --pure-lockfile
- name: Set package.json version lucide
run: yarn workspace lucide-react version --new-version ${{ needs.pre-build.outputs.VERSION }} --no-git-tag-version
- name: Build
run: yarn workspace lucide-react build
- name: Test
run: yarn workspace lucide-react test
- name: Publish
run: yarn workspace lucide-react publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Commit package.json
run: |
git add packages/lucide-react/package.json
git -c user.name="Lucide Bot" -c user.email="lucide-bot@users.noreply.github.com" \
commit -m ":package: Bump version lucide-react to ${{ needs.pre-build.outputs.VERSION }}" --no-verify --quiet
git remote set-url --push origin https://lucide-bot:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY.git
git push origin HEAD:master
lucide-vue:
if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest
needs: pre-build
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '12.x'
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ needs.pre-build.outputs.YARN_CACHE_DIR }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn --pure-lockfile
- name: Set new version
run: yarn workspace lucide-vue version --new-version ${{ needs.pre-build.outputs.VERSION }} --no-git-tag-version
- name: Build
run: yarn workspace lucide-vue build
- name: Test
run: yarn workspace lucide-vue test
- name: Publish
run: yarn workspace lucide-vue publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Commit package.json
run: |
git add packages/lucide-vue/package.json
git -c user.name="Lucide Bot" -c user.email="lucide-bot@users.noreply.github.com" \
commit -m ":package: Bump version lucide-vue to ${{ needs.pre-build.outputs.VERSION }}" --no-verify --quiet
git remote set-url --push origin https://lucide-bot:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY.git
git push origin HEAD:master

3
.gitignore vendored
View File

@@ -4,10 +4,9 @@
node_modules node_modules
dist dist
build build
/lib
sandbox sandbox
stash stash
coverage coverage
stats stats
*.log *.log
index.d.ts packages/**/src/icons/*.js

166
README.md
View File

@@ -9,6 +9,15 @@
## What is Lucide? ## What is Lucide?
Lucide is a community-run fork of [Feather Icons](https://github.com/feathericons/feather), open for anyone to contribute icons. Lucide is a community-run fork of [Feather Icons](https://github.com/feathericons/feather), open for anyone to contribute icons.
Started after growing disaffection of the moderation of the [Feather Icons](https://github.com/feathericons/feather) project, with over 300+ open issues and over 100+ open PRs, this project is no longer maintained. The owner of the project stopped merging icons and want to keep the project like it now is. Hundreds of developers/designer wasted their time trying to contribute, what a shame.
We're trying to expanding the icon set as much as possible while keeping it nice-looking, we do it as a community of devs and designers, join us!
### Why choose Lucide over Feather Icons
- Lucide already expended the icon set by 130+ in less then a year, so more icons to work with.
- Well maintained code base.
- Active community.
## Table of Contents ## Table of Contents
@@ -16,38 +25,14 @@ Lucide is a community-run fork of [Feather Icons](https://github.com/feathericon
* [Package managers](#package-managers) * [Package managers](#package-managers)
* [CDN](#cdn) * [CDN](#cdn)
* [Usage](#usage) * [Usage](#usage)
* [Unpkg](#with-unpkg) * [Web](#web)
* [ESModules](#with-esmodules) * [React](#react)
* [Options](#additional-options) * [Vue](#vue)
* [Treeshake library](#treeshake-the-library-only-use-the-icons-you-use)
* [Custom binding](#custom-element-binding)
* [React](#with-react)
* [Vue](#with-vue)
* [Figma](#figma) * [Figma](#figma)
* [Contributing](#contributing) * [Contributing](#contributing)
* [Community](#community) * [Community](#community)
* [License](#license) * [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 ## Usage
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) 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)
@@ -55,104 +40,23 @@ At its core, Lucide is a collection of [SVG](https://svgontheweb.com/#svg) files
The following are additional ways you can use Lucide. 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 the Javascript library you can easily incorporate the icon you want in your webpage.
### With unpkg ### Web
Here is a complete example with unpkg Implementation of the lucide icon library for web applications.
```html ```sh
<!DOCTYPE html> npm install lucide
<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> #or
<script>
lucide.createIcons(); yarn add lucide
</script>
</body>
``` ```
### With ESModules For more details, see the [documentation](https://github.com/lucide-icons/lucide/blob/master/packages/lucide-react/README.md).
To reduce bundle size, lucide is built to be fully treeshakable. ### React
The `createIcons` function will search for HTMLElements with the attribute `icon-name` and replace it with the svg from the given icon name.
```html Implementation of the lucide icon library for react applications.
<!-- Your HTML file -->
<i icon-name="menu"></i>
```
```js
import { createIcons, icons } from 'lucide';
// Caution, 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', // attribute for the icon name.
});
```
#### Treeshake 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);
```
### With React
You can also use the Lucide library using the react package.
```sh ```sh
yarn add lucide-react yarn add lucide-react
@@ -162,11 +66,11 @@ yarn add lucide-react
npm install lucide-react npm install lucide-react
``` ```
For more details, see the [documentation](https://github.com/lucide-icons/lucide/blob/master/packages/lucide-react/README.md). For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/master/packages/lucide-react#lucide-react).
### With Vue ### Vue
You can also use the Lucide library using the Vue package. Implementation of the lucide icon library for vue applications.
```sh ```sh
yarn add lucide-vue yarn add lucide-vue
@@ -176,11 +80,15 @@ yarn add lucide-vue
npm install lucide-vue npm install lucide-vue
``` ```
For more details, see the [documentation](https://github.com/lucide-icons/lucide/blob/master/packages/lucide-vue/README.md). For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/master/packages/lucide-vue#lucide-vue).
### Figma ### Figma
You can use the components from [this Figma file](https://www.figma.com/file/g0UipfQlRfGrntKPxZknM7/Featherity). The lucide figma plugin.
Visit [Figma community page](https://www.figma.com/community/plugin/939567362549682242/Lucide-Icons) to install the plugin.
<img width="420" src="https://www.figma.com/community/plugin/939567362549682242/thumbnail" alt="Figma Lucide Cover">
## Contributing ## Contributing
@@ -194,10 +102,10 @@ Join the community on our [Discord](https://discord.gg/EH6nSts) server!
## License ## License
Lucide is licensed under the [ISC License](https://github.com/lucide-icons/lucide/blob/master/LICENSE). Lucide is totally free for commercial use and personally use, this software is licensed under the [ISC License](https://github.com/lucide-icons/lucide/blob/master/LICENSE).
<p align="center"> ## Sponsors
<a href="https://vercel.com?utm_source=lucide&utm_campaign=oss">
<img src="./site/public/vercel.svg" alt="Powered by Vercel" width="200" /> <a href="https://vercel.com?utm_source=lucide&utm_campaign=oss">
</a> <img src="./site/public/vercel.svg" alt="Powered by Vercel" width="200" />
</p> </a>

View File

@@ -1,13 +1,5 @@
module.exports = { module.exports = {
presets: [ presets: ['@babel/env'],
[
'@babel/env',
{
loose: true,
modules: false,
},
],
],
env: { env: {
test: { test: {
presets: ['@babel/env'], presets: ['@babel/env'],

View File

@@ -1 +1,15 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="2" y="6" width="20" height="12" rx="2"/><circle cx="12" cy="12" r="2"/><path d="M6 12h.01M18 12h.01"/></svg> <svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="2" y="6" width="20" height="12" rx="2" />
<circle cx="12" cy="12" r="2" />
<path d="M6 12h.01M18 12h.01" />
</svg>

Before

Width:  |  Height:  |  Size: 252 B

After

Width:  |  Height:  |  Size: 331 B

View File

@@ -1,8 +1,8 @@
<svg <svg
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
stroke-width="2" stroke-width="2"

Before

Width:  |  Height:  |  Size: 455 B

After

Width:  |  Height:  |  Size: 455 B

View File

@@ -1,8 +1,8 @@
<svg <svg
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
stroke-width="2" stroke-width="2"

Before

Width:  |  Height:  |  Size: 347 B

After

Width:  |  Height:  |  Size: 347 B

View File

@@ -11,6 +11,6 @@
> >
<path d="M4 22h14a2 2 0 002-2V7.5L14.5 2H6a2 2 0 00-2 2v4" /> <path d="M4 22h14a2 2 0 002-2V7.5L14.5 2H6a2 2 0 00-2 2v4" />
<path d="M14 2v6h6" /> <path d="M14 2v6h6" />
<path d="M2 15l10-0" /> <path d="M2 15h10" />
<path d="M9 18l3-3-3-3" /> <path d="M9 18l3-3-3-3" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 352 B

After

Width:  |  Height:  |  Size: 350 B

View File

@@ -11,6 +11,6 @@
> >
<path d="M4 22h14a2 2 0 002-2V7.5L14.5 2H6a2 2 0 00-2 2v4" /> <path d="M4 22h14a2 2 0 002-2V7.5L14.5 2H6a2 2 0 00-2 2v4" />
<path d="M14 2v6h6" /> <path d="M14 2v6h6" />
<path d="M2 15l10-0" /> <path d="M2 15h10" />
<path d="M5 12l-3 3 3 3" /> <path d="M5 12l-3 3 3 3" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 353 B

After

Width:  |  Height:  |  Size: 351 B

View File

@@ -1,8 +1,8 @@
<svg <svg
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
stroke-width="2" stroke-width="2"

Before

Width:  |  Height:  |  Size: 347 B

After

Width:  |  Height:  |  Size: 347 B

View File

@@ -1,8 +1,8 @@
<svg <svg
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
stroke-width="2" stroke-width="2"

Before

Width:  |  Height:  |  Size: 304 B

After

Width:  |  Height:  |  Size: 304 B

View File

@@ -1,8 +1,8 @@
<svg <svg
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
stroke-width="2" stroke-width="2"

Before

Width:  |  Height:  |  Size: 321 B

After

Width:  |  Height:  |  Size: 321 B

View File

@@ -1,8 +1,8 @@
<svg <svg
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
stroke-width="2" stroke-width="2"

Before

Width:  |  Height:  |  Size: 341 B

After

Width:  |  Height:  |  Size: 341 B

View File

@@ -1,8 +1,8 @@
<svg <svg
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
stroke-width="2" stroke-width="2"

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 282 B

View File

@@ -1,5 +0,0 @@
[build]
base = "site/"
publish = "build/"
command = "yarn deploy"
ignore = "git diff --quiet origin/master HEAD ../icons ../site"

View File

@@ -1,67 +1,50 @@
{ {
"name": "lucide", "private": true,
"description": "Lucide is a community-run fork of Feather Icons, open for anyone to contribute icons.", "workspaces": [
"version": "0.14.0", "packages/*",
"license": "ISC", "site"
"homepage": "https://lucide.dev", ],
"bugs": "https://github.com/lucide-icons/lucide/issues",
"repository": {
"type": "git",
"url": "https://github.com/lucide-icons/lucide.git"
},
"amdName": "lucide",
"source": "build/lucide.js",
"main": "dist/cjs/lucide.js",
"main:umd": "dist/umd/lucide.js",
"module": "dist/esm/lucide.js",
"unpkg": "dist/umd/lucide.min.js",
"sideEffects": false,
"scripts": { "scripts": {
"start": "babel-watch --watch src", "build": "yarn lucide build && yarn lucide-react build && yarn lucide-vue build",
"clean": "rimraf dist && rimraf build && rimraf index.d.ts", "test": "yarn lucide build:icons && yarn lucide-react build:icons && yarn lucide-vue build:icons && jest",
"build": "yarn clean && yarn build:move && yarn build:icons && yarn build:dts && yarn build:es && yarn build:bundles", "lucide": "yarn workspace lucide",
"build:move": "cp -av src build", "lucide-react": "yarn workspace lucide-react",
"build:icons": "npx babel-node ./scripts/buildIcons.js --presets @babel/env", "lucide-vue": "yarn workspace lucide-vue",
"build:dts": "npx babel-node ./scripts/buildDts.js --presets @babel/env", "build:icons": "babel-node ./scripts/buildIcons.js --presets @babel/env",
"build:es": "babel build -d dist/esm", "optimize": "babel-node ./scripts/optimizeSvgs.js --presets @babel/env",
"build:bundles": "rollup -c rollup.config.js", "addtags": "babel-node ./scripts/addMissingKeysToTags.js --presets @babel/env"
"optimize": "npx babel-node ./scripts/optimizeSvgs.js --presets @babel/env",
"addtags": "npx babel-node ./scripts/addMissingKeysToTags.js --presets @babel/env",
"test": "jest"
}, },
"devDependencies": { "devDependencies": {
"@ampproject/rollup-plugin-closure-compiler": "^0.25.2", "@ampproject/rollup-plugin-closure-compiler": "^0.25.2",
"@atomico/rollup-plugin-sizes": "^1.1.4", "@atomico/rollup-plugin-sizes": "^1.1.4",
"@babel/cli": "^7.10.5", "@babel/cli": "^7.10.5",
"@babel/core": "^7.11.1", "@babel/core": "^7.11.1",
"@babel/node": "^7.10.5", "@babel/node": "^7.13.10",
"@babel/plugin-transform-runtime": "^7.11.5", "@babel/plugin-transform-runtime": "^7.11.5",
"@babel/preset-env": "^7.11.0", "@babel/preset-env": "^7.11.0",
"@rollup/plugin-babel": "^5.0.0", "@rollup/plugin-babel": "^5.0.0",
"babel-jest": "^26.3.0", "@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-node-resolve": "^11.2.0",
"@rollup/plugin-replace": "^2.4.1",
"babel-jest": "^26.6.3",
"babel-plugin-add-import-extension": "^1.4.3", "babel-plugin-add-import-extension": "^1.4.3",
"cheerio": "^1.0.0-rc.2",
"core-js": "3", "core-js": "3",
"eslint": "^4.19.1", "eslint": "^4.19.1",
"eslint-config-airbnb-base": "^12.1.0", "eslint-config-airbnb-base": "^12.1.0",
"eslint-config-prettier": "^2.9.0", "eslint-config-prettier": "^2.9.0",
"eslint-plugin-import": "^2.5.0", "eslint-plugin-import": "^2.5.0",
"eslint-plugin-prettier": "^2.5.0", "eslint-plugin-prettier": "^2.5.0",
"html-minifier": "^3.5.8",
"htmlparser2": "^4.1.0",
"husky": "^4.3.6", "husky": "^4.3.6",
"jest": "^26.4.2", "jest": "^26.4.2",
"lint-staged": "^10.5.3", "lint-staged": "^10.5.3",
"minimist": "^1.2.5", "minimist": "^1.2.5",
"prettier": "1.17.1", "prettier": "1.17.1",
"rollup": "^2.7.3", "rollup": "^2.7.3",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-license": "^2.0.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-terser": "^5.2.0",
"rollup-plugin-visualizer": "^4.1.0", "rollup-plugin-visualizer": "^4.1.0",
"svgo": "^1.3.2" "svgo": "^1.3.2",
"svgson": "^4.1.0"
}, },
"husky": { "husky": {
"hooks": { "hooks": {

View File

@@ -2,6 +2,7 @@
"name": "lucide-figma", "name": "lucide-figma",
"version": "0.13.0", "version": "0.13.0",
"license": "ISC", "license": "ISC",
"private": true,
"main": "build/ui.js", "main": "build/ui.js",
"scripts": { "scripts": {
"build": "webpack --mode=production", "build": "webpack --mode=production",

View File

@@ -0,0 +1,15 @@
ISC License
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
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -1,6 +1,8 @@
# Lucide React # Lucide React
Use the lucide icon library in you react app. Implementation of the lucide icon library for react applications.
> What is lucide? Read it [here](https://github.com/lucide-icons/lucide#what-is-lucide).
## Installation ## Installation

View File

@@ -1,3 +1,6 @@
const mainConfig = require('../../babel.config');
module.exports = { module.exports = {
presets: ['react-app'], presets: ['react-app'],
env: mainConfig.env,
}; };

View File

@@ -1,54 +0,0 @@
const fs = require("fs")
const path = require("path")
const srcDirectory = path.join(__dirname, "dist")
// Declare type definitions
const typeDefinitions = `
/// <reference types="react" />
import { SVGAttributes } from 'react'
// Create interface extending SVGAttributes
export interface LucideProps extends Partial<React.SVGProps<SVGSVGElement>> {
color?: string
size?: string | number
stroke?: string | number
strokeWidth?: string | number
}
// Generated icons
`
// Write type definitions to file
fs.writeFileSync(
path.join(srcDirectory, "index.d.ts"),
typeDefinitions,
"utf-8",
)
const iconsFolder = "./build/icons"
fs.readdir(iconsFolder, (err, files) => {
files.forEach(file => {
const fileName = file.replace(".js", "")
// Convert fileName to component name
const componentName = fileName.split("-")
.map(word => word[0].toUpperCase() + word.substr(1, word.length))
.join("")
// Declare component type
const exportTypeString = `export declare const ${componentName}: (props: LucideProps) => JSX.Element;\n`
// Add component to the types file
fs.appendFileSync(
path.join(srcDirectory, "index.d.ts"),
exportTypeString,
"utf-8",
)
})
console.log("Generated index.d.ts file with", files.length, "icons")
})

View File

@@ -3,9 +3,6 @@ module.exports = {
roots: ['<rootDir>/src/', '<rootDir>/tests/'], roots: ['<rootDir>/src/', '<rootDir>/tests/'],
moduleFileExtensions: ['js'], moduleFileExtensions: ['js'],
transformIgnorePatterns: [`/node_modules`], transformIgnorePatterns: [`/node_modules`],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
transform: { transform: {
'^.+\\.js$': 'babel-jest', '^.+\\.js$': 'babel-jest',
}, },

View File

@@ -16,14 +16,13 @@
"main:umd": "dist/umd/lucide-react.js", "main:umd": "dist/umd/lucide-react.js",
"module": "dist/esm/lucide-react.js", "module": "dist/esm/lucide-react.js",
"unpkg": "dist/umd/lucide-react.min.js", "unpkg": "dist/umd/lucide-react.min.js",
"typings": "dist/index.d.ts", "typings": "dist/lucide-react.d.ts",
"scripts": { "scripts": {
"build": "yarn clean && yarn build:move && yarn build:icons && yarn build:es && yarn build:types && yarn build:bundles", "build": "yarn clean && yarn build:icons && yarn build:es && yarn build:types && yarn build:bundles",
"clean": "rm -rf dist && rm -rf build", "clean": "rm -rf dist && rm -rf ./src/icons/*.js",
"build:move": "cp -av src build", "build:icons": "yarn --cwd ../../ build:icons --output=../packages/lucide-react/src --templateSrc=../packages/lucide-react/scripts/exportTemplate --renderUniqueKey",
"build:icons": "yarn --cwd ../../ build:icons --output=../packages/lucide-react/build --templateSrc=../packages/lucide-react/scripts/exportTemplate --camelizeAttrs --noDefaultAttrs --renderUniqueKey", "build:es": "yarn --cwd ../../ babel packages/lucide-react/src -d packages/lucide-react/dist/esm",
"build:types": "node build-types.js", "build:types": "yarn --cwd ../../ babel-node packages/lucide-react/scripts/buildTypes.js",
"build:es": "yarn --cwd ../../ babel packages/lucide-react/build -d packages/lucide-react/dist/esm",
"build:bundles": "yarn --cwd ../../ rollup -c packages/lucide-react/rollup.config.js", "build:bundles": "yarn --cwd ../../ rollup -c packages/lucide-react/rollup.config.js",
"test": "jest" "test": "jest"
}, },

View File

@@ -1,11 +1,11 @@
const plugins = require('../../rollup.plugins'); import plugins from '../../rollup.plugins';
const pkg = require('./package.json'); import pkg from './package.json';
const packageName = 'LucideReact'; const packageName = 'LucideReact';
const outputFileName = 'lucide-react'; const outputFileName = 'lucide-react';
const rootDir = 'packages/lucide-react'; // It runs from the root const rootDir = 'packages/lucide-react'; // It runs from the root
const outputDir = `${rootDir}/dist`; const outputDir = `${rootDir}/dist`;
const inputs = [`${rootDir}/build/lucide-react.js`]; const inputs = [`${rootDir}/src/lucide-react.js`];
const bundles = [ const bundles = [
{ {
format: 'umd', format: 'umd',
@@ -30,7 +30,7 @@ const configs = bundles
inputs.map(input => ({ inputs.map(input => ({
input, input,
plugins: plugins(pkg, minify), plugins: plugins(pkg, minify),
external: ['react', 'prop-types'], external: ['react', 'prop-types', 'lucide'],
output: { output: {
name: packageName, name: packageName,
file: `${outputDir}/${format}/${outputFileName}${minify ? '.min' : ''}.js`, file: `${outputDir}/${format}/${outputFileName}${minify ? '.min' : ''}.js`,
@@ -39,6 +39,7 @@ const configs = bundles
globals: { globals: {
react: 'react', react: 'react',
'prop-types': 'PropTypes', 'prop-types': 'PropTypes',
lucide: 'lucide',
}, },
}, },
})), })),

View File

@@ -0,0 +1,44 @@
import path from 'path';
import {
writeFile,
readSvgDirectory,
resetFile,
toPascalCase,
appendFile,
} from '../../../scripts/helpers';
const srcDirectory = path.join(__dirname, '../dist');
// Declare type definitions
const typeDefinitions = `\
/// <reference types="react" />
import { SVGAttributes } from 'react'
// Create interface extending SVGAttributes
export interface LucideProps extends Partial<React.SVGProps<SVGSVGElement>> {
color?: string
size?: string | number
stroke?: string | number
strokeWidth?: string | number
}
// Generated icons
`;
const ICONS_DIR = path.resolve(__dirname, '../../../icons');
const TYPES_FILE = 'lucide-react.d.ts';
resetFile(TYPES_FILE, srcDirectory);
writeFile(typeDefinitions, TYPES_FILE, srcDirectory);
const svgFiles = readSvgDirectory(ICONS_DIR);
svgFiles.forEach(svgFile => {
const iconName = path.basename(svgFile, '.svg');
const componentName = toPascalCase(iconName);
const exportTypeString = `export declare const ${componentName}: (props: LucideProps) => JSX.Element;\n`;
appendFile(exportTypeString, TYPES_FILE, srcDirectory);
});
console.log(`Generated ${TYPES_FILE} file with`, svgFiles.length, 'icons');

View File

@@ -1,8 +1,7 @@
export default ({ componentName, node }) => ` export default ({ componentName, children }) => `
import createReactComponent from '../createReactComponent'; import createReactComponent from '../createReactComponent';
import defaultAttributes from '../defaultAttributes';
const ${componentName} = createReactComponent('${componentName}', ['svg', defaultAttributes, ${node}]); const ${componentName} = createReactComponent('${componentName}', ${JSON.stringify(children)});
export default ${componentName}; export default ${componentName};
`; `;

View File

@@ -1,21 +1,22 @@
import { forwardRef, createElement } from 'react'; import { forwardRef, createElement } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import defaultAttributes from './defaultAttributes';
export default (iconName, [tag, attrs, children]) => { export default (iconName, iconNode) => {
const Component = forwardRef( const Component = forwardRef(
({ color = 'currentColor', size = 24, strokeWidth = 2, ...rest }, ref) => ({ color = 'currentColor', size = 24, strokeWidth = 2, ...rest }, ref) =>
createElement( createElement(
tag, 'svg',
{ {
ref, ref,
...attrs, ...defaultAttributes,
width: size, width: size,
height: size, height: size,
stroke: color, stroke: color,
strokeWidth, strokeWidth,
...rest, ...rest,
}, },
children.map(([childTag, childAttrs]) => createElement(childTag, childAttrs)), iconNode.map(([tag, attrs]) => createElement(tag, attrs)),
), ),
); );

View File

@@ -0,0 +1 @@
Folder for generated icons

View File

@@ -1,5 +0,0 @@
/*
Icons exports.
Will be generated
*/

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import renderer from 'react-test-renderer'; import renderer from 'react-test-renderer';
import { Grid } from '..' import { Grid } from '../src/icons'
describe('Using lucide icon components', () => { describe('Using lucide icon components', () => {
it('should render an component', () => { it('should render an component', () => {

View File

@@ -0,0 +1,15 @@
ISC License
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
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -1,6 +1,8 @@
# Lucide Vue # Lucide Vue
Use the lucide icon library in you Vue app. Implementation of the lucide icon library for Vue applications.
> What is lucide? Read it [here](https://github.com/lucide-icons/lucide#what-is-lucide).
## Installation ## Installation

View File

@@ -7,7 +7,7 @@ module.exports = {
'^.+\\.vue$': 'vue-jest', '^.+\\.vue$': 'vue-jest',
}, },
transformIgnorePatterns: [`/node_modules`], transformIgnorePatterns: [`/node_modules`],
snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'], snapshotSerializers: ['jest-serializer-vue'],
moduleNameMapper: { moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1', '^@/(.*)$': '<rootDir>/src/$1',
}, },

View File

@@ -21,19 +21,16 @@
"vue": "^2.6.12" "vue": "^2.6.12"
}, },
"scripts": { "scripts": {
"build": "yarn clean && yarn build:move && yarn build:icons && yarn build:es && yarn build:bundles", "build": "yarn clean && yarn build:icons && yarn build:es && yarn build:bundles",
"clean": "rm -rf dist && rm -rf build", "clean": "rm -rf dist && rm -rf ./src/icons/*.js",
"build:move": "cp -av src build", "build:icons": "yarn --cwd ../../ build:icons --output=../packages/lucide-vue/src --templateSrc=../packages/lucide-vue/scripts/exportTemplate",
"build:icons": "yarn --cwd ../../ build:icons --output=../packages/lucide-vue/build --templateSrc=../packages/lucide-vue/scripts/exportTemplate --noDefaultAttrs", "build:es": "yarn --cwd ../../ babel packages/lucide-vue/src -d packages/lucide-vue/dist/esm",
"build:es": "yarn --cwd ../../ babel packages/lucide-vue/build -d packages/lucide-vue/dist/esm",
"build:bundles": "yarn --cwd ../../ rollup -c packages/lucide-vue/rollup.config.js", "build:bundles": "yarn --cwd ../../ rollup -c packages/lucide-vue/rollup.config.js",
"test": "jest", "test": "jest",
"test:watch": "jest --watchAll" "test:watch": "jest --watchAll"
}, },
"devDependencies": { "devDependencies": {
"@vue/test-utils": "^1.1.2", "@vue/test-utils": "^1.1.2",
"babel-jest": "^26.6.3",
"jest": "^26.6.3",
"jest-serializer-vue": "^2.0.2", "jest-serializer-vue": "^2.0.2",
"vue-jest": "^3.0.7", "vue-jest": "^3.0.7",
"vue-template-compiler": "^2.6.12" "vue-template-compiler": "^2.6.12"

View File

@@ -1,11 +1,11 @@
const plugins = require('../../rollup.plugins'); import plugins from '../../rollup.plugins';
const pkg = require('./package.json'); import pkg from './package.json';
const packageName = 'LucideVue'; const packageName = 'LucideVue';
const outputFileName = 'lucide-vue'; const outputFileName = 'lucide-vue';
const rootDir = 'packages/lucide-vue'; // It runs from the root const rootDir = 'packages/lucide-vue'; // It runs from the root
const outputDir = `${rootDir}/dist`; const outputDir = `${rootDir}/dist`;
const inputs = [`${rootDir}/build/lucide-vue.js`]; const inputs = [`${rootDir}/src/lucide-vue.js`];
const bundles = [ const bundles = [
{ {
format: 'umd', format: 'umd',

View File

@@ -1,8 +1,7 @@
export default ({ componentName, node }) => ` export default ({ componentName, children }) => `
import createVueComponent from '../createVueComponent'; import createVueComponent from '../createVueComponent';
import defaultAttributes from '../defaultAttributes';
const ${componentName} = createVueComponent('${componentName}Icon', ['svg', defaultAttributes, ${node}]); const ${componentName} = createVueComponent('${componentName}Icon', ${JSON.stringify(children)});
export default ${componentName}; export default ${componentName};
`; `;

View File

@@ -1,4 +1,6 @@
export default (iconName, [tag, defaultAttrs, children]) => ({ import defaultAttributes from './defaultAttributes';
export default (iconName, iconNode) => ({
name: iconName, name: iconName,
functional: true, functional: true,
props: { props: {
@@ -27,13 +29,13 @@ export default (iconName, [tag, defaultAttrs, children]) => ({
}, },
) { ) {
return createElement( return createElement(
tag, 'svg',
{ {
// eslint-disable-next-line prettier/prettier // eslint-disable-next-line prettier/prettier
class: [defaultClass, data.class, data.staticClass, data.attrs && data.attrs.class].filter(Boolean), class: [defaultClass, data.class, data.staticClass, data.attrs && data.attrs.class].filter(Boolean),
style: [data.style, data.staticStyle, data.attrs && data.attrs.style].filter(Boolean), style: [data.style, data.staticStyle, data.attrs && data.attrs.style].filter(Boolean),
attrs: { attrs: {
...defaultAttrs, ...defaultAttributes,
width: size, width: size,
height: size, height: size,
stroke: color, stroke: color,
@@ -41,7 +43,7 @@ export default (iconName, [tag, defaultAttrs, children]) => ({
...data.attrs, ...data.attrs,
}, },
}, },
children.map(([childTag, childAttrs]) => createElement(childTag, { attrs: childAttrs })), iconNode.map(([tag, attrs]) => createElement(tag, { attrs })),
); );
}, },
}); });

View File

@@ -0,0 +1 @@
Folder for generated icons

View File

@@ -1,5 +0,0 @@
/*
Icons exports.
Will be generated
*/

View File

@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import { Smile } from '..' import { Smile } from '../src/icons'
describe('Using lucide icon components', () => { describe('Using lucide icon components', () => {
it('should render an component', () => { it('should render an component', () => {

1
packages/lucide/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
src/icons/*.js

View File

@@ -0,0 +1,10 @@
stats
node_modules
tests
scripts
build
src
babel.config.js
jest.config.js
rollup.config.js
yarn.error.log

15
packages/lucide/LICENSE Normal file
View File

@@ -0,0 +1,15 @@
ISC License
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
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

120
packages/lucide/README.md Normal file
View File

@@ -0,0 +1,120 @@
# Lucide
Implementation of the lucide icon library for web applications.
## 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
### 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 built to be fully treeshakable.
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';
// Caution, 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', // attribute for the icon name.
});
```
#### Treeshake 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);
```

View File

@@ -0,0 +1,14 @@
const mainConfig = require('../../babel.config');
module.exports = {
presets: [
[
'@babel/env',
{
loose: true,
modules: false,
},
],
],
env: mainConfig.env,
};

View File

@@ -5,9 +5,6 @@ module.exports = {
roots: ['<rootDir>/src/', '<rootDir>/tests/'], roots: ['<rootDir>/src/', '<rootDir>/tests/'],
moduleFileExtensions: ['js'], moduleFileExtensions: ['js'],
transformIgnorePatterns: [`/node_modules/(?!${esModules})`], transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
transform: { transform: {
'^.+\\.js$': 'babel-jest', '^.+\\.js$': 'babel-jest',
}, },

View File

@@ -0,0 +1,30 @@
{
"name": "lucide",
"description": "Lucide is a community-run fork of Feather Icons, open for anyone to contribute icons.",
"version": "0.14.0",
"license": "ISC",
"homepage": "https://lucide.dev",
"bugs": "https://github.com/lucide-icons/lucide/issues",
"repository": {
"type": "git",
"url": "https://github.com/lucide-icons/lucide.git",
"directory": "packages/lucide"
},
"amdName": "lucide",
"source": "build/lucide.js",
"main": "dist/cjs/lucide.js",
"main:umd": "dist/umd/lucide.js",
"module": "dist/esm/lucide.js",
"unpkg": "dist/umd/lucide.min.js",
"typings": "dist/lucide.d.ts",
"sideEffects": false,
"scripts": {
"build": "yarn clean && yarn build:icons && yarn build:es && yarn build:types && yarn build:bundles",
"clean": "rm -rf dist && rm -rf ./src/icons/*.js",
"build:icons": "yarn --cwd ../../ build:icons --output=../packages/lucide/src",
"build:es": "babel src -d dist/esm",
"build:types": "yarn --cwd ../../ babel-node packages/lucide/scripts/buildTypes.js",
"build:bundles": "rollup -c rollup.config.js",
"test": "jest"
}
}

View File

@@ -1,9 +1,9 @@
const plugins = require('./rollup.plugins'); import plugins from '../../rollup.plugins';
const pkg = require('./package.json'); import pkg from './package.json';
const outputFileName = pkg.name; const outputFileName = pkg.name;
const outputDir = 'dist'; const outputDir = 'dist';
const inputs = ['build/lucide.js']; const inputs = ['src/lucide.js'];
const bundles = [ const bundles = [
{ {
format: 'umd', format: 'umd',
@@ -38,4 +38,4 @@ const configs = bundles
) )
.flat(); .flat();
module.exports = configs; export default configs;

View File

@@ -0,0 +1,37 @@
import path from 'path';
import { readSvgDirectory, resetFile, appendFile, toPascalCase } from '../../../scripts/helpers';
const TARGET_DIR = path.join(__dirname, '../dist');
const ICONS_DIR = path.resolve(__dirname, '../../../icons');
const TYPES_FILE_NAME = 'lucide.d.ts';
// Generates header of d.ts file include some types and functions
const typeDefinitions = `\
export type IconName = string;
export type IconNode = readonly [tag: string, object:SVGProps<SVGSVGElement>, children:IconNode?];
export type IconsObj = { [IconName]: IconNode }
export interface Attributes extends Partial<Props<Element>> {}
export function createElement(icon: IconNode): SVGSVGElement;
export function createIcons({ icons: IconsObj, nameAttr: string = 'icon-name', attrs: Attributes = {} }): VoidFunction;
export declare const icons: IconsObj;
// Generated icons
`;
resetFile(TYPES_FILE_NAME, TARGET_DIR);
appendFile(typeDefinitions, TYPES_FILE_NAME, TARGET_DIR);
const svgFiles = readSvgDirectory(ICONS_DIR);
svgFiles.forEach(svgFile => {
const nameSvg = path.basename(svgFile, '.svg');
const namePascal = toPascalCase(nameSvg);
appendFile(`export declare const ${namePascal}: IconNode;\n`, TYPES_FILE_NAME, TARGET_DIR);
});
console.log(`Generated ${TYPES_FILE_NAME} file with`, svgFiles.length, 'icons');

View File

@@ -5,7 +5,7 @@ export default {
viewBox: '0 0 24 24', viewBox: '0 0 24 24',
fill: 'none', fill: 'none',
stroke: 'currentColor', stroke: 'currentColor',
strokeWidth: 2, 'stroke-width': 2,
strokeLinecap: 'round', 'stroke-linecap': 'round',
strokeLinejoin: 'round', 'stroke-linejoin': 'round',
}; };

View File

@@ -0,0 +1 @@
Folder for generated icons

View File

@@ -1,5 +1,5 @@
import replaceElement from './replaceElement'; import replaceElement from './replaceElement';
import * as allIcons from './icons/index'; import * as allIcons from './icons';
/** /**
* Replaces all elements with matching nameAttr with the defined icons * Replaces all elements with matching nameAttr with the defined icons
@@ -34,4 +34,4 @@ export { default as createElement } from './createElement';
Icons exports. Icons exports.
*/ */
export { allIcons as icons }; export { allIcons as icons };
export * from './icons/index'; export * from './icons';

View File

@@ -17,6 +17,7 @@ export const getAttrs = element =>
* @returns {Array} * @returns {Array}
*/ */
export const getClassNames = attrs => { export const getClassNames = attrs => {
if (typeof attrs === 'string') return attrs;
if (!attrs || !attrs.class) return ''; if (!attrs || !attrs.class) return '';
if (attrs.class && typeof attrs.class === 'string') { if (attrs.class && typeof attrs.class === 'string') {
return attrs.class.split(' '); return attrs.class.split(' ');
@@ -38,6 +39,7 @@ export const combineClassNames = arrayOfClassnames => {
return classNameArray return classNameArray
.map(classItem => classItem.trim()) .map(classItem => classItem.trim())
.filter(Boolean) .filter(Boolean)
.filter((value, index, self) => self.indexOf(value) === index)
.join(' '); .join(' ');
}; };
@@ -63,23 +65,21 @@ export default (element, { nameAttr, icons, attrs }) => {
} }
const elementAttrs = getAttrs(element); const elementAttrs = getAttrs(element);
const [tag, iconAttributes, children] = iconNode;
const [, iconAttrs] = iconNode; const iconAttrs = {
...iconAttributes,
const allAttrs = { 'icon-name': iconName,
...iconAttrs,
...attrs, ...attrs,
}; };
iconNode[1] = { ...allAttrs }; const classNames = combineClassNames(['lucide', elementAttrs, attrs]);
const classNames = combineClassNames([iconAttrs, elementAttrs, attrs]);
if (classNames) { if (classNames) {
iconNode[1].class = classNames; iconAttrs.class = classNames;
} }
const svgElement = createElement(iconNode); const svgElement = createElement([tag, iconAttrs, children]);
return element.parentNode.replaceChild(svgElement, element); return element.parentNode.replaceChild(svgElement, element);
}; };

View File

@@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`createIcons should add custom attributes 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"black\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" icon-name=\\"volume-2\\" class=\\"lucide icon custom-class\\"><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 010 14.14M15.54 8.46a5 5 0 010 7.07\\"></path></svg>"`;
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\\" icon-name=\\"volume-2\\" class=\\"lucide\\"><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 010 14.14M15.54 8.46a5 5 0 010 7.07\\"></path></svg>"`;

View File

@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // 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"`; exports[`combineClassNames should retuns a string of classNames 1`] = `"item item1 item2 item3 item4 item5 item6 item7 item8 item9"`;

View File

@@ -1,15 +1,19 @@
import * as icons from './icons'; import * as icons from '../src/icons';
import { createIcons } from '../src/lucide'; import { createIcons } from '../src/lucide';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { minify } from 'html-minifier'; import { parseSync, stringify } from 'svgson';
const ICONS_DIR = path.resolve(__dirname, '../icons'); const ICONS_DIR = path.resolve(__dirname, '../../../icons');
const getOriginalSvg = (iconName) => { const getOriginalSvg = (iconName) => {
const svgContent = fs.readFileSync(path.join(ICONS_DIR, `${iconName}.svg`), 'utf8'); const svgContent = fs.readFileSync(path.join(ICONS_DIR, `${iconName}.svg`), 'utf8');
const svgParsed = parseSync(svgContent);
return minify(svgContent, { collapseWhitespace: true, keepClosingSlash: true, }); svgParsed.attributes['icon-name'] = iconName;
svgParsed.attributes['class'] = 'lucide';
return stringify(svgParsed, { selfClose: false });
}; };
describe('createIcons', () => { describe('createIcons', () => {
@@ -19,6 +23,8 @@ describe('createIcons', () => {
createIcons({icons}); createIcons({icons});
const svg = getOriginalSvg('volume-2'); const svg = getOriginalSvg('volume-2');
expect(document.body.innerHTML).toBe(svg)
expect(document.body.innerHTML).toMatchSnapshot() expect(document.body.innerHTML).toMatchSnapshot()
}); });
@@ -36,10 +42,10 @@ describe('createIcons', () => {
}); });
it('should add custom attributes', () => { it('should add custom attributes', () => {
document.body.innerHTML = `<i icon-name="volume-2"></i>`; document.body.innerHTML = `<i icon-name="volume-2" class="lucide"></i>`;
const attrs = { const attrs = {
class: 'icon custom-class', class: 'lucide icon custom-class',
fill: 'black', fill: 'black',
}; };
@@ -54,6 +60,8 @@ describe('createIcons', () => {
return acc; return acc;
},{}) },{})
expect(document.body.innerHTML).toMatchSnapshot();
expect(attributesAndValues).toEqual(expect.objectContaining(attrs)); expect(attributesAndValues).toEqual(expect.objectContaining(attrs));
}); });
}); });

View File

@@ -44,6 +44,7 @@ describe('getClassNames', () => {
describe('combineClassNames', () => { describe('combineClassNames', () => {
it('should retuns a string of classNames', () => { it('should retuns a string of classNames', () => {
const arrayOfClassnames = [ const arrayOfClassnames = [
'item',
{ {
class: ['item1','item2','item3'] class: ['item1','item2','item3']
}, },

View File

@@ -1,18 +1,20 @@
const babel = require('@rollup/plugin-babel').default; /* eslint-disable import/no-extraneous-dependencies */
const bundleSize = require('@atomico/rollup-plugin-sizes'); import babel from '@rollup/plugin-babel';
const compiler = require('@ampproject/rollup-plugin-closure-compiler'); import bundleSize from '@atomico/rollup-plugin-sizes';
const { terser } = require('rollup-plugin-terser'); import compiler from '@ampproject/rollup-plugin-closure-compiler';
const visualizer = require('rollup-plugin-visualizer'); import { terser } from 'rollup-plugin-terser';
const license = require('rollup-plugin-license'); import visualizer from 'rollup-plugin-visualizer';
const replace = require('rollup-plugin-replace'); import license from 'rollup-plugin-license';
const resolve = require('rollup-plugin-node-resolve'); import replace from '@rollup/plugin-replace';
const commonJS = require('rollup-plugin-commonjs'); import resolve from '@rollup/plugin-node-resolve';
import commonJS from '@rollup/plugin-commonjs';
const plugins = (pkg, minify) => const plugins = (pkg, minify) =>
[ [
replace({ replace({
'icons = {}': 'icons = allIcons', 'icons = {}': 'icons = allIcons',
delimiters: ['', ''], delimiters: ['', ''],
preventAssignment: false,
}), }),
babel({ babel({
babelHelpers: 'bundled', babelHelpers: 'bundled',
@@ -34,4 +36,4 @@ const plugins = (pkg, minify) =>
}), }),
].filter(Boolean); ].filter(Boolean);
module.exports = plugins; export default plugins;

View File

@@ -4,24 +4,28 @@ import path from 'path';
import prettier from 'prettier'; import prettier from 'prettier';
import { toPascalCase } from '../helpers'; import { toPascalCase } from '../helpers';
export default function(iconNode, outputDirectory, template) { export default function(iconNodes, outputDirectory, template, { showLog = true }) {
const icons = Object.keys(iconNode); const icons = Object.keys(iconNodes);
const iconsDistDirectory = path.join(outputDirectory, `icons`); const iconsDistDirectory = path.join(outputDirectory, `icons`);
if (!fs.existsSync(iconsDistDirectory)) { if (!fs.existsSync(iconsDistDirectory)) {
fs.mkdirSync(iconsDistDirectory); fs.mkdirSync(iconsDistDirectory);
} }
icons.forEach(icon => { icons.forEach(iconName => {
const location = path.join(iconsDistDirectory, `${icon}.js`); const location = path.join(iconsDistDirectory, `${iconName}.js`);
const componentName = toPascalCase(icon); const componentName = toPascalCase(iconName);
const node = JSON.stringify(iconNode[icon]); let { children } = iconNodes[iconName];
children = children.map(({name, attributes}) => ([name, attributes]))
const elementTemplate = template({ componentName, node }); const elementTemplate = template({ componentName, iconName, children });
fs.writeFileSync(location, prettier.format(elementTemplate, { parser: 'babel' }), 'utf-8');
console.log('Successfully built', componentName); fs.writeFileSync(location, prettier.format(elementTemplate, { singleQuote: true, trailingComma: 'all', parser: 'babel' }), 'utf-8');
if(showLog) {
console.log('Successfully built', componentName);
}
}); });
} }

View File

@@ -1,35 +0,0 @@
import path from 'path';
import { readSvgDirectory, resetFile, appendFile, toPascalCase } from './helpers';
const ICONS_DIR = path.resolve(__dirname, '../icons');
const DTS_FILE_NAME = 'index.d.ts';
const DTS_FILE_ROOT = path.resolve(__dirname, '../');
resetFile(DTS_FILE_NAME, DTS_FILE_ROOT);
// Generates header of d.ts file include some types and functions
appendFile(
`
export type IconData = readonly [string, object, IconData?];
export type IconString = string;
export function createElement(ico: IconData): SVGSVGElement;
export declare const icons: { [key: string]: IconData };
// Generated icons
`,
DTS_FILE_NAME,
DTS_FILE_ROOT,
);
const svgFiles = readSvgDirectory(ICONS_DIR);
svgFiles.forEach(svgFile => {
const nameSvg = path.basename(svgFile, '.svg');
const namePascal = toPascalCase(nameSvg);
appendFile(`export declare const ${namePascal}: IconData;\n`, DTS_FILE_NAME, DTS_FILE_ROOT);
});
console.log(`Successfully generated '${DTS_FILE_NAME}'.`);

View File

@@ -1,15 +1,12 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
// eslint-disable-next-line import/no-extraneous-dependencies import getArgumentOptions from 'minimist'; // eslint-disable-line import/no-extraneous-dependencies
import getArgumentOptions from 'minimist';
import renderIconsObject from './render/renderIconsObject'; import renderIconsObject from './render/renderIconsObject';
import renderIconNodes from './render/renderIconNodes';
import generateIconFiles from './build/generateIconFiles'; import generateIconFiles from './build/generateIconFiles';
import generateExportsFile from './build/generateExportsFile'; import generateExportsFile from './build/generateExportsFile';
import { readSvgDirectory } from './helpers';
/* eslint-disable import/no-dynamic-require */ import { readSvgDirectory } from './helpers';
const cliArguments = getArgumentOptions(process.argv.slice(2)); const cliArguments = getArgumentOptions(process.argv.slice(2));
@@ -23,26 +20,14 @@ if (!fs.existsSync(OUTPUT_DIR)) {
const svgFiles = readSvgDirectory(ICONS_DIR); const svgFiles = readSvgDirectory(ICONS_DIR);
const icons = renderIconsObject(svgFiles, ICONS_DIR); const icons = renderIconsObject(svgFiles, ICONS_DIR, cliArguments.renderUniqueKey);
const iconVNodes = renderIconNodes(icons, cliArguments); const defaultIconFileTemplate = './templates/defaultIconFileTemplate';
// eslint-disable-next-line import/no-dynamic-require
const defaultIconFileTemplate = ({ componentName, node }) => ` const iconFileTemplate = require(cliArguments.templateSrc || defaultIconFileTemplate).default;
const ${componentName} = ${node};
export default ${componentName};
`;
const iconFileTemplate = cliArguments.templateSrc
? require(cliArguments.templateSrc).default
: defaultIconFileTemplate;
// Generates iconsNodes files for each icon // Generates iconsNodes files for each icon
generateIconFiles(iconVNodes, OUTPUT_DIR, iconFileTemplate); generateIconFiles(icons, OUTPUT_DIR, iconFileTemplate, { showLog: !cliArguments.silent });
// Generates entry files for the compiler filled with icons exports // Generates entry files for the compiler filled with icons exports
generateExportsFile( generateExportsFile(path.join(SRC_DIR, 'icons/index.js'), path.join(OUTPUT_DIR, 'icons'), icons);
path.join(SRC_DIR, 'icons/index.js'),
path.join(OUTPUT_DIR, 'icons'),
iconVNodes,
);

View File

@@ -2,9 +2,10 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
/** /**
* Converts string to PascalCase * Converts string to CamelCase
* *
* @param {string} string * @param {string} string
* @returns {string} A camelized string
*/ */
export const toCamelCase = string => export const toCamelCase = string =>
string.replace(/^([A-Z])|[\s-_]+(\w)/g, (match, p1, p2) => string.replace(/^([A-Z])|[\s-_]+(\w)/g, (match, p1, p2) =>
@@ -15,6 +16,7 @@ export const toCamelCase = string =>
* Converts string to PascalCase * Converts string to PascalCase
* *
* @param {string} string * @param {string} string
* @returns {string} A pascalized string
*/ */
export const toPascalCase = string => { export const toPascalCase = string => {
const camelCase = toCamelCase(string); const camelCase = toCamelCase(string);
@@ -23,9 +25,10 @@ export const toPascalCase = string => {
}; };
/** /**
* Converts string to PascalCase * Converts string to KebabCase
* *
* @param {string} string * @param {string} string
* @returns {string} A kebabized string
*/ */
export const toKebabCase = string => string.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase(); export const toKebabCase = string => string.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
@@ -42,6 +45,7 @@ export const resetFile = (fileName, outputDirectory) =>
* Reads the file contents. * Reads the file contents.
* *
* @param {string} path * @param {string} path
* @returns {string} The contents of a file
*/ */
export const readFile = entry => fs.readFileSync(path.resolve(__dirname, '../', entry), 'utf-8'); export const readFile = entry => fs.readFileSync(path.resolve(__dirname, '../', entry), 'utf-8');
@@ -69,6 +73,7 @@ export const writeFile = (content, fileName, outputDirectory) =>
* reads the icon directory * reads the icon directory
* *
* @param {string} directory * @param {string} directory
* @returns {array} An array of file paths containig svgs
*/ */
export const readSvgDirectory = directory => export const readSvgDirectory = directory =>
fs.readdirSync(directory).filter(file => path.extname(file) === '.svg'); fs.readdirSync(directory).filter(file => path.extname(file) === '.svg');
@@ -79,7 +84,8 @@ export const readSvgDirectory = directory =>
* @param {string} fileName * @param {string} fileName
* @param {string} directory * @param {string} directory
*/ */
export const readSvg = (fileName, directory) => fs.readFileSync(path.join(directory, fileName)); export const readSvg = (fileName, directory) =>
fs.readFileSync(path.join(directory, fileName), 'utf-8');
/** /**
* writes content to a file * writes content to a file
@@ -91,7 +97,13 @@ export const readSvg = (fileName, directory) => fs.readFileSync(path.join(direct
export const writeSvgFile = (fileName, outputDirectory, content) => export const writeSvgFile = (fileName, outputDirectory, content) =>
fs.writeFileSync(path.join(outputDirectory, fileName), content, 'utf-8'); fs.writeFileSync(path.join(outputDirectory, fileName), content, 'utf-8');
// This is a djb2 hashing function /**
* djb2 hashing function
*
* @param {string} string
* @param {number} seed
* @returns {string} A hashed string of 6 characters
*/
export const hash = (string, seed = 5381) => { export const hash = (string, seed = 5381) => {
let i = string.length; let i = string.length;
@@ -103,3 +115,27 @@ export const hash = (string, seed = 5381) => {
// eslint-disable-next-line no-bitwise // eslint-disable-next-line no-bitwise
return (seed >>> 0).toString(36).substr(0, 6); return (seed >>> 0).toString(36).substr(0, 6);
}; };
/**
* Generate Hashed string based on name and attributes
*
* @param {object} seed
* @param {string} seed.name A name, for example an icon name
* @param {object} seed.attributes An object of SVGElement Attrbutes
* @returns {string} A hashed string of 6 characters
*/
export const generateHashedKey = ({ name, attributes }) => hash(JSON.stringify([name, attributes]));
/**
* Checks if array of items contains duplicated items
*
* @param {array} children an array of items
* @returns {Boolean} if items contains duplicated items.
*/
export const hasDuplicatedChildren = children => {
const hashedKeys = children.map(generateHashedKey);
return !hashedKeys.every(
(key, index) => index === hashedKeys.findIndex(childKey => childKey === key),
);
};

View File

@@ -1,6 +1,6 @@
const { promises: fs } = require("fs"); const { promises: fs } = require('fs');
const outlineStroke = require("svg-outline-stroke"); const outlineStroke = require('svg-outline-stroke');
const { parse, stringify } = require("svgson"); const { parse, stringify } = require('svgson');
const inputDir = `./icons/`; const inputDir = `./icons/`;
const outputDir = `./converted_icons/`; const outputDir = `./converted_icons/`;
@@ -8,19 +8,16 @@ const outputDir = `./converted_icons/`;
async function init() { async function init() {
try { try {
const files = await fs.readdir(inputDir); const files = await fs.readdir(inputDir);
for (let file of files) { for (const file of files) {
const icon = await fs.readFile(`${inputDir}${file}`); const icon = await fs.readFile(`${inputDir}${file}`);
const scaled = await parse(icon.toString(), { const scaled = await parse(icon.toString(), {
transformNode: transformForward transformNode: transformForward,
}); });
const outlined = await outlineStroke(stringify(scaled)); const outlined = await outlineStroke(stringify(scaled));
const outlinedWithoutAttrs = await parse(outlined, { const outlinedWithoutAttrs = await parse(outlined, {
transformNode: transformBackwards transformNode: transformBackwards,
}); });
await fs.writeFile( await fs.writeFile(`${outputDir}${file}`, stringify(outlinedWithoutAttrs));
`${outputDir}${file}`,
stringify(outlinedWithoutAttrs)
);
} }
} catch (err) { } catch (err) {
console.log(err); console.log(err);
@@ -30,25 +27,25 @@ async function init() {
init(); init();
function transformForward(node) { function transformForward(node) {
if (node.name === "svg") { if (node.name === 'svg') {
return { return {
...node, ...node,
attributes: { attributes: {
...node.attributes, ...node.attributes,
width: 960, width: 960,
height: 960 height: 960,
} },
}; };
} }
return node; return node;
} }
function transformBackwards(node) { function transformBackwards(node) {
if (node.name === "svg") { if (node.name === 'svg') {
const { width, height, ...attributes } = node.attributes; const { width, height, ...attributes } = node.attributes;
return { return {
...node, ...node,
attributes attributes,
}; };
} }
return node; return node;

View File

@@ -1,30 +1,13 @@
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
import Svgo from 'svgo'; import Svgo from 'svgo';
import cheerio from 'cheerio';
import { format } from 'prettier'; import { format } from 'prettier';
import { parseSync, stringify } from 'svgson';
import DEFAULT_ATTRS from './default-attrs.json'; import DEFAULT_ATTRS from './default-attrs.json';
/**
* Process SVG string.
* @param {string} svg - An SVG string.
* @param {Promise<string>}
*/
function processSvg(svg) {
return (
optimize(svg)
.then(setAttrs)
.then(optimizedSvg => format(optimizedSvg, { parser: 'babel' }))
// remove semicolon inserted by prettier
// because prettier thinks it's formatting JSX not HTML
.then(svg => svg.replace(/;/g, ''))
);
}
/** /**
* Optimize SVG with `svgo`. * Optimize SVG with `svgo`.
* @param {string} svg - An SVG string. * @param {string} svg - An SVG string.
* @returns {Promise<string>} * @returns {Promise<string>} An optimized svg
*/ */
function optimize(svg) { function optimize(svg) {
const svgo = new Svgo({ const svgo = new Svgo({
@@ -42,14 +25,30 @@ function optimize(svg) {
/** /**
* Set default attibutes on SVG. * Set default attibutes on SVG.
* @param {string} svg - An SVG string. * @param {string} svg - An SVG string.
* @returns {string} * @returns {string} An SVG string, included with the default attributes.
*/ */
function setAttrs(svg) { function setAttrs(svg) {
const $ = cheerio.load(svg); const contents = parseSync(svg);
Object.keys(DEFAULT_ATTRS).forEach(key => $('svg').attr(key, DEFAULT_ATTRS[key])); contents.attributes = DEFAULT_ATTRS;
return $('body').html(); return stringify(contents);
}
/**
* Process SVG string.
* @param {string} svg An SVG string.
* @returns {Promise<string>} An optimized svg
*/
function processSvg(svg) {
return (
optimize(svg)
.then(setAttrs)
.then(optimizedSvg => format(optimizedSvg, { parser: 'babel' }))
// remove semicolon inserted by prettier
// because prettier thinks it's formatting JSX not HTML
.then(svg => svg.replace(/;/g, ''))
);
} }
export default processSvg; export default processSvg;

View File

@@ -1,53 +0,0 @@
/* eslint-disable import/no-extraneous-dependencies */
import { parseDOM } from 'htmlparser2';
import DEFAULT_ATTRS from './default-attrs.json';
import { toCamelCase, hash } from '../helpers';
const camelizeAttrs = attrs =>
Object.keys(attrs).reduce((newAttrs, attr) => {
const attrKey = toCamelCase(attr);
newAttrs[attrKey] = attrs[attr];
return newAttrs;
}, {});
export default (iconsObject, options) => {
const iconNodes = {};
Object.keys(iconsObject).forEach(icon => {
const svgString = iconsObject[icon];
const dom = parseDOM(svgString);
const children = dom.map(element => {
if (options.renderUniqueKey) {
const hashSource = {
name: element.name,
...element.attribs,
};
const uniqueKey = hash(JSON.stringify(hashSource));
element.attribs.key = uniqueKey;
}
return [
element.name,
{
...(options.camelizeAttrs ? camelizeAttrs(element.attribs) : element.attribs),
},
];
});
iconNodes[icon] = !options.noDefaultAttrs
? [
'svg',
{
...(options.camelizeAttrs ? camelizeAttrs(DEFAULT_ATTRS) : DEFAULT_ATTRS),
},
children,
]
: children;
});
return iconNodes;
};

View File

@@ -1,19 +1,7 @@
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
import path from 'path'; import { basename } from 'path';
import cheerio from 'cheerio'; import { parseSync } from 'svgson';
import { minify } from 'html-minifier'; import { generateHashedKey, readSvg, hasDuplicatedChildren } from '../helpers';
import { readSvg } from '../helpers';
/**
* Get contents between opening and closing `<svg>` tags.
* @param {string} svg
* @returns {string}
*/
function getSvgContents(svg) {
const $ = cheerio.load(svg);
return minify($('svg').html(), { collapseWhitespace: true });
}
/** /**
* Build an object in the format: `{ <name>: <contents> }`. * Build an object in the format: `{ <name>: <contents> }`.
@@ -21,12 +9,29 @@ function getSvgContents(svg) {
* @param {Function} getSvg - A function that returns the contents of an SVG file given a filename. * @param {Function} getSvg - A function that returns the contents of an SVG file given a filename.
* @returns {Object} * @returns {Object}
*/ */
export default (svgFiles, iconsDirectory) => export default (svgFiles, iconsDirectory, renderUniqueKey = false) =>
svgFiles svgFiles
.map(svgFile => { .map(svgFile => {
const name = path.basename(svgFile, '.svg'); const name = basename(svgFile, '.svg');
const svg = readSvg(svgFile, iconsDirectory); const svg = readSvg(svgFile, iconsDirectory);
const contents = getSvgContents(svg); const contents = parseSync(svg);
if (!(contents.children && contents.children.length)) {
throw new Error(`${name}.svg has no children!`);
}
if (hasDuplicatedChildren(contents.children)) {
throw new Error(`Duplicated children in ${name}.svg`);
}
if (renderUniqueKey) {
contents.children = contents.children.map(child => {
child.attributes.key = generateHashedKey(child);
return child;
});
}
return { name, contents }; return { name, contents };
}) })
.reduce((icons, icon) => { .reduce((icons, icon) => {

View File

@@ -0,0 +1,11 @@
export default ({ componentName, children }) => `
import defaultAttributes from '../defaultAttributes';
const ${componentName} = [
'svg',
defaultAttributes,
${JSON.stringify(children)}
];
export default ${componentName};
`;

View File

@@ -14,12 +14,10 @@
"@chakra-ui/core": "^1.0.0-rc.8", "@chakra-ui/core": "^1.0.0-rc.8",
"downloadjs": "^1.4.7", "downloadjs": "^1.4.7",
"framer-motion": "^3.3.0", "framer-motion": "^3.3.0",
"fuse.js": "^6.0.4",
"jszip": "^3.4.0", "jszip": "^3.4.0",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"lucide-react": "^0.11.0", "lucide-react": "0.14.0",
"next": "^10.0.4", "next": "^10.0.4",
"object-path": "0.11.5",
"react": "^16.13.1", "react": "^16.13.1",
"react-color": "2.17.3", "react-color": "2.17.3",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",

View File

@@ -1,8 +1,8 @@
import {Button, Flex, Link, WrapItem, Text, Wrap,} from "@chakra-ui/core"; import { Button, Flex, Link, WrapItem, Text, Wrap } from "@chakra-ui/core";
import download from "downloadjs"; import download from "downloadjs";
import JSZip from "jszip"; import JSZip from "jszip";
import { Download, Github } from 'lucide-react'; import { Download, Github } from 'lucide-react';
import {IconCustomizerDrawer} from "./IconCustomizerDrawer"; import { IconCustomizerDrawer } from "./IconCustomizerDrawer";
function generateZip(icons) { function generateZip(icons) {
const zip = new JSZip(); const zip = new JSZip();
@@ -31,7 +31,6 @@ const Header = ({ data }) => {
An open-source icon library, a fork of <Link href="https://github.com/feathericons/feather" isExternal>Feather Icons</Link>. <br/>We're expanding the icon set as much as possible while keeping it nice-looking - <Link href={repositoryUrl} isExternal>join us</Link>! An open-source icon library, a fork of <Link href="https://github.com/feathericons/feather" isExternal>Feather Icons</Link>. <br/>We're expanding the icon set as much as possible while keeping it nice-looking - <Link href={repositoryUrl} isExternal>join us</Link>!
</Text> </Text>
<Wrap <Wrap
isInline
marginTop={3} marginTop={3}
marginBottom={10} marginBottom={10}
spacing="15px" spacing="15px"

View File

@@ -218,7 +218,7 @@ const IconDetailOverlay = ({ open = true, close, icon }) => {
{ icon.contributors.map((commit, index) => ( { icon.contributors.map((commit, index) => (
<Link href={`https://github.com/${commit.author}`} isExternal key={`${index}_${commit.sha}`}> <Link href={`https://github.com/${commit.author}`} isExternal key={`${index}_${commit.sha}`}>
<Tooltip label={commit.author} key={commit.sha}> <Tooltip label={commit.author} key={commit.sha}>
<Avatar name={commit.author} showBorder={false} src={`https://github.com/${commit.author}.png?size=88`} /> <Avatar name={commit.author} src={`https://github.com/${commit.author}.png?size=88`} />
</Tooltip> </Tooltip>
</Link> </Link>
)) } )) }

View File

@@ -1,5 +0,0 @@
/*
Icons exports.
Will be generated
*/

View File

@@ -1,3 +0,0 @@
// 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>"`;

View File

@@ -1,21 +0,0 @@
const Download = [
"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: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }],
["polyline", { points: "7 10 12 15 17 10" }],
["line", { x1: "12", y1: "15", x2: "12", y2: "3" }]
]
];
export default Download;

View File

@@ -1,27 +0,0 @@
const Globe = [
"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"
},
[
["circle", { cx: "12", cy: "12", r: "10" }],
["line", { x1: "2", y1: "12", x2: "22", y2: "12" }],
[
"path",
{
d:
"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"
}
]
]
];
export default Globe;

View File

@@ -1,6 +0,0 @@
export { default as Download } from './download';
export { default as Globe } from './globe';
export { default as Menu } from './menu';
export { default as Moon } from './moon';
export { default as Volume2 } from './volume-2';

View File

@@ -1,21 +0,0 @@
const Menu = [
"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" }],
["line", { x1: "3", y1: "6", x2: "21", y2: "6" }],
["line", { x1: "3", y1: "18", x2: "21", y2: "18" }]
]
];
export default Menu;

View File

@@ -1,17 +0,0 @@
const Moon = [
"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: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" }]]
];
export default Moon;

View File

@@ -1,23 +0,0 @@
const Volume2 = [
"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" }],
[
"path",
{ d: "M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07" }
]
]
];
export default Volume2;

5992
yarn.lock

File diff suppressed because it is too large Load Diff