Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a418010baa | ||
|
|
329d75a2c1 | ||
|
|
2482416aef | ||
|
|
11d7b48d70 | ||
|
|
5cbf9363cf | ||
|
|
92531a8a86 | ||
|
|
c67655d402 | ||
|
|
edc614bf43 | ||
|
|
ded24ab61f | ||
|
|
b626e91d7c | ||
|
|
a68ea8c33e | ||
|
|
4fd815a46c | ||
|
|
0e180515a3 | ||
|
|
5dbae5df45 | ||
|
|
bcad75bddd | ||
|
|
78a0640832 | ||
|
|
05ea32948c | ||
|
|
2e11931d2b | ||
|
|
689b3e1d83 | ||
|
|
8ec672bd86 | ||
|
|
c096bf2325 | ||
|
|
a796cb105d | ||
|
|
e4ac2cc4e9 | ||
|
|
2ee208652f | ||
|
|
d0826259d1 | ||
|
|
3acbfb428b |
15
.vscode/launch.json
vendored
Normal 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
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"devs",
|
||||
"preact",
|
||||
"Preact"
|
||||
]
|
||||
}
|
||||
@@ -2,4 +2,4 @@
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Text formatting",
|
||||
"icon": "type"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
9
icons/arrow-down-left-from-circle.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"tags": [
|
||||
"direction"
|
||||
],
|
||||
"categories": [
|
||||
"arrows"
|
||||
]
|
||||
}
|
||||
15
icons/arrow-down-left-from-circle.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="8,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 |
9
icons/arrow-down-right-from-circle.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"tags": [
|
||||
"direction"
|
||||
],
|
||||
"categories": [
|
||||
"arrows"
|
||||
]
|
||||
}
|
||||
15
icons/arrow-down-right-from-circle.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="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 |
14
icons/arrow-up-left-from-circle.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"tags": [
|
||||
"direction",
|
||||
"keyboard",
|
||||
"key",
|
||||
"escape",
|
||||
"button"
|
||||
],
|
||||
"categories": [
|
||||
"arrows",
|
||||
"coding"
|
||||
]
|
||||
}
|
||||
15
icons/arrow-up-left-from-circle.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="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 |
9
icons/arrow-up-right-from-circle.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"tags": [
|
||||
"direction"
|
||||
],
|
||||
"categories": [
|
||||
"arrows"
|
||||
]
|
||||
}
|
||||
15
icons/arrow-up-right-from-circle.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 |
@@ -5,7 +5,8 @@
|
||||
"command line",
|
||||
"terminal",
|
||||
"prompt",
|
||||
"shell"
|
||||
"shell",
|
||||
"console"
|
||||
],
|
||||
"categories": [
|
||||
"coding",
|
||||
|
||||
11
icons/clipboard-paste.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"tags": [
|
||||
"copy",
|
||||
"paste"
|
||||
],
|
||||
"categories": [
|
||||
"text",
|
||||
"arrows"
|
||||
]
|
||||
}
|
||||
15
icons/clipboard-paste.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 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
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"tags": [
|
||||
"album",
|
||||
"vinyl",
|
||||
"record",
|
||||
"music"
|
||||
],
|
||||
"categories": [
|
||||
"devices",
|
||||
"multimedia"
|
||||
]
|
||||
}
|
||||
15
icons/disc-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"
|
||||
>
|
||||
<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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -6,7 +6,9 @@
|
||||
"undo",
|
||||
"rewind",
|
||||
"timeline",
|
||||
"version"
|
||||
"version",
|
||||
"time machine",
|
||||
"backup"
|
||||
],
|
||||
"categories": [
|
||||
"arrows",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"bake"
|
||||
],
|
||||
"categories": [
|
||||
"food-beverage"
|
||||
"food-beverage",
|
||||
"home"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
11
icons/orbit.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"tags": [
|
||||
"planet",
|
||||
"space",
|
||||
"physics"
|
||||
],
|
||||
"categories": [
|
||||
"science"
|
||||
]
|
||||
}
|
||||
17
icons/orbit.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"
|
||||
>
|
||||
<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 |
@@ -4,7 +4,9 @@
|
||||
"on",
|
||||
"off",
|
||||
"device",
|
||||
"switch"
|
||||
"switch",
|
||||
"reboot",
|
||||
"restart"
|
||||
],
|
||||
"categories": [
|
||||
"connectivity"
|
||||
|
||||
15
icons/rat.json
Normal 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
@@ -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 |
@@ -2,7 +2,13 @@
|
||||
"$schema": "../icon.schema.json",
|
||||
"tags": [
|
||||
"arrow",
|
||||
"left"
|
||||
"left",
|
||||
"counter-clockwise",
|
||||
"restart",
|
||||
"reload",
|
||||
"refresh",
|
||||
"backup",
|
||||
"undo"
|
||||
],
|
||||
"categories": [
|
||||
"arrows",
|
||||
|
||||
@@ -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
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"tags": [
|
||||
"arrow",
|
||||
"previous",
|
||||
"music",
|
||||
"left",
|
||||
"reverse"
|
||||
],
|
||||
"categories": [
|
||||
"multimedia",
|
||||
"arrows"
|
||||
]
|
||||
}
|
||||
14
icons/step-back.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="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
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"tags": [
|
||||
"arrow",
|
||||
"next",
|
||||
"music",
|
||||
"right",
|
||||
"continue"
|
||||
],
|
||||
"categories": [
|
||||
"multimedia",
|
||||
"arrows"
|
||||
]
|
||||
}
|
||||
14
icons/step-forward.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="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
@@ -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
@@ -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 |
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>>
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -7,6 +7,7 @@ module.exports = {
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:@next/next/recommended',
|
||||
'plugin:react-hooks/recommended'
|
||||
],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -17,3 +17,7 @@ body {
|
||||
min-height: 100%;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
html:has(*:target) {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
123
site/src/components/CategoryChangesBar.tsx
Normal 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;
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 & 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 & 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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
45
site/src/components/IconCategory.tsx
Normal 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;
|
||||
168
site/src/components/IconCategoryDrawer.tsx
Normal 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
|
||||
78
site/src/components/IconCategoryList.tsx
Normal 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;
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
66
site/src/components/IconReorder.tsx
Normal 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);
|
||||
});
|
||||
78
site/src/components/IconReorderItem.tsx
Normal 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;
|
||||
@@ -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' }}>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
97
site/src/components/UnCategorizedIcons.tsx
Normal 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;
|
||||
28
site/src/hooks/useRaisedShadow.ts
Normal 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;
|
||||
}
|
||||
32
site/src/lib/categories.ts
Normal 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)));
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
17
site/src/lib/generateZip.ts
Normal 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
|
||||
16
site/src/lib/getFallbackZip.tsx
Normal 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
@@ -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]));
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
// }
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
|
||||