Compare commits

...

26 Commits

Author SHA1 Message Date
Daniel Bayley
a418010baa Add arrow-*-circle icons (including escape key) (#1013)
* Add `arrow-up-left-from-circle` (<kbd>esc</kbd>ape key) icon

* Add `arrow-up-right-from-circle` icon

* Add `arrow-down-left-from-circle` icon

* Add `arrow-down-right-from-circle` icon
2023-04-14 22:39:23 +02:00
Wojciech Maj
329d75a2c1 Add note on use of <use> (#1050)
* Add note on use of `<use>`

* Update note

Co-authored-by: Karsa <contact@karsa.org>

---------

Co-authored-by: Karsa <contact@karsa.org>
2023-04-12 21:14:34 +02:00
Lucide Bot
2482416aef 📦 Bump lucide package versions to 0.144.0 2023-04-12 18:55:01 +00:00
Daniel Bayley
11d7b48d70 Modify disc to add disc alternate (vinyl/record) icon (#1014)
* Modify `disc` icon

* Add `disc` alternate (`vinyl`/`record`) icon


Update icons/disc-2.svg

Co-authored-by: Jakob Guddas <github@jguddas.de>

---------

Co-authored-by: Jakob Guddas <github@jguddas.de>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2023-04-12 20:49:05 +02:00
Daniel Bayley
5cbf9363cf Fix rotate-ccw metadata (#1049) 2023-04-12 12:37:20 +02:00
Lucide Bot
92531a8a86 📦 Bump lucide package versions to 0.143.0 2023-04-12 10:26:00 +00:00
Daniel Bayley
c67655d402 Refine heart-* icons (#1017)
Co-authored-by: Karsa <contact@karsa.org>
2023-04-12 12:20:32 +02:00
Lucide Bot
edc614bf43 📦 Bump lucide package versions to 0.142.0 2023-04-12 09:41:22 +00:00
Daniel Bayley
ded24ab61f Add book-* (git repository) icons (#1011)
* Add `book-copy` (`git clone` repo) icon

* Add `book-marked` (`git` repo) icon

* Add `book-template` (`git` template repo) icon

* Add `book-up` (`git push` repo) icon

* Add `book-down` (`git pull` repo) icon

* Add `book-add` (`git add` repo) icon

* Add `book-minus` (`git rm` repo) icon

* Add `book-x` (`git rm` repo) icon

* Add `book-lock` (`git` private repo) icon

* Add `book-key` (`git` private repo) icon
2023-04-12 11:35:52 +02:00
Lucide Bot
b626e91d7c 📦 Bump lucide package versions to 0.141.0 2023-04-12 09:27:35 +00:00
henritonus
a68ea8c33e add rat icon (#936)
* add rat icon

* Update icons/rat.json

Co-authored-by: Jakob Guddas <github@jguddas.de>

* Update rat.svg

---------

Co-authored-by: Karsa <contact@karsa.org>
Co-authored-by: Jakob Guddas <github@jguddas.de>
2023-04-12 11:21:34 +02:00
Daniel Bayley
4fd815a46c Improve rotate-* metadata (#1046)
* Improve `rotate-*` metadata

* Improve `history` metadata
Update icons/history.json

Co-authored-by: Karsa <contact@karsa.org>

* Apply suggestions from code review

---------

Co-authored-by: Karsa <contact@karsa.org>
2023-04-12 10:57:55 +02:00
Lucide Bot
0e180515a3 📦 Bump lucide package versions to 0.140.0 2023-04-12 06:26:42 +00:00
Daniel Bayley
5dbae5df45 Add text icon (#1044)
* Add `text` icon
Update icons/text.json

Co-authored-by: Karsa <contact@karsa.org>

* Apply suggestions from code review

---------

Co-authored-by: Karsa <contact@karsa.org>
2023-04-12 08:21:05 +02:00
Daniel Bayley
bcad75bddd Improve power metadata (#1047) 2023-04-12 08:17:29 +02:00
Daniel Bayley
78a0640832 Add metadata for chevron-right-square icon (#1033) 2023-04-11 09:58:38 +02:00
Karsa
05ea32948c Added home category to microwave 2023-04-11 09:18:33 +02:00
Lucide Bot
2e11931d2b 📦 Bump lucide package versions to 0.139.0 2023-04-11 07:09:43 +00:00
Daniel Bayley
689b3e1d83 Add step-forward/back icons (#1034)
* Add `step-forward` icon

* Add `step-back` icon
2023-04-11 09:03:47 +02:00
Lucide Bot
8ec672bd86 📦 Bump lucide package versions to 0.138.0 2023-04-10 18:50:41 +00:00
xnousnow
c096bf2325 Add clipboard-paste icon (#984)
* Adds `clipboard-paste` icon (#951)

* Update icon by ericfennis's review

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2023-04-10 20:44:39 +02:00
Karsa
a796cb105d Update [iconName].tsx
Revert "quickfix", mea culpa
2023-04-10 12:59:54 +02:00
Karsa
e4ac2cc4e9 Update [iconName].tsx
Quickfix for invalid method call
2023-04-10 12:57:30 +02:00
Eric Fennis
2ee208652f Add site categories (#876)
* bump package

* Add Logo

* remove console

* prettify it

* add favicons and fix issue

* Add categorie page

* add drag and drop

* Make drag and drop working

* Add drag options

* Add modal

* small styling fixes

* fix search

* Add code editor

* Add more styling

* Add more categories

* create context provider

* refactor eslint thing

* update chakra-ui

* improve, category bar

* Add sortly

* Add categories

* Try to fix new categories

* Fix Categories

* Add docs Menu Tree data

* Start with sectiontitles

* Create link list

* Add Docs menu to mobile

* Add some more pages and text

* Optimize text

* add license to the menu

* update packages

* Fix build

* Update title

* Remove ModifiedTooltip

* Fix assets

* add yarn to copy-assets command

* install deps

* update

* try something

* new categories page

* try something

* Add icons reorder

* add new icons page

* add category view button

* add categories

* Fix vercel build

* Add sidebar and costumize button

* fix merge conlfict

* Remove console.logs

* add sidebar

* Add icon overview

* Fix key render issue

* Fix types

* Fix category render

* Fix build

* update lockfile

* Added category icon and icon count in list. Moved scrollbar to the left to make it less intrusive

---------

Co-authored-by: Eric Fennis <eric.fennis@endurance.com>
Co-authored-by: Eric Fennis <eric@dreamteam.nl>
Co-authored-by: Karsa <karsa@karsa.org>
2023-04-09 16:49:11 +02:00
Lucide Bot
d0826259d1 📦 Bump lucide package versions to 0.137.0 2023-04-09 11:34:19 +00:00
Daniel Bayley
3acbfb428b Add orbit icon (#1010) 2023-04-09 13:28:56 +02:00
108 changed files with 2337 additions and 547 deletions

15
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}"
}
]
}

7
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"cSpell.words": [
"devs",
"preact",
"Preact"
]
}

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Text formatting",
"icon": "type"
}
}

View File

@@ -55,6 +55,8 @@ Here are rules that should be followed to keep quality and consistency when maki
Before an icon is added to the library, we like to have readable and optimized svg code.
Never use [`<use>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use). While it may sometimes seem like a good way to optimize file size, there's no way to ensure that the referenced element IDs will be unique once the SVGs are embedded in HTML documents.
### Global Attributes
For each icon these attributes are applied, corresponding to the above rules.

View File

@@ -0,0 +1,9 @@
{
"$schema": "../icon.schema.json",
"tags": [
"direction"
],
"categories": [
"arrows"
]
}

View File

@@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="8,22 2,22 2,16 " />
<line x1="2" y1="22" x2="12" y2="12" />
<path d="M2,12C2,6.5,6.5,2,12,2s10,4.5,10,10s-4.5,10-10,10" />
</svg>

After

Width:  |  Height:  |  Size: 355 B

View File

@@ -0,0 +1,9 @@
{
"$schema": "../icon.schema.json",
"tags": [
"direction"
],
"categories": [
"arrows"
]
}

View File

@@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="22,16 22,22 16,22 " />
<line x1="22" y1="22" x2="12" y2="12" />
<path d="M12,22C6.5,22,2,17.5,2,12S6.5,2,12,2s10,4.5,10,10" />
</svg>

After

Width:  |  Height:  |  Size: 359 B

View File

@@ -0,0 +1,14 @@
{
"$schema": "../icon.schema.json",
"tags": [
"direction",
"keyboard",
"key",
"escape",
"button"
],
"categories": [
"arrows",
"coding"
]
}

View 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="M12,2c5.5,0,10,4.5,10,10s-4.5,10-10,10S2,17.5,2,12" />
<polyline points="2,8 2,2 8,2 " />
<line x1="2" y1="2" x2="12" y2="12" />
</svg>

After

Width:  |  Height:  |  Size: 352 B

View File

@@ -0,0 +1,9 @@
{
"$schema": "../icon.schema.json",
"tags": [
"direction"
],
"categories": [
"arrows"
]
}

View File

@@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="16,2 22,2 22,8 " />
<line x1="22" y1="2" x2="12" y2="12" />
<path d="M22,12c0,5.5-4.5,10-10,10S2,17.5,2,12S6.5,2,12,2" />
</svg>

After

Width:  |  Height:  |  Size: 354 B

18
icons/book-copy.json Normal file
View File

@@ -0,0 +1,18 @@
{
"$schema": "../icon.schema.json",
"tags": [
"read",
"dictionary",
"booklet",
"library",
"code",
"version control",
"git",
"repository",
"clone"
],
"categories": [
"development",
"coding"
]
}

16
icons/book-copy.svg Normal file
View 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,16V4c0-1.1,0.9-2,2-2h11" />
<path d="M5,14H4c-1.1,0-2,0.9-2,2s0.9,2,2,2h1" />
<path d="M22,18H11c-1.1,0-2,0.9-2,2l0,0" />
<path d="M11,6h11v16H11c-1.1,0-2-0.9-2-2V8C9,6.9,9.9,6,11,6z" />
</svg>

After

Width:  |  Height:  |  Size: 415 B

14
icons/book-down.json Normal file
View File

@@ -0,0 +1,14 @@
{
"$schema": "../icon.schema.json",
"tags": [
"code",
"version control",
"git",
"repository",
"pull"
],
"categories": [
"development",
"coding"
]
}

16
icons/book-down.svg Normal file
View 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 19.5A2.5 2.5 0 0 1 6.5 17H20" />
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2Z" />
<path d="M12 13V7" />
<path d="m9 10 3 3 3-3" />
</svg>

After

Width:  |  Height:  |  Size: 386 B

15
icons/book-key.json Normal file
View File

@@ -0,0 +1,15 @@
{
"$schema": "../icon.schema.json",
"tags": [
"code",
"version control",
"git",
"repository",
"private"
],
"categories": [
"development",
"coding",
"security"
]
}

17
icons/book-key.svg Normal file
View 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="M20,2l-4.5,4.5"/>
<path d="M19,3l1,1"/>
<circle cx="14" cy="8" r="2"/>
<path d="M4,19.5C4,18.1,5.1,17,6.5,17H20" />
<path d="M20,8v14H6.5C5.1,22,4,20.9,4,19.5v-15C4,3.1,5.1,2,6.5,2H14" />
</svg>

After

Width:  |  Height:  |  Size: 415 B

15
icons/book-lock.json Normal file
View File

@@ -0,0 +1,15 @@
{
"$schema": "../icon.schema.json",
"tags": [
"code",
"version control",
"git",
"repository",
"private"
],
"categories": [
"development",
"coding",
"security"
]
}

16
icons/book-lock.svg Normal file
View 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,19.5C4,18.1,5.1,17,6.5,17H20" />
<path d="M18,6V4c0-1.1-0.9-2-2-2s-2,0.9-2,2v2" />
<path d="M20,15v7H6.5C5.1,22,4,20.9,4,19.5v-15C4,3.1,5.1,2,6.5,2H10" />
<path d="M13,6h6c0.6,0,1,0.4,1,1v3c0,0.6-0.4,1-1,1h-6c-0.6,0-1-0.4-1-1V7C12,6.4,12.4,6,13,6z" />
</svg>

After

Width:  |  Height:  |  Size: 480 B

17
icons/book-marked.json Normal file
View File

@@ -0,0 +1,17 @@
{
"$schema": "../icon.schema.json",
"tags": [
"read",
"dictionary",
"booklet",
"library",
"code",
"version control",
"git",
"repository"
],
"categories": [
"development",
"coding"
]
}

15
icons/book-marked.svg Normal file
View File

@@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="10,2 10,10 13,7 16,10 16,2" />
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
</svg>

After

Width:  |  Height:  |  Size: 384 B

15
icons/book-minus.json Normal file
View File

@@ -0,0 +1,15 @@
{
"$schema": "../icon.schema.json",
"tags": [
"code",
"version control",
"git",
"repository",
"remove",
"delete"
],
"categories": [
"development",
"coding"
]
}

15
icons/book-minus.svg Normal file
View File

@@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="9" y1="10" x2="15" y2="10"/>
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
</svg>

After

Width:  |  Height:  |  Size: 374 B

14
icons/book-plus.json Normal file
View File

@@ -0,0 +1,14 @@
{
"$schema": "../icon.schema.json",
"tags": [
"code",
"version control",
"git",
"repository",
"add"
],
"categories": [
"development",
"coding"
]
}

16
icons/book-plus.svg Normal file
View 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"
>
<line x1="12" y1="7" x2="12" y2="13"/>
<line x1="9" y1="10" x2="15" y2="10"/>
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
</svg>

After

Width:  |  Height:  |  Size: 415 B

15
icons/book-template.json Normal file
View File

@@ -0,0 +1,15 @@
{
"$schema": "../icon.schema.json",
"tags": [
"read",
"code",
"version control",
"git",
"repository",
"dashed"
],
"categories": [
"development",
"coding"
]
}

23
icons/book-template.svg Normal file
View File

@@ -0,0 +1,23 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M20,22h-2" />
<path d="M20,15v2l-2,0" />
<path d="M4,19.5V15" />
<path d="M20,8v3" />
<path d="M18,2h2v2" />
<path d="M4,11V9" />
<path d="M12,2h2" />
<path d="M12,22h2" />
<path d="M12,17h2" />
<path d="M8,22H6.5C5.1,22,4,20.9,4,19.5S5.1,17,6.5,17H8" />
<path d="M4,5V4.5C4,3.1,5.1,2,6.5,2H8" />
</svg>

After

Width:  |  Height:  |  Size: 536 B

14
icons/book-up.json Normal file
View File

@@ -0,0 +1,14 @@
{
"$schema": "../icon.schema.json",
"tags": [
"code",
"version control",
"git",
"repository",
"push"
],
"categories": [
"development",
"coding"
]
}

16
icons/book-up.svg Normal file
View 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 19.5A2.5 2.5 0 0 1 6.5 17H20" />
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2Z" />
<path d="M12,13V7" />
<path d="M9,10l3-3l3,3" />
</svg>

After

Width:  |  Height:  |  Size: 386 B

15
icons/book-x.json Normal file
View File

@@ -0,0 +1,15 @@
{
"$schema": "../icon.schema.json",
"tags": [
"code",
"version control",
"git",
"repository",
"remove",
"delete"
],
"categories": [
"development",
"coding"
]
}

16
icons/book-x.svg Normal file
View 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="M9.5,7l5,5" />
<path d="M14.5,7l-5,5" />
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
</svg>

After

Width:  |  Height:  |  Size: 387 B

View File

@@ -5,7 +5,8 @@
"command line",
"terminal",
"prompt",
"shell"
"shell",
"console"
],
"categories": [
"coding",

View File

@@ -0,0 +1,11 @@
{
"$schema": "../icon.schema.json",
"tags": [
"copy",
"paste"
],
"categories": [
"text",
"arrows"
]
}

15
icons/clipboard-paste.svg Normal file
View 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 2H9a1 1 0 0 0-1 1v2c0 .6.4 1 1 1h6c.6 0 1-.4 1-1V3c0-.6-.4-1-1-1Z" />
<path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2M16 4h2a2 2 0 0 1 2 2v2M11 14h10" />
<path d="m17 10 4 4-4 4" />
</svg>

After

Width:  |  Height:  |  Size: 425 B

13
icons/disc-2.json Normal file
View File

@@ -0,0 +1,13 @@
{
"$schema": "../icon.schema.json",
"tags": [
"album",
"vinyl",
"record",
"music"
],
"categories": [
"devices",
"multimedia"
]
}

15
icons/disc-2.svg Normal file
View 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"
>
<circle cx="12" cy="12" r="4" />
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="12" x2="12" y2="12.01"/>
</svg>

After

Width:  |  Height:  |  Size: 324 B

View File

@@ -10,5 +10,5 @@
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="10" />
<circle cx="12" cy="12" r="3" />
<circle cx="12" cy="12" r="2" />
</svg>

Before

Width:  |  Height:  |  Size: 279 B

After

Width:  |  Height:  |  Size: 279 B

View File

@@ -9,6 +9,6 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M20.42 4.58a5.4 5.4 0 0 0-7.65 0l-.77.78-.77-.78a5.4 5.4 0 0 0-7.65 0C1.46 6.7 1.33 10.28 4 13l8 8 8-8c2.67-2.72 2.54-6.3.42-8.42z" />
<path d="m12 13-1-1 2-2-3-2.5 2.77-2.92" />
<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z" />
<path d="m12 13-1-1 2-2-3-3 2-2" />
</svg>

Before

Width:  |  Height:  |  Size: 400 B

After

Width:  |  Height:  |  Size: 398 B

View File

@@ -9,8 +9,8 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M20.42 4.58a5.4 5.4 0 0 0-7.65 0l-.77.78-.77-.78a5.4 5.4 0 0 0-7.65 0C1.46 6.7 1.33 10.28 4 13l8 8 8-8c2.67-2.72 2.54-6.3.42-8.42z" />
<path d="M12 5.36 8.87 8.5a2.13 2.13 0 0 0 0 3h0a2.13 2.13 0 0 0 3 0l2.26-2.21a3 3 0 0 1 4.22 0l2.4 2.4" />
<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z" />
<path d="M12 5 9.04 7.96a2.17 2.17 0 0 0 0 3.08v0c.82.82 2.13.85 3 .07l2.07-1.9a2.82 2.82 0 0 1 3.79 0l2.96 2.66" />
<path d="m18 15-2-2" />
<path d="m15 18-2-2" />
</svg>

Before

Width:  |  Height:  |  Size: 516 B

After

Width:  |  Height:  |  Size: 531 B

View File

@@ -9,7 +9,7 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M4.12 4.107a5.4 5.4 0 0 0-.538.473C1.46 6.7 1.33 10.28 4 13l8 8 4.5-4.5" />
<path d="M19.328 13.672 20 13c2.67-2.72 2.54-6.3.42-8.42a5.4 5.4 0 0 0-7.65 0l-.77.78-.77-.78a5.4 5.4 0 0 0-2.386-1.393" />
<line x1="2" x2="22" y1="2" y2="22" />
<line x1="2" y1="2" x2="22" y2="22" />
<path d="M16.5 16.5 12 21l-7-7c-1.5-1.45-3-3.2-3-5.5a5.5 5.5 0 0 1 2.14-4.35" />
<path d="M8.76 3.1c1.15.22 2.13.78 3.24 1.9 1.5-1.5 2.74-2 4.5-2A5.5 5.5 0 0 1 22 8.5c0 2.12-1.3 3.78-2.67 5.17" />
</svg>

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 450 B

View File

@@ -9,6 +9,6 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M20.42 4.58a5.4 5.4 0 0 0-7.65 0l-.77.78-.77-.78a5.4 5.4 0 0 0-7.65 0C1.46 6.7 1.33 10.28 4 13l8 8 8-8c2.67-2.72 2.54-6.3.42-8.42z" />
<path d="M3.5 12h6l.5-1 2 4.5 2-7 1.5 3.5h5" />
<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z" />
<path d="M3.22 12H9.5l.5-1 2 4.5 2-7 1.5 3.5h5.27" />
</svg>

Before

Width:  |  Height:  |  Size: 404 B

After

Width:  |  Height:  |  Size: 416 B

View File

@@ -9,5 +9,5 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M20.42 4.58a5.4 5.4 0 0 0-7.65 0l-.77.78-.77-.78a5.4 5.4 0 0 0-7.65 0C1.46 6.7 1.33 10.28 4 13l8 8 8-8c2.67-2.72 2.54-6.3.42-8.42z" />
<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z" />
</svg>

Before

Width:  |  Height:  |  Size: 354 B

After

Width:  |  Height:  |  Size: 360 B

View File

@@ -6,7 +6,9 @@
"undo",
"rewind",
"timeline",
"version"
"version",
"time machine",
"backup"
],
"categories": [
"arrows",

View File

@@ -7,6 +7,7 @@
"bake"
],
"categories": [
"food-beverage"
"food-beverage",
"home"
]
}
}

11
icons/orbit.json Normal file
View File

@@ -0,0 +1,11 @@
{
"$schema": "../icon.schema.json",
"tags": [
"planet",
"space",
"physics"
],
"categories": [
"science"
]
}

17
icons/orbit.svg Normal file
View 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"
>
<circle cx="12" cy="12" r="3" />
<circle cx="18" cy="6" r="2" />
<circle cx="6" cy="18" r="2" />
<path d="M18.8,7.8c0.2,0.3,0.4,0.7,0.5,1.1c0.4,1,0.6,2,0.6,3.1s-0.2,2.2-0.6,3.1c-0.4,1-1,1.8-1.7,2.5 s-1.6,1.3-2.5,1.7c-1,0.4-2,0.6-3.1,0.6" />
<path d="M5.2,16.2c-0.2-0.3-0.4-0.7-0.5-1.1c-0.4-1-0.6-2-0.6-3.1s0.2-2.2,0.6-3.1c0.4-1,1-1.8,1.7-2.5S7.9,5,8.9,4.6 c1-0.4,2-0.6,3.1-0.6" />
</svg>

After

Width:  |  Height:  |  Size: 599 B

View File

@@ -4,7 +4,9 @@
"on",
"off",
"device",
"switch"
"switch",
"reboot",
"restart"
],
"categories": [
"connectivity"

15
icons/rat.json Normal file
View File

@@ -0,0 +1,15 @@
{
"$schema": "../icon.schema.json",
"tags": [
"animal",
"mouse",
"mice",
"rodent",
"pet",
"pest",
"plague"
],
"categories": [
"animals"
]
}

18
icons/rat.svg Normal file
View File

@@ -0,0 +1,18 @@
<svg
width="24"
height="24"
viewBox="0 0 24 24"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M14 16.5v-.77a2.73 2.73 0 0 1 3.27-2.68l3.02.6a1.4 1.4 0 0 0 1.5-2.08l-1.63-2.8a3 3 0 1 0-3.35-4.82" />
<path d="M16 9h-.01" />
<path d="M17 5.12V5a3 3 0 1 0-5.24 2h-1A6.77 6.77 0 0 0 4 13.77C4 16.1 5.9 18 8.23 18H9" />
<path d="M13 22H4a2 2 0 1 1 0-4h12" />
<path d="M13.67 18c.21-.44.33-.94.33-1.45A3.65 3.65 0 0 0 10.26 13" />
</svg>

After

Width:  |  Height:  |  Size: 604 B

View File

@@ -2,7 +2,13 @@
"$schema": "../icon.schema.json",
"tags": [
"arrow",
"left"
"left",
"counter-clockwise",
"restart",
"reload",
"refresh",
"backup",
"undo"
],
"categories": [
"arrows",

View File

@@ -2,7 +2,11 @@
"$schema": "../icon.schema.json",
"tags": [
"arrow",
"right"
"right",
"clockwise",
"refresh",
"reload",
"redo"
],
"categories": [
"arrows",

14
icons/step-back.json Normal file
View File

@@ -0,0 +1,14 @@
{
"$schema": "../icon.schema.json",
"tags": [
"arrow",
"previous",
"music",
"left",
"reverse"
],
"categories": [
"multimedia",
"arrows"
]
}

14
icons/step-back.svg Normal file
View File

@@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="18" x2="18" y1="20" y2="4" />
<polygon points="14,20 4,12 14,4" />
</svg>

After

Width:  |  Height:  |  Size: 289 B

14
icons/step-forward.json Normal file
View File

@@ -0,0 +1,14 @@
{
"$schema": "../icon.schema.json",
"tags": [
"arrow",
"next",
"music",
"right",
"continue"
],
"categories": [
"multimedia",
"arrows"
]
}

14
icons/step-forward.svg Normal file
View File

@@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="6" x2="6" y1="4" y2="20" />
<polygon points="10,4 20,12 10,20" />
</svg>

After

Width:  |  Height:  |  Size: 288 B

16
icons/text.json Normal file
View File

@@ -0,0 +1,16 @@
{
"$schema": "../icon.schema.json",
"tags": [
"find",
"search",
"data",
"txt",
"pdf",
"document"
],
"categories": [
"text",
"files",
"cursors"
]
}

15
icons/text.svg Normal file
View File

@@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M17 6.1H3" />
<path d="M21 12.1H3" />
<path d="M15.1 18H3" />
</svg>

After

Width:  |  Height:  |  Size: 285 B

View File

@@ -1,7 +1,7 @@
{
"name": "lucide-angular",
"description": "A Lucide icon library package for Angular applications",
"version": "0.134.0",
"version": "0.144.0",
"author": "SMAH1",
"license": "ISC",
"homepage": "https://lucide.dev",

View File

@@ -1,6 +1,6 @@
name: lucide_icons
description: A Lucide icon library package for Flutter applications. Fork of Feather Icons, open for anyone to contribute icons.
version: 0.134.0
version: 0.144.0
homepage: https://lucide.dev
repository: https://github.com/lucide-icons/lucide

View File

@@ -1,7 +1,7 @@
{
"name": "lucide-preact",
"description": "A Lucide icon library package for Preact applications",
"version": "0.134.0",
"version": "0.144.0",
"license": "ISC",
"homepage": "https://lucide.dev",
"bugs": "https://github.com/lucide-icons/lucide/issues",

View File

@@ -1,7 +1,7 @@
{
"name": "lucide-react-native",
"description": "A Lucide icon library package for React Native applications",
"version": "0.134.0",
"version": "0.144.0",
"license": "ISC",
"homepage": "https://lucide.dev",
"bugs": "https://github.com/lucide-icons/lucide/issues",

View File

@@ -1,7 +1,7 @@
{
"name": "lucide-react",
"description": "A Lucide icon library package for React applications",
"version": "0.134.0",
"version": "0.144.0",
"license": "ISC",
"homepage": "https://lucide.dev",
"bugs": "https://github.com/lucide-icons/lucide/issues",

View File

@@ -1,7 +1,7 @@
import { forwardRef, createElement, ReactSVG, SVGProps } from 'react';
import defaultAttributes from './defaultAttributes';
type IconNode = [elementName: keyof ReactSVG, attrs: Record<string, string>][]
export type IconNode = [elementName: keyof ReactSVG, attrs: Record<string, string>][]
export type SVGAttributes = Partial<SVGProps<SVGSVGElement>>

View File

@@ -1,7 +1,7 @@
{
"name": "lucide-svelte",
"description": "A Lucide icon library package for Svelte applications",
"version": "0.134.0",
"version": "0.144.0",
"license": "ISC",
"homepage": "https://lucide.dev",
"bugs": "https://github.com/lucide-icons/lucide/issues",

View File

@@ -1,6 +1,6 @@
{
"name": "lucide-vue-next",
"version": "0.134.0",
"version": "0.144.0",
"author": "Eric Fennis",
"description": "A Lucide icon library package for Vue 3 applications",
"license": "ISC",

View File

@@ -1,6 +1,6 @@
{
"name": "lucide-vue",
"version": "0.134.0",
"version": "0.144.0",
"author": "Eric Fennis",
"description": "A Lucide icon library package for Vue 2 applications",
"license": "ISC",

View File

@@ -1,7 +1,7 @@
{
"name": "lucide",
"description": "A Lucide icon library package for web and javascript applications.",
"version": "0.134.0",
"version": "0.144.0",
"license": "ISC",
"homepage": "https://lucide.dev",
"bugs": "https://github.com/lucide-icons/lucide/issues",

353
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,7 @@ module.exports = {
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@next/next/recommended',
'plugin:react-hooks/recommended'
],
parserOptions: {
tsconfigRootDir: __dirname,

View File

@@ -6,7 +6,7 @@
"scripts": {
"dev": "next dev",
"copy-assets": "mkdir -p ./public/docs/images && cp -rf ../docs/images ./public/docs",
"prebuild": "ts-node --swc scripts/preBuild.ts",
"prebuild": "ts-node --swc scripts/preBuild.ts && pnpm -w lucide-react build",
"build": "pnpm copy-assets && pnpm prebuild && next build",
"export": "next export -o build",
"deploy": "pnpm build && pnpm export",
@@ -24,16 +24,17 @@
"@next/mdx": "^11.0.0",
"@svgr/webpack": "^6.3.1",
"downloadjs": "^1.4.7",
"framer-motion": "^6.2.8",
"element-to-path": "^1.2.1",
"framer-motion": "^4",
"fuse.js": "^6.5.3",
"gray-matter": "^4.0.3",
"js-yaml": "^4.1.0",
"jszip": "^3.7.0",
"lodash": "^4.17.20",
"lucide-react": "^0.94.0",
"lucide-react": "workspace:*",
"next": "12",
"next-mdx-remote": "^3.0.2",
"object-path": "0.11.5",
"prism-react-renderer": "^1.2.1",
"react": "17.0.2",
"react-color": "^2.19.3",
@@ -59,6 +60,7 @@
"babel-loader": "^8.1.0",
"eslint": "^8.22.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^26.5.2",
"node-fetch": "2",
"prettier": "^2.3.2",

View File

@@ -17,3 +17,7 @@ body {
min-height: 100%;
padding-bottom: 80px;
}
html:has(*:target) {
scroll-behavior: smooth;
}

View File

@@ -0,0 +1,123 @@
import {
Box,
Button,
Flex,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Text,
Divider,
} from '@chakra-ui/react';
import theme from '../lib/theme';
import { useMemo, useState } from 'react';
import CodeBlock from './CodeBlock';
import CopyButton from './CopyButton';
import { IconEntity } from 'src/types';
const CategoryChangesBar = ({ categories, changes }) => {
const [modalOpen, setModalOpen] = useState(false);
const [showCode, setShowCode] = useState(false);
const handleSubmit = () => {
setModalOpen(true);
};
const openPullRequestUrl = 'https://github.com/lucide-icons/lucide/edit/master/categories.json';
const onClose = () => {
setModalOpen(false);
};
const newMappedCategories = useMemo(() => {
return Object.fromEntries(
Object.entries(categories).map(([category, icons]) => [
category,
(icons as IconEntity[]).map(({ name }) => name),
]),
);
}, [categories]);
const categoryCode = useMemo(() => JSON.stringify(newMappedCategories, null, ' '), [
newMappedCategories,
]);
return (
<>
<Box
borderWidth="1px"
rounded="lg"
width="full"
boxShadow={theme.shadows.xl}
position="relative"
padding={4}
maxW="960px"
margin="0 auto"
>
<Flex alignItems="center" justifyContent="space-between">
<Text fontSize="lg">You're editing the overview categories.</Text>
<Flex>
<Text fontSize="lg" fontWeight="bold" marginLeft={4}>
{changes}
</Text>
<Text fontSize="lg" marginLeft={2}>
changes made to 'categories.json'
</Text>
</Flex>
<Button onClick={handleSubmit} colorScheme="red" variant="solid">
Submit Pull-request
</Button>
</Flex>
</Box>
<Modal isOpen={modalOpen} onClose={onClose} size="xl">
<ModalOverlay />
<ModalContent>
<ModalHeader>Nice changes!</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Text>To submit those changes, follow these steps:</Text>
<Text fontWeight="bold" mt={4}>
Step 1:
</Text>
<Text mt={2} mb={2}>
Copy all the code,
</Text>
<CopyButton copyText={categoryCode} buttonText="Copy code" mr={4} />
<Text fontWeight="bold" mt={6}>
Step 2:
</Text>
<Text mt={2} mb={4}>
Open Pull-request, select-all and paste the code with the changes.
</Text>
<Button as="a" variant="solid" href={openPullRequestUrl} target="__blank">
Open pull-request
</Button>
<Divider mt={8} mb={4} />
<Text fontWeight="bold" mt={6} mb={4}>
Code:
</Text>
<Button as="a" variant="solid" onClick={() => setShowCode(show => !show)}>
Show code
</Button>
{showCode && (
<Box maxHeight={320} overflow="auto" background="gray.800">
<CodeBlock code={categoryCode} language="json" showLines />
</Box>
)}
</ModalBody>
<ModalFooter>
<Button colorScheme="blue" mr={3} onClick={onClose}>
Done
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
};
export default CategoryChangesBar;

View File

@@ -2,7 +2,6 @@ import { Box, BoxProps, chakra, useColorMode } from '@chakra-ui/react';
import nightOwlLightTheme from 'prism-react-renderer/themes/nightOwlLight';
import nightOwlDarkTheme from 'prism-react-renderer/themes/nightOwl';
import uiTheme from '../lib/theme';
// import theme from 'prism-react-renderer/themes/nightOwl';
import BaseHighlight, { defaultProps, Language } from 'prism-react-renderer';
import { CSSProperties } from 'react';
import CopyButton from './CopyButton';
@@ -49,7 +48,7 @@ function CodeBlock({ code, language, metastring, showLines, ...props }: Highligh
const { colorMode } = useColorMode();
const backgroundColor =
colorMode === 'light' ? uiTheme.colors.gray[100] : uiTheme.colors.gray[700];
colorMode === 'light' ? uiTheme.colors.gray[100] : uiTheme.colors.gray[900];
const codeTheme = colorMode === 'light' ? nightOwlLightTheme : nightOwlDarkTheme;
const customizedCodeTheme = {
@@ -63,6 +62,8 @@ function CodeBlock({ code, language, metastring, showLines, ...props }: Highligh
return (
<Box position="relative" zIndex="0" {...props}>
<CodeContainer bg={backgroundColor}>
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
{/* @ts-ignore */}
<BaseHighlight
{...defaultProps}
code={code}

View File

@@ -4,7 +4,7 @@ const CopyButton = ({ copyText, buttonText = 'copy', ...props }) => {
const { hasCopied, onCopy } = useClipboard(copyText);
return (
<Button onClick={onCopy} {...props}>
<Button onClick={onCopy} {...props} variant="solid">
{hasCopied ? 'copied' : buttonText}
</Button>
);

View File

@@ -59,10 +59,21 @@ export function CustomizeIconContext({ children }): JSX.Element {
return <IconStyleContext.Provider value={value}>{children}</IconStyleContext.Provider>;
}
export function useCustomizeIconContext(): ICustomIconStyle {
export function useCustomizeIconContext() {
const context = useContext(IconStyleContext);
if (context === undefined) {
throw new Error('useCustomizeIconContext must be used within a IconStyleContextProvider');
return {
color: 'currentColor',
size: 24,
strokeWidth: 2,
iconsRef: { current: {} },
/* eslint-disable @typescript-eslint/no-empty-function */
setStroke: function() {},
setColor: function() {},
setSize: function() {},
resetStyle: function() {},
/* eslint-enable @typescript-eslint/no-empty-function */
};
}
return context;
}

View File

@@ -1,4 +1,4 @@
import { Button, Flex, Link, WrapItem, Text, Wrap, Heading } from '@chakra-ui/react';
import { Button, Flex, Link, WrapItem, Text, Wrap, Heading, Box } from '@chakra-ui/react';
import download from 'downloadjs';
import { Download, Github } from 'lucide-react';
import NextLink from 'next/link';
@@ -11,25 +11,10 @@ import PreactLogo from '../../public/framework-logos/preact.svg';
import AngularLogo from '../../public/framework-logos/angular.svg';
import FlutterLogo from '../../public/framework-logos/flutter.svg';
import SvelteLogo from '../../public/framework-logos/svelte.svg';
import { useState } from 'react';
import { useCallback, useState } from 'react';
import { useCustomizeIconContext } from './CustomizeIconContext';
import { IconEntity } from '../types';
type IconContent = [icon: string, src:string];
async function generateZip(icons: IconContent[]) {
const JSZip = (await import('jszip')).default
const zip = new JSZip();
const addingZipPromises = icons.map(([name, src]) =>
zip.file(`${name}.svg`, src),
);
await Promise.all(addingZipPromises)
return zip.generateAsync({ type: 'blob' });
}
import generateZip, { IconContent } from 'src/lib/generateZip';
interface HeaderProps {
data: IconEntity[];
@@ -37,25 +22,27 @@ interface HeaderProps {
const Header = ({ data }: HeaderProps) => {
const [zippingIcons, setZippingIcons] = useState(false);
const { iconsRef } = useCustomizeIconContext();
const { iconsRef, strokeWidth, color, size } = useCustomizeIconContext();
const downloadAllIcons = async () => {
const downloadAllIcons = useCallback(async () => {
setZippingIcons(true);
let iconEntries: IconContent[] = Object.entries(iconsRef.current).map(([name, svgEl]) => [
let iconEntries: IconContent[] = Object.entries(iconsRef.current)
.map(([name, svgEl]) => [
name,
svgEl.outerHTML,
]);
// Fallback
if (iconEntries.length === 0) {
iconEntries = data.map(icon => [icon.name, icon.src]);
const getFallbackZip = (await import('../lib/getFallbackZip')).default
iconEntries = getFallbackZip(data, { strokeWidth, color, size })
}
const zip = await generateZip(iconEntries);
download(zip, 'lucide.zip');
setZippingIcons(false);
};
}, []);
const repositoryUrl = 'https://github.com/lucide-icons/lucide';
@@ -117,83 +104,85 @@ const Header = ({ data }: HeaderProps) => {
];
return (
<Flex direction="column" align="center" justify="center">
<Heading as="h1" fontSize="4xl" mb="4" textAlign="center">
Beautiful &amp; consistent icon toolkit made by the community.
</Heading>
<Text fontSize="lg" as="p" textAlign="center" mb="1">
Open-source project and 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>
<Wrap
marginTop={4}
marginBottom={6}
spacing={{ base: 4, lg: 6 }}
justify="center"
align="center"
>
<WrapItem flexBasis="100%" style={{ marginBottom: 0 }}>
<NextLink href="/packages" passHref>
<Link _hover={{ opacity: 0.8 }} marginX="auto">
<Text fontSize="md" opacity={0.5} as="p" textAlign="center" width="100%">
Available for:
</Text>
</Link>
</NextLink>
</WrapItem>
{packages.map(({ name, href, Logo, label }) => (
<WrapItem key={name}>
<NextLink href={href} key={name} passHref>
<Link _hover={{ opacity: 0.8 }} aria-label={label} title={label}>
<Logo />
<Box maxW="1250px" mx="auto">
<Flex direction="column" align="center" justify="center" py={12}>
<Heading as="h1" fontSize="4xl" mb="4" textAlign="center">
Beautiful &amp; consistent icon toolkit made by the community.
</Heading>
<Text fontSize="lg" as="p" textAlign="center" mb="1">
Open-source project and 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>
<Wrap
marginTop={4}
marginBottom={6}
spacing={{ base: 4, lg: 6 }}
justify="center"
align="center"
>
<WrapItem flexBasis="100%" style={{ marginBottom: 0 }}>
<NextLink href="/packages" passHref>
<Link _hover={{ opacity: 0.8 }} marginX="auto">
<Text fontSize="md" opacity={0.5} as="p" textAlign="center" width="100%">
Available for:
</Text>
</Link>
</NextLink>
</WrapItem>
))}
<WrapItem>
<NextLink href="/packages" passHref>
<Link _hover={{ opacity: 0.8 }} marginX="auto">
<Text fontSize="md" opacity={0.5}>More options</Text>
</Link>
</NextLink>
</WrapItem>
</Wrap>
<Wrap marginTop={3} marginBottom={12} spacing="15px" justify="center">
<WrapItem>
<Button
leftIcon={<Download />}
size="lg"
onClick={downloadAllIcons}
isLoading={zippingIcons}
loadingText="Creating zip.."
>
Download all
</Button>
</WrapItem>
<WrapItem>
<IconCustomizerDrawer />
</WrapItem>
<WrapItem>
<Button
as="a"
leftIcon={<Github />}
size="lg"
href={repositoryUrl}
target="__blank"
>
Github
</Button>
</WrapItem>
</Wrap>
</Flex>
{packages.map(({ name, href, Logo, label }) => (
<WrapItem key={name}>
<NextLink href={href} key={name} passHref>
<Link _hover={{ opacity: 0.8 }} aria-label={label}>
<Logo />
</Link>
</NextLink>
</WrapItem>
))}
<WrapItem>
<NextLink href="/packages" passHref>
<Link _hover={{ opacity: 0.8 }} marginX="auto">
<Text fontSize="md" opacity={0.5}>More options</Text>
</Link>
</NextLink>
</WrapItem>
</Wrap>
<Wrap marginTop={3} marginBottom={12} spacing="15px" justify="center">
<WrapItem>
<Button
leftIcon={<Download />}
size="lg"
onClick={downloadAllIcons}
isLoading={zippingIcons}
loadingText="Creating zip.."
>
Download all
</Button>
</WrapItem>
<WrapItem>
<IconCustomizerDrawer />
</WrapItem>
<WrapItem>
<Button
as="a"
leftIcon={<Github />}
size="lg"
href={repositoryUrl}
target="__blank"
>
Github
</Button>
</WrapItem>
</Wrap>
</Flex>
</Box>
);
};

View File

@@ -0,0 +1,45 @@
import { Box, Text, useColorModeValue, BoxProps } from '@chakra-ui/react';
import { forwardRef } from 'react';
import theme from '../lib/theme';
interface IconCategoryProps extends BoxProps {
active?: boolean;
name: string;
dragging: boolean;
}
const IconCategory = forwardRef<HTMLDivElement, IconCategoryProps>(
({ name, active = false, dragging, children, ...props }: IconCategoryProps, ref) => {
const activeBackground = useColorModeValue(theme.colors.gray, theme.colors.gray[700]);
const toTitleCase = string =>
string
.split(' ')
.map(word => word[0].toUpperCase() + word.slice(1))
.join(' ');
return (
<Box
backgroundColor={active ? activeBackground : 'transparent'}
borderRadius={8}
ref={ref}
pointerEvents="all"
padding={4}
marginBottom={3}
transition="background 120ms ease-in"
_hover={{
background: dragging && activeBackground,
}}
{...props}
>
<Box>
<Text fontSize="xl" marginBottom={3}>
{toTitleCase(name)}
</Text>
{children}
</Box>
</Box>
);
},
);
export default IconCategory;

View File

@@ -0,0 +1,168 @@
import { Box, BoxProps, Button, Divider, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerHeader, DrawerOverlay, useBreakpointValue, useTheme, useColorModeValue } from "@chakra-ui/react"
import { motion } from "framer-motion"
import { Fragment, useMemo } from "react"
import {Category, IconEntity} from "src/types"
import {createLucideIcon} from "lucide-react";
const ListWrapper = ({ children, ...restProps }: BoxProps) => {
return (
<Box
w="full"
h="full"
overflowY="auto"
sx={{
direction: 'rtl',
'&::-webkit-scrollbar' : {
width: '4px',
},
'&::-webkit-scrollbar-track' : {
background: 'transparent',
},
'&::-webkit-scrollbar-thumb' : {
bgColor: useColorModeValue('var(--chakra-colors-gray-300)', 'var(--chakra-colors-whiteAlpha-300)'),
borderRadius: 0,
},
}}
{...restProps}
>
<Box
whiteSpace="nowrap"
height="100%"
display="flex"
flexDirection="column"
paddingBottom={8}
paddingX={2}
paddingY={1}
paddingRight={4}
sx={{
direction: 'ltr'
}}
>
{ children }
</Box>
</Box>
)
}
const CATEGORY_TOP_OFFSET = 100
interface IconCategoryDrawerProps {
data: IconEntity[];
categories: Category[]
setCategoryView: (view: boolean) => void
open: boolean
onClose: () => void
}
const IconCategoryDrawer = ({ open, onClose, categories, data, setCategoryView }: IconCategoryDrawerProps) => {
const theme = useTheme()
const useCustomDrawer = useBreakpointValue({ base: false, md: true });
const sidebarVariants = {
closed: {
width: 0,
},
open: {
width: theme.sizes['xs']
}
}
const categoryList = useMemo(() => {
return (
<>
{[{ name: 'all', title: 'All', icon: null, iconCount: data.length }, ...categories].map(({ title, name, icon, iconCount }) => {
// Show category icon?
const iconData = data.find(({ name: iconName }) => iconName === icon)
const Icon = iconData ? createLucideIcon(iconData.name, iconData.iconNode) : null
return (
<Fragment key={name}>
<Button
as="a"
colorScheme='gray'
variant='ghost'
width="100%"
justifyContent="space-between"
leftIcon={Icon ? <Icon /> : null}
onClick={(event) => {
event.stopPropagation()
if (!useCustomDrawer) {
onClose()
const [routePath] = window.location.href.split('#')
setTimeout(() =>{
window.location.href = `${routePath}#${name}`
},150)
}
setCategoryView(name !== 'all')
}}
href={useCustomDrawer ? `#${name}` : undefined}
marginBottom={1}
sx={{
opacity: .7,
flexShrink: 0,
'&.active': {
color: 'brand.500'
}
}}
>
<Box width='100%'>{title}</Box>
<Box sx={{opacity: .5}}><small>{iconCount}</small></Box>
</Button>
{name === 'all' && (
<Divider marginY={2} />
)}
</Fragment>
)
})}
<Box h={8} flexShrink={0}></Box>
</>
)
}, [categories, useCustomDrawer])
if(useCustomDrawer) {
return (
<motion.div
variants={sidebarVariants}
animate={(open ?? useCustomDrawer) ? 'open' : 'closed'}
initial={false}
style={{
height: `calc(100vh - ${CATEGORY_TOP_OFFSET}px)`,
position: 'sticky',
top: '100px'
}}
>
<ListWrapper>
{categoryList}
</ListWrapper>
</motion.div>
)
}
return (
<Drawer
placement="left"
onClose={onClose}
isOpen={open}
size="sm"
blockScrollOnMount={false}
>
<DrawerOverlay />
<DrawerContent>
<DrawerCloseButton marginTop={3.5} marginRight={3} />
<DrawerHeader />
<DrawerBody>
<ListWrapper>
{categoryList}
</ListWrapper>
</DrawerBody>
</DrawerContent>
</Drawer>
)
}
export default IconCategoryDrawer

View File

@@ -0,0 +1,78 @@
import { Box, BoxProps, Stack, Text, useColorModeValue } from '@chakra-ui/react';
import { useMemo } from 'react';
import { Category, IconEntity } from 'src/types';
import theme from '../lib/theme';
import IconList from './IconList';
interface IconCategoryProps {
icons: IconEntity[]
data: IconEntity[]
categories: Category[]
categoryProps?: {
innerProps: BoxProps,
activeCategory: string | null
}
}
const IconCategory = ({
icons,
data,
categories = [],
categoryProps = {
innerProps: {},
activeCategory: null,
},
}: IconCategoryProps) => {
const { innerProps, activeCategory, ...outerProps } = categoryProps;
const activeBackground = useColorModeValue(theme.colors.gray, theme.colors.gray[700]);
const iconCategories = useMemo(
() =>
categories.reduce((categoryMap, { name, title }) => {
const categoryIcons = data.filter(({categories}) => categories.includes(name))
const isSearching = icons.length !== data.length;
const searchResults = isSearching
? categoryIcons.filter(icon => icons.some((item) => item?.name === icon?.name))
: categoryIcons;
categoryMap.push({
title,
name,
icons: searchResults,
isActive: name === activeCategory,
});
return categoryMap;
}, []),
[icons, categories, activeCategory],
);
return (
<Stack spacing={4}>
{iconCategories
.filter(({ icons }) => icons.length)
.map(({ name, title, icons, isActive }) => (
<Box
key={name}
backgroundColor={isActive ? activeBackground : 'transparent'}
borderRadius={8}
{...outerProps}
>
<Box {...innerProps || {}}>
<Text fontSize="xl" marginBottom={3} id={name} sx={{
'&:target': {
scrollMarginTop: 20
}
}}>
{title}
</Text>
<IconList icons={icons} category={name}/>
</Box>
</Box>
))}
</Stack>
);
};
export default IconCategory;

View File

@@ -1,5 +1,5 @@
import { useContext, useState } from 'react';
import { IconStyleContext } from './CustomizeIconContext';
import { useState } from 'react';
import { useCustomizeIconContext } from './CustomizeIconContext';
import { Edit } from 'lucide-react';
import {
Button,
@@ -8,30 +8,56 @@ import {
DrawerCloseButton,
DrawerContent,
DrawerHeader,
DrawerOverlay,
FormControl,
FormLabel,
Grid,
Hide,
IconButton,
Show,
Slider,
SliderFilledTrack,
SliderThumb,
SliderTrack,
Flex,
Text,
ButtonProps,
} from '@chakra-ui/react';
import ColorPicker from './ColorPicker';
export function IconCustomizerDrawer() {
export const IconCustomizerDrawer = (props: ButtonProps) => {
const [showCustomize, setShowCustomize] = useState(false);
const { color, setColor, size, setSize, strokeWidth, setStroke, resetStyle } = useContext(IconStyleContext);
const {
color,
setColor,
size,
setSize,
strokeWidth,
setStroke,
resetStyle,
} = useCustomizeIconContext();
return (
<>
<Button as="a" leftIcon={<Edit />} size="lg" onClick={() => setShowCustomize(true)}>
Customize
</Button>
<Drawer isOpen={showCustomize} placement="right" onClose={() => setShowCustomize(false)}>
<DrawerOverlay />
<Hide below='md'>
<Button
as="a"
leftIcon={<Edit />}
size="lg"
onClick={() => setShowCustomize(true)}
{...props}
>Customize</Button>
</Hide>
<Show below='md'>
<IconButton
aria-label='Customize'
variant="solid"
color="current"
onClick={() => setShowCustomize(true)}
icon={<Edit />}
{...props}
></IconButton>
</Show>
<Drawer isOpen={showCustomize} placement="right" onClose={() => setShowCustomize(false)} size="md">
<DrawerContent>
<DrawerCloseButton />
<DrawerHeader>Customize Icons</DrawerHeader>
@@ -42,7 +68,7 @@ export function IconCustomizerDrawer() {
<ColorPicker
color={color}
value={color}
onChangeComplete={(col) => setColor(col.hex)}
onChangeComplete={col => setColor(col.hex)}
/>
</FormControl>
<FormControl>
@@ -93,4 +119,4 @@ export function IconCustomizerDrawer() {
</Drawer>
</>
);
}
};

View File

@@ -2,11 +2,11 @@ import { Box, Text, IconButton, useColorMode, Flex, Slide, ButtonGroup, Button,
import theme from "../lib/theme";
import download from 'downloadjs';
import { X as Close } from 'lucide-react';
import {useContext, useEffect, useRef} from "react";
import {IconStyleContext} from "./CustomizeIconContext";
import {IconWrapper} from "./IconWrapper";
import { useEffect, useRef } from "react";
import {useCustomizeIconContext} from "./CustomizeIconContext";
import ModifiedTooltip from "./ModifiedTooltip";
import { IconEntity } from "../types";
import { createLucideIcon } from 'lucide-react';
type IconDownload = {
src: string;
@@ -14,15 +14,17 @@ type IconDownload = {
};
interface IconDetailOverlayProps {
open: boolean
close: () => void
open?: boolean
close?: () => void
icon?: IconEntity
}
const IconDetailOverlay = ({ open = true, close, icon }: IconDetailOverlayProps) => {
const toast = useToast();
const { colorMode } = useColorMode();
const {color, strokeWidth, size} = useContext(IconStyleContext);
const { tags = [], name, iconNode } = icon ?? {};
const {color, strokeWidth, size} = useCustomizeIconContext();
const iconRef = useRef<SVGSVGElement>(null);
const [isMobile] = useMediaQuery("(max-width: 560px)")
const { isOpen, onOpen, onClose } = useDisclosure()
@@ -37,8 +39,6 @@ const IconDetailOverlay = ({ open = true, close, icon }: IconDetailOverlayProps)
return null
}
const { tags = [], name = '' } = icon;
const handleClose = () => {
onClose();
close();
@@ -54,10 +54,12 @@ const IconDetailOverlay = ({ open = true, close, icon }: IconDetailOverlayProps)
color: color,
};
const downloadIcon = ({src, name = ''} : IconDownload) => download(src, `${name}.svg`, 'image/svg+xml');
const Icon = createLucideIcon(name, iconNode)
const copyIcon = async ({src, name} : IconDownload) => {
const trimmedSrc = src.replace(/(\r\n|\n|\r|\s\s)/gm, "")
const downloadIcon = ({name = ''} : IconDownload) => download(iconRef.current.outerHTML, `${name}.svg`, 'image/svg+xml');
const copyIcon = async ({name} : IconDownload) => {
const trimmedSrc = iconRef.current.outerHTML.replace(/(\r\n|\n|\r|\s\s)/gm, "")
await navigator.clipboard.writeText(trimmedSrc)
@@ -70,14 +72,14 @@ const IconDetailOverlay = ({ open = true, close, icon }: IconDetailOverlayProps)
});
}
const downloadPNG = ({src, name}: IconDownload) => {
const downloadPNG = ({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.src = `data:image/svg+xml;base64,${btoa(iconRef.current.outerHTML)}`;
image.onload = function() {
ctx.drawImage(image, 0, 0);
@@ -98,7 +100,7 @@ const IconDetailOverlay = ({ open = true, close, icon }: IconDetailOverlayProps)
height={0}
key={name}
>
<Slide direction="bottom" in={isOpen} style={{ zIndex: 10 }}>
<Slide direction="bottom" in={isOpen} style={{ zIndex: 10, pointerEvents: 'none' }}>
<Flex
alignItems="center"
justifyContent="space-between"
@@ -120,6 +122,7 @@ const IconDetailOverlay = ({ open = true, close, icon }: IconDetailOverlayProps)
? theme.colors.white
: theme.colors.gray[700]
}
style={{ pointerEvents: 'initial' }}
padding={8}
>
<IconButton
@@ -151,14 +154,13 @@ const IconDetailOverlay = ({ open = true, close, icon }: IconDetailOverlayProps)
style={iconStyling}
className="icon-large"
>
<IconWrapper
src={icon.src}
<Icon
stroke={color}
strokeWidth={strokeWidth}
height={size}
width={size}
size={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">

View File

@@ -1,35 +1,19 @@
import { Grid } from '@chakra-ui/react';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { memo } from 'react';
import IconListItem from './IconListItem';
import { IconEntity } from '../types';
interface IconListProps {
icons: IconEntity[];
category?: string
}
const IconList = memo(({ icons }: IconListProps) => {
const router = useRouter();
const IconList = memo(({ icons, category = '' }: IconListProps) => {
return (
<Grid templateColumns={`repeat(auto-fill, minmax(150px, 1fr))`} gap={5} marginBottom="320px">
<Grid templateColumns={`repeat(auto-fill, minmax(80px, 1fr))`} gap={5} marginBottom={6} justifyItems={'center'}>
{icons.map(icon => {
return (
<Link
key={icon.name}
scroll={false}
shallow={true}
href={{
pathname: '/icon/[iconName]',
query: {
...router.query,
iconName: icon.name,
},
}}
>
<IconListItem {...icon} />
</Link>
<IconListItem {...icon} key={`${category}-${icon.name}`}/>
);
})}
</Grid>

View File

@@ -1,21 +1,25 @@
import { Button, ButtonProps, Flex, Text, useToast } from '@chakra-ui/react';
import { Button, ButtonProps, Tooltip, useToast } from '@chakra-ui/react';
import download from 'downloadjs';
import { memo } from 'react';
import { memo, useCallback } from 'react';
import { createLucideIcon, IconNode } from 'lucide-react';
import { useCustomizeIconContext } from './CustomizeIconContext';
import { IconWrapper } from './IconWrapper';
import { useRouter } from 'next/router';
interface IconListItemProps {
interface IconListItemProps extends ButtonProps {
name: string;
onClick?: ButtonProps['onClick']
src: string;
iconNode: IconNode;
}
const IconListItem = ({ name, onClick, src: svg }: IconListItemProps) => {
const IconListItem = ({ name, iconNode }: IconListItemProps) => {
const router = useRouter()
const toast = useToast();
const { color, size, strokeWidth, iconsRef } = useCustomizeIconContext();
const handleClick:ButtonProps['onClick'] = async (event) => {
const src = (iconsRef.current[name].outerHTML ?? svg).replace(/(\r\n|\n|\r|(>\s\s<))/gm, "")
const Icon = createLucideIcon(name, iconNode)
const handleClick:ButtonProps['onClick'] = useCallback(async (event) => {
const src = (iconsRef.current[name].outerHTML).replace(/(\r\n|\n|\r|(>\s\s<))/gm, "")
if (event.shiftKey) {
await navigator.clipboard.writeText(src)
@@ -25,46 +29,53 @@ const IconListItem = ({ name, onClick, src: svg }: IconListItemProps) => {
status: 'success',
duration: 1500,
});
return
}
if (event.altKey) {
download(src, `${name}.svg`, 'image/svg+xml');
return
}
if (onClick) {
onClick(event);
}
}
router.push({
pathname: `/icon/${name}`,
query: (
router.query?.search != null
? { search: router.query.search }
: {}
),
},
undefined,
{
shallow: true,
scroll: false
})
}, [iconsRef, name, router, toast])
return (
<Button
variant="ghost"
borderWidth="1px"
rounded="lg"
padding={2}
height={32}
position="relative"
whiteSpace="normal"
onClick={handleClick}
key={name}
alignItems="center"
>
<Flex direction="column" align="center" justify="stretch" width="100%" gap={4}>
<Flex flex={2} flexBasis="100%" minHeight={10} align="flex-end">
<IconWrapper
src={svg}
stroke={color}
strokeWidth={strokeWidth}
height={size}
width={size}
ref={iconEl => (iconsRef.current[name] = iconEl)}
/>
</Flex>
<Flex flex={1} minHeight={10} align="center">
<Text wordBreak="break-word" maxWidth="100%">
{name}
</Text>
</Flex>
</Flex>
</Button>
<Tooltip label={name}>
<Button
as="a"
variant="ghost"
borderWidth="1px"
rounded="lg"
padding={2}
height={20}
width={20}
position="relative"
whiteSpace="normal"
alignItems="center"
sx={{cursor: 'pointer'}}
onClick={handleClick}
>
<Icon
ref={iconEl => (iconsRef.current[name] = iconEl)}
size={size}
stroke={color}
strokeWidth={strokeWidth}
/>
</Button>
</Tooltip>
);
};

View File

@@ -1,16 +1,38 @@
import { Box, Text } from '@chakra-ui/react';
import React, { useState } from 'react';
import { Box, Text, IconButton, HStack } from '@chakra-ui/react';
import React, { memo, useEffect, useState } from 'react';
import useSearch from '../lib/useSearch';
import IconList from './IconList';
import { SearchInput } from './SearchInput';
import { IconEntity } from '../types';
import { Category, IconEntity } from '../types';
import { SidebarClose, SidebarOpen } from 'lucide-react';
import IconCategoryList from './IconCategoryList';
import { IconCustomizerDrawer } from './IconCustomizerDrawer';
import IconCategoryDrawer from './IconCategoryDrawer';
interface IconOverviewProps {
data: IconEntity[];
categories: Category[]
}
const IconOverview = ({ data }: IconOverviewProps) => {
const IconOverview = ({ data, categories }: IconOverviewProps): JSX.Element => {
const [query, setQuery] = useState('');
const [sidebarOpen, setSidebarOpen] = useState<boolean | undefined>();
const [categoryView, setCategoryView] = useState(false);
useEffect(() => {
if(
typeof window !== 'undefined' &&
window.location.href.split('#').length === 2
) {
const [,hash] = window.location.href.split('#')
setCategoryView(categories.some(({ name }) => name === hash))
}
}, [])
const SidebarIcon = sidebarOpen ? SidebarOpen : SidebarClose;
const searchResults = useSearch(query, data, [
{ name: 'name', weight: 2 },
@@ -18,20 +40,49 @@ const IconOverview = ({ data }: IconOverviewProps) => {
]);
return (
<>
<SearchInput onChange={setQuery} count={data.length} />
<Box>
<HStack position="sticky" top={0} zIndex={1} gap={2} padding={5}>
<IconButton
aria-label="Close overlay"
variant="solid"
color="current"
onClick={() => setSidebarOpen(currentView => {
if(currentView == null) {
return false
}
<Box marginTop={5}>
{searchResults.length > 0 ? (
<IconList icons={searchResults} />
) : (
<Text fontSize="2xl" fontWeight="bold" textAlign="center" wordBreak="break-word">
No results found for "{query}"
</Text>
)}
</Box>
</>
return !currentView
})}
icon={<SidebarIcon />}
/>
<SearchInput onChange={setQuery} count={data.length} />
<IconCustomizerDrawer size="md" paddingX={6} />
</HStack>
<HStack marginBottom="320px" padding={5} alignItems="flex-start">
<IconCategoryDrawer
open={sidebarOpen}
onClose={() => setSidebarOpen(false)}
categories={categories}
data={data}
setCategoryView={setCategoryView}
/>
<Box flex={1} paddingTop={1}>
{searchResults.length > 0 ? (
categoryView ? (
<IconCategoryList icons={searchResults} data={data} categories={categories} />
) : (
<IconList icons={searchResults} />
)
) : (
<Text fontSize="2xl" fontWeight="bold" textAlign="center" wordBreak="break-word">
No results found for "{query}"
</Text>
)}
</Box>
</HStack>
</Box>
);
};
export default IconOverview;
export default memo(IconOverview)

View File

@@ -0,0 +1,66 @@
import { Box, BoxProps } from '@chakra-ui/react';
import { AnimatePresence, Reorder } from 'framer-motion';
import { memo, RefObject } from 'react';
import { IconEntity } from '../types';
import IconReorderItem from './IconReorderItem';
interface IconListProps {
icons: IconEntity[];
setIcons: (icons) => void;
dropZones?: RefObject<[string, HTMLDivElement][]>;
onDrop?: (name: string, category: string) => void;
dragging: boolean;
setDragging: (dragging) => void;
sx?: BoxProps['sx'];
}
const IconReorder = ({
icons,
setIcons,
dropZones,
onDrop,
dragging,
setDragging,
sx,
}: IconListProps) => {
return (
<Box
as={Reorder.Group}
display="flex"
flexWrap="wrap"
gap={5}
marginBottom={6}
onReorder={setIcons}
layoutScroll
values={icons.map(({ name }) => name)}
sx={{
'.dragging': {
position: 'absolute',
},
...sx,
}}
>
<AnimatePresence>
{icons.map(icon => {
return (
<IconReorderItem
icon={icon}
key={icon.name}
dropZones={dropZones}
onDrop={onDrop}
dragging={dragging}
setDragging={setDragging}
/>
);
})}
</AnimatePresence>
</Box>
);
};
export default memo(IconReorder, (prevProps, nextProps) => {
const prevIconsNames = prevProps.icons.map(({ name }) => name);
const nextIconsNames = nextProps.icons.map(({ name }) => name);
return JSON.stringify(prevIconsNames) === JSON.stringify(nextIconsNames);
});

View File

@@ -0,0 +1,78 @@
import { useState } from 'react';
import { useMotionValue, Reorder } from 'framer-motion';
import useRaisedShadow from '../hooks/useRaisedShadow';
import IconListItem from './IconListItem';
import { IconEntity } from '../types';
import { RefObject } from 'react';
interface Props {
icon: IconEntity
dropZones?: RefObject<[string, HTMLDivElement][]>
onDrop?: (name:string, category: string) => void
dragging: boolean;
setDragging: (dragging) => void;
}
const IconReorderItem = ({ icon, dropZones, onDrop, setDragging }: Props): JSX.Element => {
const y = useMotionValue(0);
const boxShadow = useRaisedShadow(y);
const [dragItem, setDragItem] = useState(false);
const onDragEnd = (event) => {
setDragItem(false)
setDragging(false);
const dropZone = dropZones.current?.find(([, el]) => {
if (!Array.isArray(event?.path)) {
return false
}
return Array.from(event.path).includes(el)
})
if (dropZone?.[0] && onDrop) {
const category = dropZone?.[0]
console.log(icon.name, category);
onDrop(icon.name, category)
}
};
const onDrag = () => {
setDragItem(true)
setDragging(true);
};
return (
<Reorder.Item
value={icon.name}
style={{
boxShadow, y,
listStyle: 'none',
pointerEvents: dragItem ? 'none' : 'all',
cursor: dragItem ? 'grab': 'pointer',
}}
drag
dragConstraints={{ top: 0, left: 0, right: 0, bottom: 0 }}
dragElastic={1}
onDragEnd={onDragEnd}
onDrag={onDrag}
initial={{
opacity: 0,
scale: 0,
}}
animate={{
opacity: 1,
scale: 1,
transition: {
duration: 0.2,
delay: 0.02,
},
}}
>
<IconListItem {...icon} />
</Reorder.Item>
);
};
export default IconReorderItem;

View File

@@ -15,6 +15,7 @@ import NextLink from 'next/link';
import { Moon, Sun, Menu, X } from 'lucide-react';
import { useMobileNavigationContext, useMobileNavigationValue } from './MobileNavigationProvider';
import Logo from './Logo';
import menuItems from '../static/menuItems';
interface LayoutProps extends BoxProps {
aside?: BoxProps['children'];
@@ -37,12 +38,14 @@ const Layout = ({ aside, children }: LayoutProps) => {
};
function setQuery(query) {
router.push({
router.push(
{
pathname: '/',
query: { query: query },
},
undefined,
{ shallow: true })
},
undefined,
{ shallow: true },
);
}
useKeyBindings({
@@ -56,7 +59,7 @@ const Layout = ({ aside, children }: LayoutProps) => {
return (
<Box h="100vh">
<Flex mb={16} w="full">
<Flex w="full">
<Flex
alignItems="center"
justifyContent="space-between"
@@ -72,29 +75,28 @@ const Layout = ({ aside, children }: LayoutProps) => {
<Flex justifyContent="center" alignItems="center">
{showBaseNavigation ? (
<>
<NextLink href="/docs" passHref>
<Link marginRight={12} fontSize="xl">
Documentation
</Link>
</NextLink>
<NextLink href="/packages" passHref>
<Link marginRight={12} fontSize="xl">
Packages
</Link>
</NextLink>
<NextLink href="/license" passHref>
<Link marginRight={12} fontSize="xl">
License
</Link>
</NextLink>
<Link
href="https://github.com/lucide-icons/lucide"
isExternal
marginRight={6}
fontSize="xl"
>
Github
</Link>
{menuItems.map(menuItem => {
if (menuItem.isExternal) {
return (
<Link
href={menuItem.href}
isExternal
marginRight={6}
fontSize="lg"
key={menuItem.name}
>
{menuItem.name}
</Link>
);
}
return (
<NextLink href={menuItem.href} passHref key={menuItem.name}>
<Link marginRight={8} fontSize="lg">
{menuItem.name}
</Link>
</NextLink>
);
})}
</>
) : null}
<IconButton
@@ -115,8 +117,12 @@ const Layout = ({ aside, children }: LayoutProps) => {
</Flex>
</Flex>
<Flex>
{aside ? <Box as="aside" marginRight={{ base: 0, lg: -240, }}>{aside}</Box> : null}
<Flex margin="0 auto" direction="column" maxW="1250px" px={5} width="100%">
{aside ? (
<Box as="aside" marginRight={{ base: 0, lg: -240 }}>
{aside}
</Box>
) : null}
<Flex margin="0 auto" direction="column" width="100%">
{children}
<Divider mb={6} mt={12} />
<p style={{ alignSelf: 'center' }}>

View File

@@ -12,6 +12,7 @@ import {
import NextLink from 'next/link';
import { useRouter } from 'next/router';
import { ReactNode, useEffect } from 'react';
import menuItems from '../static/menuItems';
import Logo from './Logo';
import { useMobileNavigationContext } from './MobileNavigationProvider';
@@ -35,31 +36,30 @@ const MobileMenu = ({ children }: { children?: ReactNode }): JSX.Element => {
</DrawerHeader>
<DrawerBody>
<Box mb={4}>
<NextLink href="/docs" passHref>
<Link fontSize="lg" fontWeight="bold" display="block" mb={2}>
Documentation
</Link>
</NextLink>
<NextLink href="/packages" passHref>
<Link marginRight={12} fontSize="lg" fontWeight="bold" display="block" mb={2}>
Packages
</Link>
</NextLink>
<NextLink href="/license" passHref>
<Link marginRight={12} fontSize="xl">
License
</Link>
</NextLink>
<Link
href="https://github.com/lucide-icons/lucide"
isExternal
fontSize="lg"
fontWeight="bold"
display="block"
mb={2}
>
Github
</Link>
{menuItems.map(menuItem => {
if (menuItem.isExternal) {
return (
<Link
href="https://github.com/lucide-icons/lucide"
isExternal
fontSize="lg"
fontWeight="bold"
display="block"
mb={2}
key={menuItem.name}
>
{menuItem.name}
</Link>
);
}
return (
<NextLink href={menuItem.href} passHref>
<Link fontSize="lg" fontWeight="bold" display="block" mb={2}>
{menuItem.name}
</Link>
</NextLink>
);
})}
</Box>
<Divider mt={2} />
{children}

View File

@@ -4,7 +4,7 @@ import {
InputGroup,
InputLeftElement,
useColorMode,
useUpdateEffect
useUpdateEffect,
} from '@chakra-ui/react';
import { Search as SearchIcon } from 'lucide-react';
import React, { useEffect, useRef, useState } from 'react';
@@ -17,59 +17,57 @@ interface SearchInputProps {
count: number;
}
export const SearchInput = (
({ onChange, count }: SearchInputProps) => {
const { colorMode } = useColorMode();
export const SearchInput = ({ onChange, count }: SearchInputProps) => {
const { colorMode } = useColorMode();
const [urlValue, setUrlValue] = useRouterParam('search');
const [urlValue, setUrlValue] = useRouterParam('search');
const [inputValue, setInputValue] = useState('');
const debouncedValue = useDebounce(inputValue.trim(), 300);
const [inputValue, setInputValue] = useState('');
const debouncedValue = useDebounce(inputValue.trim(), 300);
useUpdateEffect(() => {
onChange(debouncedValue);
setUrlValue(debouncedValue);
}, [debouncedValue]);
useUpdateEffect(() => {
onChange(debouncedValue);
setUrlValue(debouncedValue);
}, [debouncedValue]);
useEffect(() => {
if (urlValue && !inputValue) {
setInputValue(urlValue);
onChange(urlValue);
useEffect(() => {
if (urlValue && !inputValue) {
setInputValue(urlValue);
onChange(urlValue);
}
}, [urlValue]);
const ref = useRef(null);
// Keyboard `/` shortcut
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === '/' && ref.current !== document.activeElement) {
event.preventDefault();
ref.current.focus();
}
}, [urlValue]);
const ref = useRef(null);
// Keyboard `/` shortcut
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === '/' && ref.current !== document.activeElement) {
event.preventDefault();
ref.current.focus();
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, []);
};
return (
<InputGroup position="sticky" top={4} zIndex={1}>
<InputLeftElement
children={
<Icon>
<SearchIcon />
</Icon>
}
/>
<Input
ref={ref}
placeholder={`Search ${count} icons (Press "/" to focus)`}
onChange={(event) => setInputValue(event.target.value)}
value={inputValue}
bg={colorMode == 'light' ? theme.colors.white : theme.colors.gray[700]}
/>
</InputGroup>
);
}
);
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, []);
return (
<InputGroup>
<InputLeftElement
children={
<Icon>
<SearchIcon />
</Icon>
}
/>
<Input
ref={ref}
placeholder={`Search ${count} icons (Press "/" to focus)`}
onChange={event => setInputValue(event.target.value)}
value={inputValue}
bg={colorMode == 'light' ? theme.colors.white : theme.colors.gray[700]}
/>
</InputGroup>
);
};

View File

@@ -0,0 +1,97 @@
import { Box, Heading, useColorModeValue } from '@chakra-ui/react';
import { motion } from 'framer-motion';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Category, IconEntity } from 'src/types';
import theme from '../lib/theme';
import IconReorder from './IconReorder';
const UnCategorizedIcons = ({
icons,
dropZones,
dragging,
setDragging,
categories,
handleChange,
}): JSX.Element => {
const [scrollPosition, setScrollPosition] = useState(0);
const boxBackground = useColorModeValue(theme.colors.white, theme.colors.gray[700]);
const allIconContainerRef = useRef(null);
const unCategorizedIcons = useMemo(() => {
return (icons as IconEntity[]).filter(icon => {
return !Object.values(categories as Category[])
.flat()
.some(categorizedIcon => categorizedIcon.name === icon.name);
});
}, [icons, categories]);
const onItemDrop = useCallback(
(iconName: string, targetCategory: string) => {
const newIcons = [...categories[targetCategory].map(({ name }) => name), iconName];
handleChange(targetCategory)(newIcons);
},
[categories],
);
useEffect(() => {
allIconContainerRef.current.addEventListener('scroll', () => {
setScrollPosition(allIconContainerRef.current.scrollTop);
});
}, []);
return (
<Box paddingTop={0} position="sticky" top="0">
<Box
marginTop={5}
marginBottom={320}
maxWidth="calc(1600px / 2)"
width="100%"
height="calc(100vh)"
borderWidth="1px"
boxSizing="border-box"
rounded="lg"
boxShadow={theme.shadows.xl}
bg={boxBackground}
padding={8}
overflowY={dragging ? 'visible' : 'auto'}
ref={allIconContainerRef}
as={motion.div}
layoutScroll
>
<Box marginTop={dragging ? scrollPosition * -1 : undefined}>
<Heading as="h5" size="sm" marginBottom={4}>
Uncategorized Icons
</Heading>
<IconReorder
// key={`uncatogorized-${newKey}`}
icons={unCategorizedIcons}
dropZones={dropZones}
onDrop={onItemDrop}
// eslint-disable-next-line @typescript-eslint/no-empty-function
setIcons={() => {}}
dragging={dragging}
setDragging={setDragging}
/>
<Heading as="h5" size="sm" marginBottom={4}>
All Icons
</Heading>
<IconReorder
icons={Object.values(icons)}
dropZones={dropZones}
onDrop={onItemDrop}
// eslint-disable-next-line @typescript-eslint/no-empty-function
setIcons={() => {}}
dragging={dragging}
setDragging={setDragging}
sx={{
opacity: 0.4,
}}
/>
</Box>
</Box>
</Box>
);
};
export default UnCategorizedIcons;

View File

@@ -0,0 +1,28 @@
import { animate, MotionValue, useMotionValue } from 'framer-motion';
import { useEffect } from 'react';
const inactiveShadow = '0px 0px 0px rgba(0,0,0,0.8)';
export default function useRaisedShadow(value: MotionValue<number>): MotionValue<string> {
const boxShadow = useMotionValue(inactiveShadow);
useEffect(() => {
let isActive = false;
value.onChange(latest => {
const wasActive = isActive;
if (latest !== 0) {
isActive = true;
if (isActive !== wasActive) {
animate(boxShadow, '5px 5px 10px rgba(0,0,0,0.3)');
}
} else {
isActive = false;
if (isActive !== wasActive) {
animate(boxShadow, inactiveShadow);
}
}
});
}, [value, boxShadow]);
return boxShadow;
}

View File

@@ -0,0 +1,32 @@
import fs from "fs";
import path from "path";
import {Category, IconEntity} from "../types";
import {getAllData} from "./icons";
const directory = path.join(process.cwd(), "../categories");
export function getAllCategoryFiles() {
const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json');
return fileNames
.map((fileName) => path.basename(fileName, '.json'));
}
export async function getData(name: string, icons: IconEntity[]): Promise<Category> {
const jsonPath = path.join(directory, `${name}.json`);
const jsonContent = fs.readFileSync(jsonPath, "utf8");
const categoryJson = JSON.parse(jsonContent);
return {
...categoryJson,
name,
iconCount: icons.reduce((acc, curr) => (curr.categories.includes(name) ? ++acc : acc), 0)
};
}
export async function getAllCategories(): Promise<Category[]> {
const names = getAllCategoryFiles();
const icons = await getAllData();
return Promise.all(names.map((name) => getData(name, icons)));
}

View File

@@ -1,14 +1,22 @@
import NextCache from './nextCache';
import {parseSync} from 'svgson';
import {getAllData} from './icons';
import {getAllData, GetDataOptions} from './icons';
export type IconNode = [string, object, IconNode[]];
export type IconNodes = {[iconName: string]: IconNode};
export function fetchIconNodes(writeCache = true): Promise<IconNodes> {
export function fetchIconNodes(writeCache = true, options?: GetDataOptions): Promise<IconNodes> {
if (options?.withChildKeys) {
return NextCache.resolve('api-icon-nodes-with-keys', async () => {
return (await getAllData({ withChildKeys : true})).reduce((acc, icon) => {
acc[icon.name] = icon.iconNode
return acc;
}, {});
}, writeCache);
}
return NextCache.resolve('api-icon-nodes', async () => {
return (await getAllData()).reduce((acc, icon) => {
acc[icon.name] = parseSync(icon.src).children.map(({name, attributes}) => [name, attributes]);
acc[icon.name] = icon.iconNode
return acc;
}, {});
}, writeCache);

View File

@@ -0,0 +1,17 @@
export type IconContent = [icon: string, src:string];
async function generateZip(icons: IconContent[]) {
const JSZip = (await import('jszip')).default
const zip = new JSZip();
const addingZipPromises = icons.map(([name, src]) =>
zip.file(`${name}.svg`, src),
);
await Promise.all(addingZipPromises)
return zip.generateAsync({ type: 'blob' });
}
export default generateZip

View File

@@ -0,0 +1,16 @@
import { createLucideIcon, LucideProps } from "lucide-react"
import { IconEntity } from "src/types"
import { renderToStaticMarkup } from 'react-dom/server';
import { IconContent } from "./generateZip";
const getFallbackZip = (icons: IconEntity[], params: LucideProps) => {
return icons
.map<IconContent>((icon) => {
const Icon = createLucideIcon(icon.name, icon.iconNode)
const src = renderToStaticMarkup(<Icon {...params} />)
return [icon.name, src]
})
}
export default getFallbackZip

28
site/src/lib/helpers.ts Normal file
View File

@@ -0,0 +1,28 @@
/**
* djb2 hashing function
*
* @param {string} string
* @param {number} seed
* @returns {string} A hashed string of 6 characters
*/
export const hash = (string: string, seed = 5381) => {
let i = string.length;
while (i) {
// eslint-disable-next-line no-bitwise, no-plusplus
seed = (seed * 33) ^ string.charCodeAt(--i);
}
// eslint-disable-next-line no-bitwise
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]));

View File

@@ -1,7 +1,10 @@
import fs from "fs";
import path from "path";
import { parseSync } from "svgson";
import { IconNode } from "../../../packages/lucide-react/src/createLucideIcon";
import { IconEntity } from "../types";
import { getContributors } from "./fetchAllContributors";
import { generateHashedKey } from "./helpers";
const directory = path.join(process.cwd(), "../icons");
@@ -13,25 +16,42 @@ export function getAllNames() {
.map((fileName) => path.basename(fileName, '.json'));
}
export async function getData(name: string) {
export interface GetDataOptions {
withChildKeys?: boolean
}
export async function getData(name: string, { withChildKeys = false }: GetDataOptions | undefined = {}) {
const svgPath = path.join(directory, `${name}.svg`);
const svgContent = fs.readFileSync(svgPath, "utf8");
const jsonPath = path.join(directory, `${name}.json`);
const jsonContent = fs.readFileSync(jsonPath, "utf8");
const iconJson = JSON.parse(jsonContent);
const { tags, categories } = JSON.parse(jsonContent);
const iconNode = parseSync(svgContent).children.map(
(child) => {
const { name, attributes } = child
if (withChildKeys) {
attributes.key = generateHashedKey(child)
}
return [name, attributes]
}
) as IconNode
const contributors = await getContributors(name);
return {
...iconJson,
name,
tags,
categories,
contributors,
src: svgContent
iconNode,
};
}
export async function getAllData(): Promise<IconEntity[]> {
export async function getAllData(options?: GetDataOptions): Promise<IconEntity[]> {
const names = getAllNames();
return Promise.all(names.map((name) => getData(name)));
return Promise.all(names.map((name) => getData(name, options)));
}

View File

@@ -21,6 +21,19 @@ const theme = {
'800': '#A70B0B',
'900': '#720707'
},
},
component: {
Button: {
variants: {
solid: (props) => {
// if(props?.colorScheme === 'red') {
return {
bg: props.colorMode === 'dark' ? 'red.700' : 'red.100',
}
// }
},
}
}
}
};

View File

@@ -1,8 +1,16 @@
import { NextApiRequest, NextApiResponse } from 'next';
import {fetchIconNodes} from '../../../lib/fetchIconNodes';
export default async function handler(req, res) {
res.setHeader(
'Cache-Control',
'public, max-age=86400'
).status(200).json(await fetchIconNodes(false));
export default async function handler(request: NextApiRequest, response: NextApiResponse) {
const params = request.query
return response
.setHeader(
'Cache-Control',
'public, max-age=86400'
)
.status(200)
.json(
await fetchIconNodes(false, params)
);
}

Some files were not shown because too many files have changed in this diff Show More