Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
571cab88ee | ||
|
|
68c5ed6097 | ||
|
|
85efb8e1b6 | ||
|
|
00c3487dff | ||
|
|
cec73c5217 | ||
|
|
8f1c7eb737 | ||
|
|
0dd10483c9 | ||
|
|
a0c447cece | ||
|
|
7cf928e94c | ||
|
|
8caf6efe72 | ||
|
|
a9fec942ff | ||
|
|
00cbc81331 | ||
|
|
4e3af5c601 | ||
|
|
632dda526a | ||
|
|
c858240c01 | ||
|
|
1fb70e67ee | ||
|
|
b533cf8480 | ||
|
|
51fd3af446 | ||
|
|
f3c3fea228 | ||
|
|
29574a6385 | ||
|
|
5c96b8d848 | ||
|
|
2c38fac9b1 | ||
|
|
0b88415247 | ||
|
|
9b25845258 | ||
|
|
49973ff32b | ||
|
|
714f63d0d3 |
@@ -1,4 +1,4 @@
|
||||
{
|
||||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
25
.github/workflows/font.yml
vendored
@@ -2,21 +2,31 @@ name: Build Lucide
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
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: Update repos
|
||||
run: sudo apt-get update
|
||||
|
||||
- name: Install FontForge
|
||||
run: sudo apt-get install zlib1g-dev fontforge woff2
|
||||
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
|
||||
run: git clone https://github.com/bramstein/sfnt2woff-zopfli.git sfnt2woff-zopfli
|
||||
|
||||
|
||||
- name: Install and move sfnt2woff-zopfli
|
||||
run: |
|
||||
cd sfnt2woff-zopfli
|
||||
@@ -26,6 +36,7 @@ jobs:
|
||||
- name: Clone woff2
|
||||
run: git clone --recursive https://github.com/google/woff2.git
|
||||
|
||||
|
||||
- name: Install woff2
|
||||
run: |
|
||||
cd woff2
|
||||
@@ -34,12 +45,20 @@ jobs:
|
||||
|
||||
- name: Install Font Custom dependency
|
||||
run: sudo gem install fontcustom
|
||||
|
||||
- name: Install "outline-stroke"
|
||||
run: sudo yarn add svg-outline-stroke svgson
|
||||
|
||||
- name: "Outline SVG"
|
||||
run: mkdir converted_icons && node scripts/outline_svg.js
|
||||
|
||||
|
||||
- name: Build 'Lucide'
|
||||
run: echo "Building Featherity font" && fontcustom compile ./icons -h -n Featherity -o build -F
|
||||
run: echo "Building Lucide font" && fontcustom compile ./converted_icons -h -n Lucide -o build -F
|
||||
|
||||
|
||||
- name: Zip 'Lucide'
|
||||
run: zip -r Featherity.zip build
|
||||
run: zip -r Lucide.zip build
|
||||
|
||||
- name: 'Upload to Artifacts'
|
||||
uses: actions/upload-artifact@v1.0.0
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
|
||||
|
||||
The following is a set of guidelines for contributing to Featherity. Feel free to propose changes to this document in a pull request.
|
||||
The following is a set of guidelines for contributing to Lucide. Feel free to propose changes to this document in a pull request.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
@@ -22,3 +22,6 @@ 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/lucide-icons/lucide/issues/new?title=Icon%20Request:) with a title of `Icon request: <icon name>` and add as much information as possible.
|
||||
|
||||
## Icon Requests from Feather
|
||||
If you are a designer who wants to contribute to Lucide but you don't know what icons to work on, then have a look at the Requests from Feather. All open, unfinished and valid requests can be found in [Feather Icon Requests](https://github.com/lucide-icons/lucide/issues/119).
|
||||
|
||||
17
README.md
@@ -1,3 +1,5 @@
|
||||
<p align=center><img width="410" src="https://lucide.netlify.app/logo-text.svg" alt="Lucide Logo"></p>
|
||||
|
||||
# Lucide
|
||||
|
||||

|
||||
@@ -10,9 +12,18 @@ Lucide is a community-run fork of [Feather Icons](https://github.com/feathericon
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Installation](#installation)
|
||||
* [Package managers](#package-managers)
|
||||
* [CDN](#cdn)
|
||||
* [Usage](#usage)
|
||||
* [Unpkg](#with-unpkg)
|
||||
* [ESModules](#with-esmodules)
|
||||
* [Options](#additional-options)
|
||||
* [Treeshake library](#treeshake-the-library-only-use-the-icons-you-use)
|
||||
* [Custom binding](#custom-element-binding)
|
||||
* [Figma](#figma)
|
||||
* [Contributing](#contributing)
|
||||
* [Community](#community)
|
||||
* [License](#license)
|
||||
|
||||
## Installation
|
||||
@@ -62,7 +73,7 @@ Here is a complete example with unpkg
|
||||
|
||||
### With ESModules
|
||||
|
||||
To reduce bundle size, lucide is build to be fully threeshakeble.
|
||||
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
|
||||
@@ -73,7 +84,7 @@ The `createIcons` function will search for HTMLElements with the attribute `icon
|
||||
```js
|
||||
import { createIcons, icons } from 'lucide';
|
||||
|
||||
// Caustion, this will import all the icons and bundle them.
|
||||
// Caution, this will import all the icons and bundle them.
|
||||
createIcons({icons});
|
||||
|
||||
// Recommended way, to include only the icons you need.
|
||||
@@ -107,7 +118,7 @@ createIcons({
|
||||
});
|
||||
```
|
||||
|
||||
#### Threeshake the library, only use the icons you use
|
||||
#### Treeshake the library, only use the icons you use
|
||||
|
||||
```js
|
||||
import { createIcons, Menu, ArrowRight, Globe } from 'lucide';
|
||||
|
||||
19
categories.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"arrows": [],
|
||||
"brands": [],
|
||||
"code": [],
|
||||
"connectivity": ["airplay"],
|
||||
"cursors": [],
|
||||
"development": [],
|
||||
"devices": ["alarm-clock"],
|
||||
"file-system": [],
|
||||
"layout": [],
|
||||
"maths": ["activity"],
|
||||
"multimedia": [],
|
||||
"notifications": ["alert-circle", "alert-octagon", "alert-triangle"],
|
||||
"nature": [],
|
||||
"shopping": [],
|
||||
"shapes": [],
|
||||
"sports": [],
|
||||
"text-edit": ["align-center","align-right","align-left","align-justify" ]
|
||||
}
|
||||
78
docs/ICON_DESIGN_GUIDE.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Icon Design Guide
|
||||
|
||||
Here are rules that should be followed to keep quality and consistency when making icons for Lucide.
|
||||
|
||||
## Summary of the rules we have
|
||||
|
||||
1. Icons must be designed on a **24 by 24 pixels** canvas.
|
||||
2. Icons must have a **1 pixel padding** within the canvas.
|
||||
3. Icons must have a **stroke width of 2 pixels**.
|
||||
4. Icons must use **round joins**.
|
||||
5. Icons must use **round caps**.
|
||||
6. Icons must use **centered strokes**.
|
||||
7. Shapes (such as rectangles) in icons must have **border radius of 2 pixels**.
|
||||
8. Distinct elements must have **2 pixels of spacing between each other**.
|
||||
|
||||
## The Rules Visualized
|
||||
|
||||
### 1. Icons must be designed on a 24 by 24 pixels canvas.
|
||||
|
||||

|
||||
|
||||
### 2. Icons must have a 1 pixel padding within the canvas.
|
||||
|
||||

|
||||
|
||||
### 3. Icons must have a stroke width of 2 pixels.
|
||||
|
||||

|
||||
|
||||
### 4. Icons must use round joins.
|
||||
|
||||

|
||||
|
||||
### 5. Icons must use round caps.
|
||||
|
||||

|
||||
|
||||
### 6. Icons must use centered strokes.
|
||||
|
||||

|
||||
|
||||
### 7. Shapes (such as squares) in icons must have border radius of 2 pixels.
|
||||
|
||||

|
||||
|
||||
### 8. Distinct elements must have 2 pixels of spacing between each other.
|
||||
|
||||

|
||||
|
||||
## Code Conventions
|
||||
|
||||
Before an icon is added to the library, we like to have readable and optimized svg code.
|
||||
|
||||
### Global Attributes
|
||||
|
||||
For each icon these attributes are applied, corresponding to the above rules.
|
||||
|
||||
```xml
|
||||
<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"
|
||||
>
|
||||
<!-- SVGElements -->
|
||||
</svg>
|
||||
```
|
||||
|
||||
### Minify paths
|
||||
|
||||
Code of paths can get really big.
|
||||
To reduce file size we like to minify the code.
|
||||
We recommend to use the [SVGOMG](https://jakearchibald.github.io/svgomg/) to minify paths.
|
||||
BIN
docs/images/1px-padding.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/images/24px-24px.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
docs/images/2px-border-radius.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/images/2px-element-spacing.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
docs/images/2px-stroke.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/images/centered-strokes.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/images/round-caps.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
docs/images/round-joints.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
14
icons/album.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"
|
||||
>
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
|
||||
<polyline points="11 3 11 11 14 8 17 11 17 3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 319 B |
@@ -9,8 +9,7 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="18" y1="10" x2="6" y2="10" />
|
||||
<line x1="21" y1="6" x2="3" y2="6" />
|
||||
<line x1="21" y1="14" x2="3" y2="14" />
|
||||
<line x1="18" y1="18" x2="6" y2="18" />
|
||||
<line x1="17" y1="12" x2="7" y2="12" />
|
||||
<line x1="19" y1="18" x2="5" y2="18" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 374 B After Width: | Height: | Size: 332 B |
@@ -9,8 +9,7 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="21" y1="10" x2="3" y2="10" />
|
||||
<line x1="21" y1="6" x2="3" y2="6" />
|
||||
<line x1="21" y1="14" x2="3" y2="14" />
|
||||
<line x1="21" y1="18" x2="3" y2="18" />
|
||||
<line x1="3" y1="6" x2="21" y2="6" />
|
||||
<line x1="3" y1="12" x2="21" y2="12" />
|
||||
<line x1="3" y1="18" x2="21" y2="18" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 374 B After Width: | Height: | Size: 332 B |
@@ -9,8 +9,7 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="17" y1="10" x2="3" y2="10" />
|
||||
<line x1="21" y1="6" x2="3" y2="6" />
|
||||
<line x1="21" y1="14" x2="3" y2="14" />
|
||||
<line x1="15" y1="12" x2="3" y2="12" />
|
||||
<line x1="17" y1="18" x2="3" y2="18" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 374 B After Width: | Height: | Size: 332 B |
@@ -9,8 +9,7 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="21" y1="10" x2="7" y2="10" />
|
||||
<line x1="21" y1="6" x2="3" y2="6" />
|
||||
<line x1="21" y1="14" x2="3" y2="14" />
|
||||
<line x1="21" y1="12" x2="9" y2="12" />
|
||||
<line x1="21" y1="18" x2="7" y2="18" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 374 B After Width: | Height: | Size: 332 B |
14
icons/axe.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="m14 12-8.501 8.501c-0.828 0.828-2.17 0.828-2.998 0-1e-3 -1e-3 -1e-3 -1e-3 -2e-3 -0-0.828-0.828-0.828-2.17 0-2.998l8.501-8.501" />
|
||||
<path d="m9 7 4-4 6 6h3s-0.051 0.254-0.13 0.648c-0.538 2.691-2.477 4.888-5.081 5.756-1.003 0.334-1.789 0.596-1.789 0.596v-3z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 480 B |
15
icons/beaker.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="M4.5 3h15" />
|
||||
<path d="M6 3v16a2 2 0 002 2h8a2 2 0 002-2V3" />
|
||||
<path d="M6 14h12" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 308 B |
@@ -10,6 +10,6 @@
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="2" y1="2" x2="22" y2="22" />
|
||||
<path d="M9.5 4h5L17 7h3a2 2 0 012 2v7.5M7 7H4a2 2 0 00-2 2v9a2 2 0 002 2h16"/>
|
||||
<path d="M14.121 15.121A3 3 0 119.88 10.88"/>
|
||||
<path d="M9.5 4h5L17 7h3a2 2 0 012 2v7.5M7 7H4a2 2 0 00-2 2v9a2 2 0 002 2h16" />
|
||||
<path d="M14.121 15.121A3 3 0 119.88 10.88" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 379 B After Width: | Height: | Size: 381 B |
17
icons/clover.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"
|
||||
>
|
||||
<path d="M16.2 3.8a2.7 2.7 0 00-3.81 0l-.4.38-.4-.4a2.7 2.7 0 00-3.82 0C6.73 4.85 6.67 6.64 8 8l4 4 4-4c1.33-1.36 1.27-3.15.2-4.2z" />
|
||||
<path d="M8 8c-1.36-1.33-3.15-1.27-4.2-.2a2.7 2.7 0 000 3.81l.38.4-.4.4a2.7 2.7 0 000 3.82C4.85 17.27 6.64 17.33 8 16" />
|
||||
<path d="M16 16c1.36 1.33 3.15 1.27 4.2.2a2.7 2.7 0 000-3.81l-.38-.4.4-.4a2.7 2.7 0 000-3.82C19.15 6.73 17.36 6.67 16 8" />
|
||||
<path d="M7.8 20.2a2.7 2.7 0 003.81 0l.4-.38.4.4a2.7 2.7 0 003.82 0c1.06-1.06 1.12-2.85-.21-4.21l-4-4-4 4c-1.33 1.36-1.27 3.15-.2 4.2z" />
|
||||
<path d="M7 17L2 22" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 762 B |
15
icons/file-check-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"
|
||||
>
|
||||
<path d="M4 22h14a2 2 0 002-2V7.5L14.5 2H6a2 2 0 00-2 2v4" />
|
||||
<path d="M14 2v6h6" />
|
||||
<path d="M3 15l2 2 4-4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 326 B |
15
icons/file-check.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="M14.5 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V7.5L14.5 2z" />
|
||||
<polyline points="14 2 14 8 20 8" />
|
||||
<path d="M9 15l2 2 4-4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 356 B |
16
icons/file-code.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="M4 22h14a2 2 0 002-2V7.5L14.5 2H6a2 2 0 00-2 2v4" />
|
||||
<path d="M14 2v6h6" />
|
||||
<path d="M9 18l3-3-3-3" />
|
||||
<path d="M5 12l-3 3 3 3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 356 B |
17
icons/file-digit.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"
|
||||
>
|
||||
<path d="M4 22h14a2 2 0 002-2V7.5L14.5 2H6a2 2 0 00-2 2v4" />
|
||||
<path d="M14 2v6h6" />
|
||||
<path d="M10 12h2v6" />
|
||||
<rect x="2" y="12" width="4" height="6" />
|
||||
<path d="M10 18h4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 392 B |
15
icons/file-minus-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"
|
||||
>
|
||||
<path d="M4 22h14a2 2 0 002-2V7.5L14.5 2H6a2 2 0 00-2 2v4" />
|
||||
<path d="M14 2v6h6" />
|
||||
<path d="M3 15h6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 320 B |
@@ -9,7 +9,7 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||
<path d="M14.5 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V7.5L14.5 2z" />
|
||||
<polyline points="14 2 14 8 20 8" />
|
||||
<line x1="9" y1="15" x2="15" y2="15" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 363 B After Width: | Height: | Size: 369 B |
16
icons/file-plus-2.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="M4 22h14a2 2 0 002-2V7.5L14.5 2H6a2 2 0 00-2 2v4" />
|
||||
<path d="M14 2v6h6" />
|
||||
<path d="M3 15h6" />
|
||||
<path d="M6 12v6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 343 B |
@@ -9,7 +9,7 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||
<path d="M14.5 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V7.5L14.5 2z" />
|
||||
<polyline points="14 2 14 8 20 8" />
|
||||
<line x1="12" y1="18" x2="12" y2="12" />
|
||||
<line x1="9" y1="15" x2="15" y2="15" />
|
||||
|
||||
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 412 B |
16
icons/file-search.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="M4 22h14a2 2 0 002-2V7.5L14.5 2H6a2 2 0 00-2 2v3" />
|
||||
<path d="M14 2v6h6" />
|
||||
<path d="M5 17a3 3 0 100-6 3 3 0 000 6z" />
|
||||
<path d="M9 18l-1.5-1.5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 373 B |
@@ -9,9 +9,9 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||
<path d="M14.5 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V7.5L14.5 2z" />
|
||||
<polyline points="14 2 14 8 20 8" />
|
||||
<line x1="16" y1="13" x2="8" y2="13" />
|
||||
<line x1="16" y1="17" x2="8" y2="17" />
|
||||
<polyline points="10 9 9 9 8 9" />
|
||||
<line x1="10" y1="9" x2="8" y2="9" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 451 B |
16
icons/file-x-2.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="M4 22h14a2 2 0 002-2V7.5L14.5 2H6a2 2 0 00-2 2v4" />
|
||||
<path d="M14 2v6h6" />
|
||||
<path d="M3 12.5l5 5" />
|
||||
<path d="M8 12.5l-5 5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 352 B |
16
icons/file-x.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="M14.5 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V7.5L14.5 2z" />
|
||||
<polyline points="14 2 14 8 20 8" />
|
||||
<line x1="9.5" y1="12.5" x2="14.5" y2="17.5" />
|
||||
<line x1="14.5" y1="12.5" x2="9.5" y2="17.5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 427 B |
@@ -9,6 +9,6 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z" />
|
||||
<polyline points="13 2 13 9 20 9" />
|
||||
<path d="M14.5 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V7.5L14.5 2z" />
|
||||
<polyline points="14 2 14 8 20 8" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 327 B |
15
icons/files.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="M15.5 2H8.6c-.4 0-.8.2-1.1.5-.3.3-.5.7-.5 1.1v12.8c0 .4.2.8.5 1.1.3.3.7.5 1.1.5h9.8c.4 0 .8-.2 1.1-.5.3-.3.5-.7.5-1.1V6.5L15.5 2z" />
|
||||
<path d="M3 7.6v12.8c0 .4.2.8.5 1.1.3.3.7.5 1.1.5h9.8" />
|
||||
<path d="M15 2v5h5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 438 B |
15
icons/flask-conical.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="M10 2v8L4.72 20.55a1 1 0 00.9 1.45h12.76a1 1 0 00.9-1.45L14 10V2" />
|
||||
<path d="M8.5 2h7" />
|
||||
<path d="M7 16h10" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 336 B |
17
icons/flask-round.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"
|
||||
>
|
||||
<path d="M10 2v7.31" />
|
||||
<path d="M14 9.3V1.99" />
|
||||
<path d="M8.5 2h7" />
|
||||
<path d="M14 9.3a6.5 6.5 0 11-4 0" />
|
||||
<path d="M5.58 16.5h12.85" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 358 B |
@@ -9,6 +9,6 @@
|
||||
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"/>
|
||||
<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>
|
||||
|
||||
|
Before Width: | Height: | Size: 326 B After Width: | Height: | Size: 328 B |
17
icons/gavel.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"
|
||||
>
|
||||
<path d="M14 13l-7.5 7.5c-.83.83-2.17.83-3 0 0 0 0 0 0 0a2.12 2.12 0 010-3L11 10" />
|
||||
<path d="M16 16l6-6" />
|
||||
<path d="M8 8l6-6" />
|
||||
<path d="M9 7l8 8" />
|
||||
<path d="M21 11l-8-8" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 396 B |
15
icons/hammer.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="M15 12l-8.5 8.5c-.83.83-2.17.83-3 0 0 0 0 0 0 0a2.12 2.12 0 010-3L12 9" />
|
||||
<path d="M17.64 15L22 10.64" />
|
||||
<path d="M20.91 11.7l-1.25-1.25c-.6-.6-.93-1.4-.93-2.25v-.86L16.01 4.6a5.56 5.56 0 00-3.94-1.64H9l.92.82A6.18 6.18 0 0112 8.4v1.56l2 2h2.47l2.26 1.91" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 484 B |
16
icons/hard-hat.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 18a1 1 0 001 1h18a1 1 0 001-1v-2a1 1 0 00-1-1H3a1 1 0 00-1 1v2z" />
|
||||
<path d="M10 10V5a1 1 0 011-1h2a1 1 0 011 1v5" />
|
||||
<path d="M4 15v-3a6 6 0 016-6h0" />
|
||||
<path d="M14 6h0a6 6 0 016 6v3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 417 B |
@@ -9,6 +9,6 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M9 11l-6 6v3h9l3-3"/>
|
||||
<path d="M22 12l-4.6 4.6a2 2 0 01-2.8 0l-5.2-5.2a2 2 0 010-2.8L14 4"/>
|
||||
<path d="M9 11l-6 6v3h9l3-3" />
|
||||
<path d="M22 12l-4.6 4.6a2 2 0 01-2.8 0l-5.2-5.2a2 2 0 010-2.8L14 4" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 314 B After Width: | Height: | Size: 316 B |
16
icons/indent.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="3 8 7 12 3 16" />
|
||||
<line x1="21" y1="12" x2="11" y2="12" />
|
||||
<line x1="21" y1="6" x2="11" y2="6" />
|
||||
<line x1="21" y1="18" x2="11" y2="18" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 373 B |
@@ -9,7 +9,7 @@
|
||||
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" />
|
||||
<line x1="4" y1="12" x2="20" y2="12" />
|
||||
<line x1="4" y1="6" x2="20" y2="6" />
|
||||
<line x1="4" y1="18" x2="20" y2="18" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 332 B After Width: | Height: | Size: 332 B |
16
icons/outdent.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="7 8 3 12 7 16" />
|
||||
<line x1="21" y1="12" x2="11" y2="12" />
|
||||
<line x1="21" y1="6" x2="11" y2="6" />
|
||||
<line x1="21" y1="18" x2="11" y2="18" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 373 B |
15
icons/pipette.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="M2 22l2-2h3l7-7" />
|
||||
<path d="M4 20v-3l7-7" />
|
||||
<path d="M14.29 13.3a1 1 0 001.41 0l.8-.8c.27-.27.27-.72 0-1s-.28-.72 0-1l4.08-4.08a2 2 0 000-2.83l-.17-.17a2 2 0 00-2.83 0L13.5 7.51c-.28.27-.73.27-1 0s-.73-.28-1 0l-.8.79a1 1 0 000 1.41l3.59 3.59z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 472 B |
17
icons/ruler.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"
|
||||
>
|
||||
<path d="m16.0 2.0 6 6-14 14-6-6 14-14" />
|
||||
<path d="m7.5 10.5 2 2" />
|
||||
<path d="m10.5 7.5 2 2" />
|
||||
<path d="m13.5 4.5 2 2" />
|
||||
<path d="m4.5 13.5 2 2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 379 B |
15
icons/shovel.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="M2 22v-5l5-5 5 5-5 5z" />
|
||||
<path d="M9.5 14.5L16 8" />
|
||||
<path d="M17 2l5 5-.5.5a3.53 3.53 0 01-5 0s0 0 0 0a3.53 3.53 0 010-5L17 2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 376 B After Width: | Height: | Size: 376 B |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "lucide",
|
||||
"description": "Lucide is a community-run fork of Feather Icons, open for anyone to contribute icons.",
|
||||
"version": "0.1.2",
|
||||
"version": "0.11.0",
|
||||
"license": "ISC",
|
||||
"amdName": "lucide",
|
||||
"homepage": "https://lucide.netlify.app",
|
||||
|
||||
55
scripts/outline_svg.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const { promises: fs } = require("fs");
|
||||
const outlineStroke = require("svg-outline-stroke");
|
||||
const { parse, stringify } = require("svgson");
|
||||
|
||||
const inputDir = `./icons/`;
|
||||
const outputDir = `./converted_icons/`;
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
const files = await fs.readdir(inputDir);
|
||||
for (let file of files) {
|
||||
const icon = await fs.readFile(`${inputDir}${file}`);
|
||||
const scaled = await parse(icon.toString(), {
|
||||
transformNode: transformForward
|
||||
});
|
||||
const outlined = await outlineStroke(stringify(scaled));
|
||||
const outlinedWithoutAttrs = await parse(outlined, {
|
||||
transformNode: transformBackwards
|
||||
});
|
||||
await fs.writeFile(
|
||||
`${outputDir}${file}`,
|
||||
stringify(outlinedWithoutAttrs)
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
function transformForward(node) {
|
||||
if (node.name === "svg") {
|
||||
return {
|
||||
...node,
|
||||
attributes: {
|
||||
...node.attributes,
|
||||
width: 960,
|
||||
height: 960
|
||||
}
|
||||
};
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
function transformBackwards(node) {
|
||||
if (node.name === "svg") {
|
||||
const { width, height, ...attributes } = node.attributes;
|
||||
return {
|
||||
...node,
|
||||
attributes
|
||||
};
|
||||
}
|
||||
return node;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"presets": ["next/babel"]
|
||||
}
|
||||
4
site/.eslintrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
const { builtinModules } = require('module')
|
||||
const rootConfig = require('../.eslintrc.js')
|
||||
|
||||
module.exports = rootConfig;
|
||||
3
site/babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: ['next/babel'],
|
||||
};
|
||||
@@ -2,6 +2,6 @@ module.exports = {
|
||||
testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules/'],
|
||||
setupFilesAfterEnv: ['<rootDir>/setupTests.js'],
|
||||
transform: {
|
||||
'^.+\\.(js|jsx|ts|tsx)$': '<rootDir>/../../node_modules/babel-jest',
|
||||
'^.+\\.(js|jsx|ts|tsx)$': '<rootDir>/node_modules/babel-jest',
|
||||
},
|
||||
};
|
||||
|
||||
5
site/next-env.d.ts
vendored
@@ -1,2 +1,7 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
|
||||
declare module "*.svg" {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
|
||||
@@ -11,16 +11,19 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/core": "^0.8.0",
|
||||
"@emotion/core": "^10.0.28",
|
||||
"@emotion/styled": "^10.0.27",
|
||||
"@chakra-ui/core": "^1.0.0-rc.8",
|
||||
"downloadjs": "^1.4.7",
|
||||
"emotion-theming": "^10.0.27",
|
||||
"framer-motion": "^2.9.4",
|
||||
"fuse.js": "^6.0.4",
|
||||
"jszip": "^3.4.0",
|
||||
"lodash": "^4.17.20",
|
||||
"lucide-react": "^0.1.2-beta.4",
|
||||
"next": "^9.5.4",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1"
|
||||
"react-color": "2.17.3",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-svg-loader": "^3.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/dom": "^7.24.4",
|
||||
@@ -31,6 +34,8 @@
|
||||
"@types/react": "^16.9.35",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"babel-jest": "^26.5.2",
|
||||
"babel-loader": "^8.1.0",
|
||||
"cheerio": "^1.0.0-rc.3",
|
||||
"jest": "^26.5.2",
|
||||
"react-test-renderer": "^16.13.1",
|
||||
"typescript": "^3.9.5"
|
||||
|
||||
BIN
site/public/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
site/public/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
site/public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
8
site/public/browserconfig.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<TileColor>#F56565</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
BIN
site/public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
site/public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
site/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
24
site/public/favicon.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"
|
||||
>
|
||||
<style>
|
||||
.path {
|
||||
stroke: #2D3748;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.path {
|
||||
stroke: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<path d="M14 12C14 9.79086 12.2091 8 10 8C7.79086 8 6 9.79086 6 12C6 16.4183 9.58172 20 14 20C18.4183 20 22 16.4183 22 12C22 8.446 20.455 5.25285 18 3.05557" class="path"/>
|
||||
<path d="M10 12C10 14.2091 11.7909 16 14 16C16.2091 16 18 14.2091 18 12C18 7.58172 14.4183 4 10 4C5.58172 4 2 7.58172 2 12C2 15.5841 3.57127 18.8012 6.06253 21" stroke="#F56565" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 720 B |
5
site/public/logo-text.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="82" height="24" viewBox="0 0 82 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M28.52 5.4h2.14v10.84h5.28V18h-7.42V5.4zm15.24 3.16h2.06V18h-1.58l-.26-1.14h-.08a5.26 5.26 0 01-1.26.94c-.507.267-1.12.4-1.84.4-1 0-1.8-.28-2.4-.84-.6-.56-.9-1.4-.9-2.52V8.56h2.06v6.06c0 .733.166 1.247.5 1.54.346.293.76.44 1.24.44.52 0 .98-.107 1.38-.32.4-.227.76-.52 1.08-.88V8.56zm9.297 8.08c.427 0 .8-.047 1.12-.14.334-.107.633-.227.9-.36l.42 1.48a4.7 4.7 0 01-1.16.42 6.55 6.55 0 01-1.5.16c-.693 0-1.347-.1-1.96-.3a4.782 4.782 0 01-1.58-.92 4.593 4.593 0 01-1.06-1.54c-.253-.613-.38-1.327-.38-2.14 0-.747.12-1.42.36-2.02.24-.613.58-1.133 1.02-1.56.44-.427.967-.753 1.58-.98a5.786 5.786 0 012.02-.34c.413 0 .827.033 1.24.1.427.067.894.22 1.4.46l-.46 1.5a3.995 3.995 0 00-1.96-.52c-.92 0-1.666.287-2.24.86-.573.573-.86 1.387-.86 2.44 0 .64.087 1.18.26 1.62.187.427.427.773.72 1.04.294.267.62.46.98.58.373.107.753.16 1.14.16zM57.211 18V8.56h2.06V18h-2.06zm1.04-11.78c-.36 0-.646-.107-.86-.32a1.18 1.18 0 01-.32-.84c0-.333.107-.607.32-.82.214-.227.5-.34.86-.34.347 0 .627.113.84.34.227.213.34.487.34.82 0 .347-.113.627-.34.84-.213.213-.493.32-.84.32zM68.33 3.78h2.06V18h-1.62l-.24-1.04h-.08c-.347.4-.734.707-1.16.92-.427.213-.98.32-1.66.32-.534 0-1.047-.1-1.54-.3a3.997 3.997 0 01-1.3-.94c-.374-.413-.674-.927-.9-1.54-.227-.613-.34-1.32-.34-2.12 0-.747.093-1.42.28-2.02.2-.6.48-1.113.84-1.54a3.71 3.71 0 011.36-.98c.533-.24 1.14-.36 1.82-.36.466 0 .92.073 1.36.22.44.147.813.353 1.12.62V3.78zm0 7.12c-.56-.64-1.234-.96-2.02-.96-.347 0-.68.06-1 .18-.32.12-.607.313-.86.58a2.834 2.834 0 00-.6 1.02c-.147.413-.22.92-.22 1.52s.066 1.113.2 1.54c.146.427.333.78.56 1.06.226.28.486.487.78.62.306.12.62.18.94.18.48 0 .9-.107 1.26-.32a2.93 2.93 0 00.96-.9V10.9zm12.858 2.74h-6.62v.08c0 .96.28 1.687.84 2.18.56.493 1.3.74 2.22.74.507 0 .96-.04 1.36-.12a6.47 6.47 0 001.24-.42l.4 1.5a6.51 6.51 0 01-1.38.42c-.533.12-1.12.18-1.76.18-.68 0-1.327-.093-1.94-.28a4.449 4.449 0 01-1.62-.88 4.253 4.253 0 01-1.08-1.52c-.267-.613-.4-1.347-.4-2.2 0-.733.107-1.4.32-2a4.56 4.56 0 01.94-1.56c.413-.44.907-.773 1.48-1a5.077 5.077 0 011.94-.36c.613 0 1.167.1 1.66.3.507.2.933.487 1.28.86.36.36.633.807.82 1.34.2.52.3 1.1.3 1.74v1zm-2.12-1.44c0-.32-.04-.62-.12-.9-.08-.28-.2-.52-.36-.72a1.605 1.605 0 00-.64-.5c-.253-.133-.56-.2-.92-.2-.667 0-1.213.213-1.64.64-.413.427-.667 1-.76 1.72l4.44-.04z" fill="#2D3748"/>
|
||||
<path d="M14 12a4 4 0 00-8 0 8 8 0 1016 0 11.97 11.97 0 00-4-8.944" stroke="#2D3748" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 12a4 4 0 008 0 8 8 0 10-16 0c0 3.584 1.571 6.801 4.063 9" stroke="#F56565" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
14
site/public/logo.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="M14 12C14 9.79086 12.2091 8 10 8C7.79086 8 6 9.79086 6 12C6 16.4183 9.58172 20 14 20C18.4183 20 22 16.4183 22 12C22 8.446 20.455 5.25285 18 3.05557" />
|
||||
<path d="M10 12C10 14.2091 11.7909 16 14 16C16.2091 16 18 14.2091 18 12C18 7.58172 14.4183 4 10 4C5.58172 4 2 7.58172 2 12C2 15.5841 3.57127 18.8012 6.06253 21" stroke="#F56565" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 554 B |
BIN
site/public/mstile-144x144.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
site/public/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
site/public/mstile-310x150.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
site/public/mstile-310x310.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
site/public/mstile-70x70.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
53
site/public/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M5152 6381 c-123 -43 -199 -158 -189 -291 6 -96 34 -139 193 -297 77
|
||||
-76 165 -167 195 -202 304 -358 520 -760 649 -1206 37 -129 34 -113 59 -240
|
||||
27 -135 30 -152 37 -215 3 -30 8 -63 9 -72 7 -35 18 -247 19 -343 0 -163 -15
|
||||
-305 -48 -463 -112 -529 -490 -1043 -971 -1320 -209 -120 -503 -220 -755 -257
|
||||
-95 -14 -434 -13 -540 2 -371 51 -728 207 -1015 442 -127 104 -227 207 -356
|
||||
368 -40 51 -140 218 -189 318 -174 352 -252 834 -181 1110 87 332 363 590 692
|
||||
644 259 43 488 -13 691 -169 150 -115 278 -312 314 -481 18 -89 23 -122 25
|
||||
-184 4 -123 45 -208 129 -263 60 -40 108 -54 177 -51 127 5 235 93 268 217 15
|
||||
58 10 207 -10 313 -42 217 -113 397 -222 562 -48 73 -61 89 -123 161 -233 267
|
||||
-569 443 -930 485 -81 9 -285 7 -365 -4 -142 -19 -317 -75 -458 -146 -250
|
||||
-126 -488 -355 -620 -599 -69 -128 -139 -322 -153 -430 -3 -19 -9 -57 -14 -85
|
||||
-11 -62 -10 -370 2 -456 4 -35 11 -82 14 -104 25 -190 86 -416 163 -609 38
|
||||
-94 137 -294 157 -317 8 -8 14 -19 14 -24 0 -13 75 -128 153 -234 195 -265
|
||||
452 -500 747 -683 119 -73 272 -149 405 -201 83 -32 266 -92 311 -102 10 -2
|
||||
53 -11 94 -20 280 -60 629 -76 873 -40 18 3 48 7 67 9 19 2 46 7 60 10 14 3
|
||||
38 8 54 11 70 11 144 27 181 40 22 7 81 26 130 41 296 90 656 292 905 508 97
|
||||
85 272 267 340 356 19 25 37 47 40 50 19 19 125 180 180 274 150 261 266 585
|
||||
310 866 6 41 13 86 16 100 25 156 25 635 -1 769 -1 9 -6 39 -9 66 -11 85 -36
|
||||
227 -52 300 -9 39 -18 79 -20 90 -8 43 -77 283 -96 335 -33 93 -60 164 -73
|
||||
195 -7 17 -25 59 -40 95 -187 448 -511 905 -903 1272 -122 114 -216 142 -340
|
||||
99z"/>
|
||||
<path d="M2827 6125 c-1 -2 -48 -5 -104 -9 -424 -25 -871 -176 -1243 -420
|
||||
-313 -205 -583 -481 -787 -801 -56 -88 -159 -284 -187 -355 -84 -214 -152
|
||||
-434 -170 -555 -9 -63 -18 -121 -22 -138 -19 -102 -25 -468 -11 -642 39 -489
|
||||
174 -973 393 -1408 35 -70 75 -145 89 -167 14 -22 25 -42 25 -45 0 -12 175
|
||||
-273 247 -367 153 -203 408 -476 533 -572 115 -88 269 -80 378 18 58 53 86
|
||||
123 86 216 1 108 -18 137 -204 320 -267 264 -444 500 -613 820 -135 257 -250
|
||||
584 -302 865 -10 50 -19 97 -20 106 -2 9 -6 38 -9 65 -4 27 -9 65 -11 84 -23
|
||||
182 -24 552 0 640 2 8 6 36 10 63 18 148 101 399 190 577 50 99 171 290 225
|
||||
354 134 160 252 272 395 376 263 190 558 314 870 364 22 4 56 9 75 13 76 13
|
||||
435 11 520 -3 47 -7 101 -16 120 -19 19 -3 42 -7 50 -10 8 -2 26 -6 40 -9 115
|
||||
-22 309 -94 460 -172 612 -313 1029 -926 1098 -1614 9 -91 8 -306 -2 -355 -77
|
||||
-373 -352 -642 -726 -711 -65 -11 -254 -5 -323 11 -132 30 -233 78 -342 161
|
||||
-160 121 -283 307 -321 485 -15 74 -23 133 -27 205 -3 61 -11 94 -30 132 -122
|
||||
243 -482 202 -542 -62 -13 -60 -8 -215 10 -306 2 -8 6 -31 10 -50 23 -129 75
|
||||
-268 160 -430 20 -38 89 -140 120 -179 247 -304 569 -489 950 -545 84 -12 335
|
||||
-13 395 -2 14 3 40 8 59 10 133 20 324 90 461 168 384 219 646 592 720 1023
|
||||
20 118 25 385 10 500 -5 33 -11 85 -15 115 -15 121 -58 307 -105 449 -286 874
|
||||
-1026 1545 -1920 1742 -126 28 -119 26 -181 34 -30 4 -65 9 -79 11 -90 12
|
||||
-393 26 -403 19z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
19
site/public/site.webmanifest
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
19
site/src/assets/styling.css
Normal file
@@ -0,0 +1,19 @@
|
||||
.icon-large svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.icon-grid {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100%;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
77
site/src/components/ColorPicker.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { SyntheticEvent, useEffect, useRef, useState } from 'react';
|
||||
import { FormLabel, Icon, Input, InputGroup, InputLeftElement } from '@chakra-ui/core';
|
||||
import { CustomPicker } from 'react-color';
|
||||
|
||||
const { Saturation, Hue } = require('react-color/lib/components/common');
|
||||
|
||||
type ColorPickerProps = {
|
||||
value: string;
|
||||
hex: string;
|
||||
hsl: string;
|
||||
hsv: string;
|
||||
onChange: (s: string, e: SyntheticEvent) => void;
|
||||
};
|
||||
|
||||
function ColorPicker({ hsv, hsl, onChange, value: color }: ColorPickerProps) {
|
||||
const [value, setValue] = useState(color);
|
||||
const input = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (color !== value && input.current !== document.activeElement) {
|
||||
setValue(color === 'currentColor' ? color : String(color).toUpperCase());
|
||||
}
|
||||
}, [color]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
let value = e.target.value;
|
||||
setValue(value);
|
||||
onChange(value, e);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormLabel htmlFor="color" fontWeight={'bold'}>
|
||||
Color
|
||||
</FormLabel>
|
||||
<InputGroup>
|
||||
<InputLeftElement
|
||||
children={
|
||||
<Icon>
|
||||
<rect x={0} width={24} y={0} height={24} fill={value} rx={2} />
|
||||
</Icon>
|
||||
}
|
||||
/>
|
||||
<Input value={value} name="color" onChange={handleChange} ref={input} />
|
||||
</InputGroup>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
paddingBottom: '75%',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
marginTop: '0.5rem',
|
||||
borderRadius: '0.375rem',
|
||||
border: '1px solid',
|
||||
borderColor: 'inherit',
|
||||
}}
|
||||
>
|
||||
<Saturation hsl={hsl} hsv={hsv} onChange={onChange} />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
minHeight: '2em',
|
||||
position: 'relative',
|
||||
margin: '0.5rem 0 0 0',
|
||||
borderRadius: '0.375rem',
|
||||
border: '1px solid',
|
||||
borderColor: 'inherit',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Hue hsl={hsl} onChange={onChange} direction={'horizontal'} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomPicker(ColorPicker);
|
||||
47
site/src/components/CustomizeIconContext.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { createContext, useState } from 'react';
|
||||
|
||||
interface ICustomIconStyle {
|
||||
color: string;
|
||||
setColor: (s: string) => void;
|
||||
strokeWidth: number;
|
||||
setStroke: (n: number) => void;
|
||||
size: number;
|
||||
setSize: (n: number) => void;
|
||||
resetStyle: () => void;
|
||||
}
|
||||
|
||||
const DEFAULT_STYLE = {
|
||||
color: 'currentColor',
|
||||
strokeWidth: 2,
|
||||
size: 24,
|
||||
}
|
||||
|
||||
export const IconStyleContext = createContext<ICustomIconStyle>({
|
||||
color: 'currentColor',
|
||||
setColor: (s: string) => null,
|
||||
strokeWidth: 2,
|
||||
setStroke: (n: number) => null,
|
||||
size: 24,
|
||||
setSize: (n: number) => null,
|
||||
resetStyle: ()=>null
|
||||
});
|
||||
|
||||
export function CustomizeIconContext({ children }) {
|
||||
const [color, setColor] = useState(DEFAULT_STYLE.color);
|
||||
const [stroke, setStroke] = useState(DEFAULT_STYLE.strokeWidth);
|
||||
const [size, setSize] = useState(DEFAULT_STYLE.size);
|
||||
|
||||
function resetStyle(){
|
||||
setColor(DEFAULT_STYLE.color);
|
||||
setStroke(DEFAULT_STYLE.strokeWidth);
|
||||
setSize(DEFAULT_STYLE.size);
|
||||
}
|
||||
|
||||
return (
|
||||
<IconStyleContext.Provider
|
||||
value={{ color, setColor, strokeWidth: stroke, setStroke, size, setSize, resetStyle }}
|
||||
>
|
||||
{children}
|
||||
</IconStyleContext.Provider>
|
||||
);
|
||||
}
|
||||
57
site/src/components/Header.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import {Button, Flex, Link, Stack, Text,} from "@chakra-ui/core";
|
||||
import download from "downloadjs";
|
||||
import JSZip from "jszip";
|
||||
import { Download, Github } from 'lucide-react';
|
||||
import {IconCustomizerDrawer} from "./IconCustomizerDrawer";
|
||||
|
||||
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 Header = ({ data }) => {
|
||||
const downloadAllIcons = async () => {
|
||||
|
||||
const zip = await generateZip(data);
|
||||
download(zip, 'feather.zip');
|
||||
};
|
||||
|
||||
const repositoryUrl = 'https://github.com/lucide-icons/lucide';
|
||||
|
||||
return (
|
||||
<Flex direction="column" align="center" justify="center">
|
||||
<Text fontSize="4xl" as="b" mb="4" textAlign="center">
|
||||
Simply beautiful open source icons, community-sourced
|
||||
</Text>
|
||||
<Text fontSize="lg" as="p" textAlign="center" mb="8">
|
||||
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>
|
||||
<Stack isInline marginTop={3} marginBottom={10}>
|
||||
<Button
|
||||
leftIcon={<Download/>}
|
||||
size="lg"
|
||||
onClick={downloadAllIcons}
|
||||
>
|
||||
Download all
|
||||
</Button>
|
||||
<IconCustomizerDrawer/>
|
||||
<Button
|
||||
as="a"
|
||||
leftIcon={<Github/>}
|
||||
size="lg"
|
||||
href={repositoryUrl}
|
||||
target="__blank"
|
||||
onClick={downloadAllIcons}
|
||||
>
|
||||
Github
|
||||
</Button>
|
||||
</Stack>
|
||||
</Flex>
|
||||
)
|
||||
};
|
||||
|
||||
export default Header;
|
||||
96
site/src/components/IconCustomizerDrawer.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import { useContext, useState } from 'react';
|
||||
import { IconStyleContext } from './CustomizeIconContext';
|
||||
import { Edit } from 'lucide-react';
|
||||
import {
|
||||
Button,
|
||||
Drawer,
|
||||
DrawerBody,
|
||||
DrawerCloseButton,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerOverlay,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Grid,
|
||||
Slider,
|
||||
SliderFilledTrack,
|
||||
SliderThumb,
|
||||
SliderTrack,
|
||||
Flex,
|
||||
Text,
|
||||
} from '@chakra-ui/core';
|
||||
import ColorPicker from './ColorPicker';
|
||||
|
||||
export function IconCustomizerDrawer() {
|
||||
const [showCustomize, setShowCustomize] = useState(false);
|
||||
const { color, setColor, size, setSize, strokeWidth, setStroke, resetStyle } = useContext(IconStyleContext);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button as="a" leftIcon={<Edit />} size="lg" onClick={() => setShowCustomize(true)}>
|
||||
Customize
|
||||
</Button>
|
||||
<Drawer isOpen={showCustomize} placement="right" onClose={() => setShowCustomize(false)}>
|
||||
<DrawerOverlay />
|
||||
<DrawerContent>
|
||||
<DrawerCloseButton />
|
||||
<DrawerHeader>Customize Icons</DrawerHeader>
|
||||
|
||||
<DrawerBody>
|
||||
<Grid gridGap={'1em'}>
|
||||
<FormControl>
|
||||
<ColorPicker
|
||||
color={color}
|
||||
value={color}
|
||||
onChangeComplete={(col) => setColor(col.hex)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="stroke">
|
||||
<Flex>
|
||||
<Text flexGrow={1} fontWeight={'bold'}>
|
||||
Stroke
|
||||
</Text>
|
||||
<Text>{strokeWidth}px</Text>
|
||||
</Flex>
|
||||
</FormLabel>
|
||||
<Slider
|
||||
value={strokeWidth}
|
||||
onChange={setStroke}
|
||||
min={0.5}
|
||||
max={3}
|
||||
step={0.5}
|
||||
name={'stroke'}
|
||||
>
|
||||
<SliderTrack>
|
||||
<SliderFilledTrack bg={color} />
|
||||
</SliderTrack>
|
||||
<SliderThumb />
|
||||
</Slider>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="size">
|
||||
<Flex>
|
||||
<Text flexGrow={1} fontWeight={'bold'}>
|
||||
Size
|
||||
</Text>
|
||||
<Text>{size}px</Text>
|
||||
</Flex>
|
||||
</FormLabel>
|
||||
<Slider value={size} onChange={setSize} min={12} max={64} step={1} name={'size'}>
|
||||
<SliderTrack>
|
||||
<SliderFilledTrack bg={color} />
|
||||
</SliderTrack>
|
||||
<SliderThumb />
|
||||
</Slider>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Button onClick={resetStyle}>Reset</Button>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
</DrawerBody>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
211
site/src/components/IconDetailOverlay.tsx
Normal file
@@ -0,0 +1,211 @@
|
||||
import { useSpring, animated } from "react-spring";
|
||||
import { Box, Text, IconButton, useColorMode, Flex, ButtonGroup, Button, useToast } from "@chakra-ui/core";
|
||||
import theme from "../lib/theme";
|
||||
import download from 'downloadjs';
|
||||
import copy from "copy-to-clipboard";
|
||||
import { X as Close } from 'lucide-react';
|
||||
import {useContext, useRef} from "react";
|
||||
import {IconStyleContext} from "./CustomizeIconContext";
|
||||
import {IconWrapper} from "./IconWrapper";
|
||||
|
||||
type IconDownload = {
|
||||
src: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
const IconDetailOverlay = ({ isOpen = true, onClose, icon }) => {
|
||||
const toast = useToast();
|
||||
const { colorMode } = useColorMode();
|
||||
const { tags = [], name } = icon;
|
||||
const {color, strokeWidth, size} = useContext(IconStyleContext);
|
||||
const iconRef = useRef<SVGSVGElement>(null);
|
||||
|
||||
const { transform, opacity } = useSpring({
|
||||
opacity: isOpen ? 1 : 0,
|
||||
transform: `translateY(${isOpen ? -120 : 0}%)`,
|
||||
config: { mass: 5, tension: 500, friction: 80 },
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
const panelStyling = {
|
||||
transform: transform.interpolate(t => t),
|
||||
opacity: opacity.interpolate(o => o),
|
||||
width: "100%",
|
||||
willChange: "transform"
|
||||
}
|
||||
|
||||
const iconStyling = (isLight) => ({
|
||||
height: "25vw",
|
||||
width: "25vw",
|
||||
minHeight: "160px",
|
||||
minWidth: "160px",
|
||||
maxHeight: "240px",
|
||||
maxWidth: "240px",
|
||||
color: color,
|
||||
});
|
||||
|
||||
const downloadIcon = ({src, name} : IconDownload) => download(src, `${name}.svg`, 'image/svg+xml');
|
||||
|
||||
const copyIcon = ({src, name} : IconDownload) => {
|
||||
copy(src);
|
||||
toast({
|
||||
title: "Copied!",
|
||||
description: `Icon "${name}" copied to clipboard.`,
|
||||
status: "success",
|
||||
duration: 1500,
|
||||
isClosable: true
|
||||
});
|
||||
}
|
||||
|
||||
const downloadPNG = ({src, name}: IconDownload) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
const image = new Image();
|
||||
image.src = `data:image/svg+xml;base64,${btoa(src)}`;
|
||||
image.onload = function() {
|
||||
ctx.drawImage(image, 0, 0);
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.download = `${name}.png`;
|
||||
link.href = canvas.toDataURL('image/png')
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
position="fixed"
|
||||
bottom={0}
|
||||
zIndex={2}
|
||||
width="100%"
|
||||
left={0}
|
||||
height={0}
|
||||
key={name}
|
||||
>
|
||||
<Flex
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
pt={4}
|
||||
pb={4}
|
||||
maxW="850px"
|
||||
margin="0 auto"
|
||||
w="full"
|
||||
px={8}
|
||||
>
|
||||
<animated.div
|
||||
style={panelStyling}
|
||||
>
|
||||
<Box
|
||||
borderWidth="1px"
|
||||
rounded="lg"
|
||||
width="full"
|
||||
boxShadow={theme.shadows.xl}
|
||||
position="relative"
|
||||
bg={
|
||||
colorMode == "light"
|
||||
? theme.colors.white
|
||||
: theme.colors.gray[700]
|
||||
}
|
||||
padding={8}
|
||||
>
|
||||
<IconButton
|
||||
size="sm"
|
||||
aria-label="Close overlay"
|
||||
variant="ghost"
|
||||
color="current"
|
||||
ml="3"
|
||||
position="absolute"
|
||||
top={4}
|
||||
right={4}
|
||||
onClick={handleClose}
|
||||
icon={<Close />}
|
||||
/>
|
||||
<Flex direction={['column', 'row']} alignItems={['center', 'flex-start']}>
|
||||
<Flex>
|
||||
<Box
|
||||
borderWidth="1px"
|
||||
rounded="md"
|
||||
position="relative"
|
||||
bg={
|
||||
colorMode == "light"
|
||||
? theme.colors.whiteAlpha[800]
|
||||
: theme.colors.blackAlpha[500]
|
||||
}
|
||||
padding={0}
|
||||
>
|
||||
<div
|
||||
style={iconStyling(colorMode == "light")}
|
||||
className="icon-large"
|
||||
>
|
||||
<IconWrapper
|
||||
content={icon.content}
|
||||
stroke={color}
|
||||
strokeWidth={strokeWidth}
|
||||
height={size}
|
||||
width={size}
|
||||
ref={iconRef}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<svg className="icon-grid" width="24" height="24" viewBox={`0 0 ${size} ${size}`} fill="none" stroke={colorMode == "light" ? '#E2E8F0' : theme.colors.gray[600]} strokeWidth="0.1" xmlns="http://www.w3.org/2000/svg">
|
||||
{ Array.from({ length:(size - 1) }, (_, i) => (
|
||||
<g key={`grid-${i}`}>
|
||||
<line key={`horizontal-${i}`} x1={0} y1={i + 1} x2={size} y2={i + 1} />
|
||||
<line key={`vertical-${i}`} x1={i + 1} y1={0} x2={i + 1} y2={size} />
|
||||
</g>
|
||||
)) }
|
||||
</svg>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex marginLeft={[0, 8]}>
|
||||
<Box>
|
||||
<Text fontSize="3xl" style={{ cursor: "pointer" }} mb={1}>
|
||||
{icon.name}
|
||||
</Text>
|
||||
<Box mb={4}>
|
||||
{ tags?.length ? (
|
||||
<Text
|
||||
fontSize="xl"
|
||||
fontWeight="bold"
|
||||
color={
|
||||
colorMode === "light"
|
||||
? 'gray.600'
|
||||
: 'gray.500'
|
||||
}
|
||||
>
|
||||
{ tags.join(' • ') }
|
||||
</Text>
|
||||
) : ''}
|
||||
|
||||
{/* <Button size="sm" fontSize="md" variant="ghost" onClick={() => downloadIcon(icon)}>
|
||||
Edit Tags
|
||||
</Button> */}
|
||||
</Box>
|
||||
<ButtonGroup spacing={4}>
|
||||
<Button variant="solid" onClick={() => downloadIcon({src: iconRef.current.outerHTML, name: icon.name})} mb={1}>
|
||||
Download SVG
|
||||
</Button>
|
||||
<Button variant="solid" onClick={() => copyIcon({src: iconRef.current.outerHTML, name: icon.name})} mb={1}>
|
||||
Copy SVG
|
||||
</Button>
|
||||
<Button variant="solid" onClick={() => downloadPNG({src: iconRef.current.outerHTML, name: icon.name})} mb={1}>
|
||||
Download PNG
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Box>
|
||||
</animated.div>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default IconDetailOverlay;
|
||||
84
site/src/components/IconList.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Button, Flex, Grid, Text, useToast } from "@chakra-ui/core";
|
||||
import download from 'downloadjs';
|
||||
import Link from 'next/link'
|
||||
import copy from "copy-to-clipboard";
|
||||
import {useContext, useMemo} from "react";
|
||||
import {IconStyleContext} from "./CustomizeIconContext";
|
||||
import {IconWrapper} from "./IconWrapper";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
const IconList = ({icons}) => {
|
||||
const router = useRouter()
|
||||
const toast = useToast();
|
||||
const {color, size, strokeWidth} = useContext(IconStyleContext);
|
||||
const { search } = router.query;
|
||||
|
||||
const query = useMemo(()=> search !== undefined ? { search } : {},[search])
|
||||
|
||||
return (
|
||||
<Grid
|
||||
templateColumns={`repeat(auto-fill, minmax(160px, 1fr))`}
|
||||
gap={5}
|
||||
marginBottom="320px"
|
||||
>
|
||||
{ icons.map((icon) => {
|
||||
const actualIcon = icon.item ? icon.item : icon;
|
||||
const { name, content } = actualIcon;
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={name}
|
||||
scroll={false}
|
||||
href={{
|
||||
pathname: '/icon/[iconName]',
|
||||
query: {
|
||||
...query,
|
||||
iconName: name,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
borderWidth="1px"
|
||||
rounded="lg"
|
||||
padding={16}
|
||||
onClick={(event) => {
|
||||
if (event.shiftKey) {
|
||||
copy(actualIcon.src);
|
||||
toast({
|
||||
title: "Copied!",
|
||||
description: `Icon "${name}" copied to clipboard.`,
|
||||
status: "success",
|
||||
duration: 1500,
|
||||
});
|
||||
}
|
||||
if (event.metaKey) {
|
||||
download(
|
||||
actualIcon.src,
|
||||
`${name}.svg`,
|
||||
"image/svg+xml"
|
||||
);
|
||||
}
|
||||
}}
|
||||
key={name}
|
||||
alignItems="center"
|
||||
>
|
||||
<Flex direction="column" align="center" justify="center">
|
||||
<IconWrapper
|
||||
content={content}
|
||||
stroke={color}
|
||||
strokeWidth={strokeWidth}
|
||||
height={size}
|
||||
width={size}
|
||||
/>
|
||||
<Text marginTop={5}>{name}</Text>
|
||||
</Flex>
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
export default IconList;
|
||||
119
site/src/components/IconOverview.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import {
|
||||
Box,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputLeftElement,
|
||||
Text,
|
||||
useColorMode,
|
||||
Icon,
|
||||
} from '@chakra-ui/core';
|
||||
import IconList from './IconList';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import useSearch from '../lib/useSearch';
|
||||
import { useRouter } from 'next/router';
|
||||
import theme from '../lib/theme';
|
||||
import { Search as SearchIcon } from 'lucide-react';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
const isFilledString = (string) => string !== undefined && string !== null && string !== '';
|
||||
|
||||
const IconOverview = ({ data }) => {
|
||||
const router = useRouter();
|
||||
const { search } = router.query;
|
||||
|
||||
const [queryText, setQueryText] = useState(search);
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
const inputElement = useRef(null);
|
||||
|
||||
function handleKeyDown(event) {
|
||||
if (event.key === '/' && inputElement.current !== document.activeElement) {
|
||||
event.preventDefault();
|
||||
inputElement.current.focus();
|
||||
}
|
||||
}
|
||||
|
||||
const setQueryParam = (searchString) => {
|
||||
const { query, asPath } = router;
|
||||
if(isFilledString(searchString)) {
|
||||
let route = {
|
||||
pathname: '',
|
||||
query
|
||||
}
|
||||
|
||||
if(query.iconName) {
|
||||
route.query.iconName = query.iconName;
|
||||
route.pathname = '/icon/[iconName]';
|
||||
}
|
||||
|
||||
route.query.search = searchString;
|
||||
|
||||
router.replace(route);
|
||||
}
|
||||
else {
|
||||
if (query?.search) {
|
||||
delete query.search;
|
||||
router.replace({
|
||||
query
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const searchResults = useMemo(() => useSearch(data, queryText), [data, queryText])
|
||||
|
||||
const handleSearchInput = debounce((event) => {
|
||||
event.persist();
|
||||
const { value = '' } = inputElement?.current;
|
||||
|
||||
setQueryText(value)
|
||||
setQueryParam(value)
|
||||
}, 400)
|
||||
|
||||
useEffect(() => {
|
||||
setQueryText(search)
|
||||
}, [search]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<InputGroup position="sticky" top={4} zIndex={1}>
|
||||
<InputLeftElement
|
||||
children={
|
||||
<Icon>
|
||||
<SearchIcon />
|
||||
</Icon>
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
ref={inputElement}
|
||||
placeholder={`Search ${Object.keys(data).length} icons (Press "/" to focus)`}
|
||||
onChange={handleSearchInput}
|
||||
defaultValue={queryText}
|
||||
bg={colorMode == 'light' ? theme.colors.white : theme.colors.gray[700]}
|
||||
/>
|
||||
</InputGroup>
|
||||
<Box marginTop={5}>
|
||||
{searchResults.length > 0 ? (
|
||||
<IconList icons={searchResults} />
|
||||
) : (
|
||||
<Text
|
||||
fontSize="2xl"
|
||||
fontWeight="bold"
|
||||
textAlign="center"
|
||||
style={{ wordBreak: 'break-word' }}
|
||||
>
|
||||
No results found for "{queryText}"
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default IconOverview;
|
||||
27
site/src/components/IconWrapper.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { forwardRef, SVGProps } from 'react';
|
||||
|
||||
interface IconWrapperProps extends SVGProps<SVGSVGElement> {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export const IconWrapper = forwardRef<SVGSVGElement, IconWrapperProps>((props, ref) => {
|
||||
const defaultAttrs : SVGProps<SVGSVGElement>= {
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
viewBox: '0 0 24 24',
|
||||
fill: 'none',
|
||||
stroke: 'currentColor',
|
||||
strokeWidth: '2px',
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
};
|
||||
|
||||
const { content, ...rest } = props;
|
||||
const attrs = {
|
||||
...defaultAttrs,
|
||||
...rest,
|
||||
};
|
||||
|
||||
return <svg ref={ref} {...attrs} dangerouslySetInnerHTML={{ __html: content }} />;
|
||||
});
|
||||
@@ -1,10 +1,15 @@
|
||||
import { Box, Divider, Flex, Text, Link, Icon, useColorMode } from "@chakra-ui/core";
|
||||
import { Box, Divider, Flex, Text, Link, Icon, useColorMode, useColorModeValue, IconButton } from "@chakra-ui/core";
|
||||
import { useKeyBindings } from "../lib/key";
|
||||
import {useRouter} from "next/router";
|
||||
import { useRouter } from "next/router";
|
||||
import NextLink from "next/link"
|
||||
import { Moon, Sun } from 'lucide-react';
|
||||
import Logo from 'babel-loader!react-svg-loader?jsx=true!../../public/logo.svg';
|
||||
|
||||
const Layout = ({ children }) => {
|
||||
const router = useRouter();
|
||||
const { colorMode, toggleColorMode } = useColorMode();
|
||||
const text = useColorModeValue('dark', 'light')
|
||||
const ColorModeToggle = useColorModeValue(Moon, Sun);
|
||||
|
||||
function setQuery(query){
|
||||
router.push({
|
||||
@@ -12,6 +17,7 @@ const Layout = ({ children }) => {
|
||||
query: { query: query }
|
||||
}).then();
|
||||
}
|
||||
|
||||
useKeyBindings({
|
||||
Escape: {
|
||||
fn: () => setQuery(""),
|
||||
@@ -35,27 +41,45 @@ const Layout = ({ children }) => {
|
||||
px={8}
|
||||
>
|
||||
<Flex justifyContent="center" alignItems="center">
|
||||
<Text
|
||||
fontSize="4xl"
|
||||
onClick={() => setQuery("")}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
Lucide
|
||||
</Text>
|
||||
<NextLink href="/" passHref>
|
||||
<Link display="flex" _hover={{textDecoration: 'none'}}>
|
||||
<Icon boxSize={12} marginRight="8px">
|
||||
<Logo/>
|
||||
</Icon>
|
||||
<Text
|
||||
fontSize="40px"
|
||||
lineHeight="48px"
|
||||
>
|
||||
Lucide
|
||||
</Text>
|
||||
</Link>
|
||||
</NextLink>
|
||||
</Flex>
|
||||
<Flex justifyContent="center" alignItems="center">
|
||||
<Link href="https://github.com/lucide-icons/lucide" isExternal style={{ fontSize: "18px", marginRight: '24px' }}>
|
||||
<Link
|
||||
href="https://github.com/lucide-icons/lucide"
|
||||
isExternal
|
||||
marginRight={6}
|
||||
fontSize="xl"
|
||||
>
|
||||
Github
|
||||
</Link>
|
||||
<div onClick={toggleColorMode} style={{ cursor: "pointer" }}>
|
||||
<Icon name={colorMode == "light" ? "moon" : "sun"} size="24px" />
|
||||
</div>
|
||||
<IconButton
|
||||
size="md"
|
||||
fontSize="lg"
|
||||
aria-label={`Switch to ${text} mode`}
|
||||
variant="ghost"
|
||||
color="current"
|
||||
ml="3"
|
||||
onClick={toggleColorMode}
|
||||
icon={<ColorModeToggle />}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex margin="0 auto" direction="column" maxW="1250px" px={8}>
|
||||
{children}
|
||||
<Divider marginTop={10} marginBottom={10} />
|
||||
<Divider marginTop={4} marginBottom={8} />
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import cheerio from 'cheerio';
|
||||
import tags from '../../../tags.json';
|
||||
|
||||
const directory = path.join(process.cwd(), "../icons");
|
||||
@@ -16,10 +17,14 @@ export function getData(name) {
|
||||
const fullPath = path.join(directory, `${name}.svg`);
|
||||
const fileContents = fs.readFileSync(fullPath, "utf8");
|
||||
|
||||
const $ = cheerio.load(fileContents);
|
||||
const content = $("svg").html();
|
||||
|
||||
return {
|
||||
name,
|
||||
tags: tags[name] || [],
|
||||
src: fileContents,
|
||||
content: content
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useDebounce } from './useDebounce';
|
||||
|
||||
function useSearch(icons: Object, query: string | string[]) {
|
||||
let iconList = useMemo(() => Object.values(icons), [icons]);
|
||||
const [results, setResults] = useState(iconList);
|
||||
// query can be an array because this is a valid query string ?query=xyz&query=abc
|
||||
const debouncedQuery = useDebounce(
|
||||
typeof query === 'string' ? query.trim() : typeof query === 'undefined' ? '' : query[0].trim(),
|
||||
300
|
||||
);
|
||||
|
||||
async function doSearch() {
|
||||
if (debouncedQuery) {
|
||||
const Fuse = (await import('fuse.js')).default;
|
||||
const fuse = new Fuse(iconList, {
|
||||
threshold: 0.2,
|
||||
keys: ['name', 'tags'],
|
||||
});
|
||||
return fuse.search(debouncedQuery);
|
||||
} else {
|
||||
return iconList;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
doSearch().then(setResults);
|
||||
}, [debouncedQuery]);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
export default useSearch;
|
||||
@@ -4,70 +4,7 @@ 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>
|
||||
),
|
||||
},
|
||||
body: `'Mukta', sans-serif`,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
20
site/src/lib/useSearch.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useDebounce } from './useDebounce';
|
||||
|
||||
function useSearch(icons: Array<any>, query:string) {
|
||||
if(!query) return icons;
|
||||
|
||||
const searchString = query.toLowerCase()
|
||||
|
||||
return icons.filter(({ name, tags }) => {
|
||||
const icon = { name, tags };
|
||||
|
||||
return Object.keys(icon).some(
|
||||
key => String(icon[key])
|
||||
.toLowerCase()
|
||||
.includes(searchString)
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
export default useSearch;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CSSReset, ThemeProvider, ColorModeProvider } from '@chakra-ui/core';
|
||||
import { ChakraProvider } from '@chakra-ui/core';
|
||||
import customTheme from '../lib/theme';
|
||||
import '../assets/styling.css';
|
||||
import Head from 'next/head';
|
||||
|
||||
const App = ({ Component, pageProps }) => {
|
||||
@@ -8,12 +9,9 @@ const App = ({ Component, pageProps }) => {
|
||||
<Head>
|
||||
<title>Lucide</title>
|
||||
</Head>
|
||||
<ThemeProvider theme={customTheme}>
|
||||
<ColorModeProvider>
|
||||
<CSSReset />
|
||||
<Component {...pageProps} />
|
||||
</ColorModeProvider>
|
||||
</ThemeProvider>
|
||||
<ChakraProvider theme={customTheme}>
|
||||
<Component {...pageProps} />
|
||||
</ChakraProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Document, { Head, Html, Main, NextScript } from "next/document";
|
||||
import { ColorModeScript } from "@chakra-ui/core"
|
||||
|
||||
class MyDocument extends Document {
|
||||
render() {
|
||||
@@ -6,9 +7,17 @@ class MyDocument extends Document {
|
||||
<Html>
|
||||
<Head>
|
||||
<link
|
||||
href="https://indestructibletype.com/fonts/Jost.css"
|
||||
href="https://fonts.googleapis.com/css2?family=Mukta:wght@400;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"/>
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/>
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/>
|
||||
<link rel="manifest" href="/site.webmanifest"/>
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#F56565"/>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg"/>
|
||||
<meta name="msapplication-TileColor" content="#F56565"/>
|
||||
<meta name="theme-color" content="#F56565"></meta>
|
||||
</Head>
|
||||
<style jsx global>{`
|
||||
* {
|
||||
@@ -20,6 +29,7 @@ class MyDocument extends Document {
|
||||
}
|
||||
`}</style>
|
||||
<body>
|
||||
<ColorModeScript />
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
|
||||
55
site/src/pages/icon/[iconName].tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import IconDetailOverlay from '../../components/IconDetailOverlay'
|
||||
import { getAllData, getData } from '../../lib/icons';
|
||||
import IconOverview from '../../components/IconOverview';
|
||||
import Layout from '../../components/Layout';
|
||||
import Header from '../../components/Header';
|
||||
|
||||
const IconPage = ({ icon, data }) => {
|
||||
const router = useRouter()
|
||||
|
||||
const onClose = () => {
|
||||
let query = {};
|
||||
|
||||
if(router.query.search) {
|
||||
query = {
|
||||
search: router.query.search
|
||||
};
|
||||
}
|
||||
|
||||
router.push({
|
||||
pathname: '/',
|
||||
query,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<IconDetailOverlay
|
||||
key={icon.name}
|
||||
icon={icon}
|
||||
onClose={onClose}
|
||||
/>
|
||||
<Header {...{data}}/>
|
||||
<IconOverview {...{data}}/>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconPage
|
||||
|
||||
export function getStaticProps({ params: { iconName } }) {
|
||||
const data = getAllData();
|
||||
const icon = getData(iconName);
|
||||
return { props: { icon, data } }
|
||||
}
|
||||
|
||||
export function getStaticPaths() {
|
||||
return {
|
||||
paths: getAllData().map(({name: iconName }) => ({
|
||||
params: { iconName },
|
||||
})),
|
||||
fallback: false,
|
||||
}
|
||||
}
|
||||
@@ -1,148 +1,34 @@
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
Grid,
|
||||
Link,
|
||||
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 Layout from '../components/Layout';
|
||||
import { getAllData } from '../lib/icons';
|
||||
import useSearch from '../lib/search';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useDebounce } from '../lib/useDebounce';
|
||||
import Layout from "../components/Layout";
|
||||
import { getAllData } from "../lib/icons";
|
||||
|
||||
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' });
|
||||
}
|
||||
import IconOverview from "../components/IconOverview";
|
||||
import IconDetailOverlay from "../components/IconDetailOverlay";
|
||||
import { useRouter } from "next/router";
|
||||
import Header from "../components/Header";
|
||||
import {CustomizeIconContext} from "../components/CustomizeIconContext";
|
||||
|
||||
const IndexPage = ({ data }) => {
|
||||
const router = useRouter();
|
||||
const { query } = router.query;
|
||||
const [queryText, setQueryText] = useState(query || '');
|
||||
const toast = useToast();
|
||||
const debouncedQuery = useDebounce(queryText, 1000);
|
||||
const results = useSearch(data, queryText);
|
||||
|
||||
useEffect(() => {
|
||||
setQueryText(query);
|
||||
}, [query]);
|
||||
|
||||
useEffect(() => {
|
||||
router.push({
|
||||
pathname: '/',
|
||||
query: { query: debouncedQuery },
|
||||
});
|
||||
}, [debouncedQuery]);
|
||||
|
||||
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);
|
||||
}, []);
|
||||
const getIcon = (iconName) => data.find(({name}) => name === iconName) || {};
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Flex direction="column" align="center" justify="center">
|
||||
<Text fontSize="3xl" as="b" mb="4">
|
||||
Simply beautiful open source icons, community-sourced
|
||||
</Text>
|
||||
<Text fontSize="lg" as="p" textAlign="center" mb="8">
|
||||
An open-source icon library, a fork of Feather Icons. <br/>We're expanding the icon set as much as possible while keeping it nice-looking - <Link href="https://github.com/lucide-icons/lucide" isExternal>join us</Link>!
|
||||
</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={queryText}
|
||||
onChange={(event) => setQueryText(event.target.value)}
|
||||
marginBottom={5}
|
||||
<CustomizeIconContext>
|
||||
<IconDetailOverlay
|
||||
isOpen={!!router.query.iconName}
|
||||
icon={getIcon(router.query.iconName)}
|
||||
onClose={() => router.push('/')}
|
||||
/>
|
||||
</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>
|
||||
)}
|
||||
<Header {...{data}}/>
|
||||
<IconOverview {...{data}}/>
|
||||
</CustomizeIconContext>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export async function getStaticProps() {
|
||||
let data = getAllData();
|
||||
|
||||
return {
|
||||
props: {
|
||||
data,
|
||||
|
||||
18
site/src/tests/IconOverview.test.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { getAllData } from '../lib/icons';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import useSearch from '../lib/useSearch';
|
||||
|
||||
describe('Icon Overview', () => {
|
||||
it('can search filter icons', async () => {
|
||||
let allData = getAllData();
|
||||
|
||||
const { result: result1, waitForNextUpdate: wait1 } = renderHook(() => useSearch(allData, ''));
|
||||
expect(result1.current).toHaveLength(allData.length);
|
||||
|
||||
const { result: result2, waitForNextUpdate: wait2 } = renderHook(() =>
|
||||
useSearch(allData, 'airplay')
|
||||
);
|
||||
await wait2();
|
||||
expect(result2.current).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
@@ -3,9 +3,8 @@ import Index from '../pages/index';
|
||||
import React from 'react';
|
||||
import { render } from './test-utils';
|
||||
import { getAllData } from '../lib/icons';
|
||||
|
||||
import App from '../pages/_app';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import useSearch from '../lib/search';
|
||||
|
||||
describe('App', () => {
|
||||
it('renders without crashing', () => {
|
||||
@@ -15,16 +14,4 @@ describe('App', () => {
|
||||
screen.getByText('Simply beautiful open source icons, community-sourced')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
it('can search filter icons', async () => {
|
||||
let allData = getAllData();
|
||||
|
||||
const { result: result1, waitForNextUpdate: wait1 } = renderHook(() => useSearch(allData, ''));
|
||||
expect(result1.current).toHaveLength(allData.length);
|
||||
|
||||
const { result: result2, waitForNextUpdate: wait2 } = renderHook(() =>
|
||||
useSearch(allData, 'airplay')
|
||||
);
|
||||
await wait2();
|
||||
expect(result2.current).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
3703
site/yarn.lock
16
tags.json
@@ -12,10 +12,12 @@
|
||||
"archive": ["index", "box"],
|
||||
"at-sign": ["mention", "at", "email", "message"],
|
||||
"award": ["achievement", "badge"],
|
||||
"axe": ["hatchet"],
|
||||
"aperture": ["camera", "photo"],
|
||||
"bar-chart": ["statistics", "diagram", "graph"],
|
||||
"bar-chart-2": ["statistics", "diagram", "graph"],
|
||||
"battery": ["power", "electricity"],
|
||||
"beaker": ["cup"],
|
||||
"battery-charging": ["power", "electricity"],
|
||||
"bell": ["alarm", "notification", "sound"],
|
||||
"bell-off": ["alarm", "notification", "silent"],
|
||||
@@ -74,14 +76,18 @@
|
||||
"file-minus": ["delete", "remove", "erase"],
|
||||
"file-plus": ["add", "create", "new"],
|
||||
"file-text": ["data", "txt", "pdf"],
|
||||
"files": ["multiple", "copy"],
|
||||
"film": ["movie", "video"],
|
||||
"filter": ["funnel", "hopper"],
|
||||
"flag": ["report"],
|
||||
"flask-conical": ["beaker", "erlenmeyer"],
|
||||
"flask-round": ["beaker"],
|
||||
"folder-minus": ["directory"],
|
||||
"folder-plus": ["directory"],
|
||||
"folder": ["directory"],
|
||||
"framer": ["logo", "design", "tool"],
|
||||
"frown": ["emoji", "face", "bad", "sad", "emotion"],
|
||||
"gavel": ["hammer", "mallet"],
|
||||
"gift": ["present", "box", "birthday", "party"],
|
||||
"git-branch": ["code", "version control"],
|
||||
"git-commit": ["code", "version control"],
|
||||
@@ -90,14 +96,17 @@
|
||||
"github": ["logo", "version control"],
|
||||
"gitlab": ["logo", "version control"],
|
||||
"globe": ["world", "browser", "language", "translate"],
|
||||
"hammer": ["mallet"],
|
||||
"hard-drive": ["computer", "server", "memory", "data"],
|
||||
"hard-hat": ["helmet", "construction"],
|
||||
"hash": ["hashtag", "number", "pound"],
|
||||
"headphones": ["music", "audio", "sound"],
|
||||
"heart": ["like", "love", "emotion"],
|
||||
"help-circle": ["question mark"],
|
||||
"hexagon": ["shape", "node.js", "logo"],
|
||||
"home": ["house", "living"],
|
||||
"image": ["picture"],
|
||||
"image": ["picture", "photo"],
|
||||
"image-off": ["picture", "photo"],
|
||||
"inbox": ["email"],
|
||||
"instagram": ["logo", "camera"],
|
||||
"key": ["password", "login", "authentication", "secure"],
|
||||
@@ -151,6 +160,7 @@
|
||||
"phone": ["call"],
|
||||
"play": ["music", "start"],
|
||||
"pie-chart": ["statistics", "diagram"],
|
||||
"pipette": ["eye dropper", "color picker"],
|
||||
"play-circle": ["music", "start"],
|
||||
"plus": ["add", "new"],
|
||||
"plus-circle": ["add", "new"],
|
||||
@@ -166,6 +176,7 @@
|
||||
"rotate-ccw": ["arrow"],
|
||||
"rotate-cw": ["arrow"],
|
||||
"rss": ["feed", "subscribe"],
|
||||
"ruler": ["measure"],
|
||||
"save": ["floppy disk"],
|
||||
"scissors": ["cut"],
|
||||
"search": ["find", "magnifier", "magnifying glass"],
|
||||
@@ -176,6 +187,7 @@
|
||||
"shield-off": ["security", "insecure"],
|
||||
"shopping-bag": ["ecommerce", "cart", "purchase", "store"],
|
||||
"shopping-cart": ["ecommerce", "cart", "purchase", "store"],
|
||||
"shovel": ["dig", "spade"],
|
||||
"shuffle": ["music"],
|
||||
"skip-back": ["music"],
|
||||
"skip-forward": ["music"],
|
||||
@@ -199,7 +211,7 @@
|
||||
"thumbs-up": ["like", "good", "emotion"],
|
||||
"toggle-left": ["on", "off", "switch"],
|
||||
"toggle-right": ["on", "off", "switch"],
|
||||
"tool": ["settings", "spanner"],
|
||||
"wrench": ["tool", "settings", "spanner"],
|
||||
"trash": ["garbage", "delete", "remove", "bin"],
|
||||
"trash-2": ["garbage", "delete", "remove", "bin"],
|
||||
"triangle": ["delta"],
|
||||
|
||||