mirror of
https://github.com/lucide-icons/lucide.git
synced 2025-12-17 13:27:43 +01:00
Compare commits
391 Commits
lucide-vue
...
0.461.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eaeef9ae37 | ||
|
|
636f966a98 | ||
|
|
1d5c725b58 | ||
|
|
d9a011994a | ||
|
|
c6c645ca7f | ||
|
|
91391eea7b | ||
|
|
49070b4d1f | ||
|
|
ae43473aa6 | ||
|
|
392bc961aa | ||
|
|
f238fe1b18 | ||
|
|
4d91fbb588 | ||
|
|
d939a7005d | ||
|
|
2c55c2ab93 | ||
|
|
cb60e31162 | ||
|
|
09cebe178d | ||
|
|
0f3fd12f16 | ||
|
|
4b0d17ad24 | ||
|
|
30fe706dd6 | ||
|
|
d6178fa471 | ||
|
|
855cb4985d | ||
|
|
9f99ea510f | ||
|
|
e2ee0c77e7 | ||
|
|
d041d5c2d2 | ||
|
|
8700418260 | ||
|
|
30077f913f | ||
|
|
bc09d5f283 | ||
|
|
88c757f540 | ||
|
|
f93beca595 | ||
|
|
b1777430b9 | ||
|
|
d30698cb38 | ||
|
|
d2f7a0931d | ||
|
|
98bbcc4b06 | ||
|
|
c253cb821b | ||
|
|
484dc38b0a | ||
|
|
11b95f883a | ||
|
|
b8cd54f1b2 | ||
|
|
98b3b6fae7 | ||
|
|
3ca465ee11 | ||
|
|
dcd19cedc9 | ||
|
|
8cea9feecc | ||
|
|
e80d98040f | ||
|
|
a1641a372b | ||
|
|
757bf0fb85 | ||
|
|
dd3aed1ecd | ||
|
|
4d98681f05 | ||
|
|
bde9e1cb6b | ||
|
|
a5e07c28bd | ||
|
|
2a68b12cbe | ||
|
|
493382b4fd | ||
|
|
6588971ead | ||
|
|
13cea45e8b | ||
|
|
5dfcfc8d1a | ||
|
|
db24b1d517 | ||
|
|
e1202b545e | ||
|
|
2e1a5cf6ea | ||
|
|
f49ecd73a5 | ||
|
|
a72cbc2d49 | ||
|
|
d4976890e5 | ||
|
|
8c8d1d3338 | ||
|
|
542507f835 | ||
|
|
c6a4908ed0 | ||
|
|
b6e71c6c7a | ||
|
|
7de43440ee | ||
|
|
c8d5260d54 | ||
|
|
0c912bd7ff | ||
|
|
3f24597a65 | ||
|
|
590d59ac5f | ||
|
|
85d427d846 | ||
|
|
478a624162 | ||
|
|
f0afdd4614 | ||
|
|
38e7431189 | ||
|
|
19dd912381 | ||
|
|
f70d5f5169 | ||
|
|
30e0d55b4a | ||
|
|
36a5a8b9ac | ||
|
|
e20e7a43ba | ||
|
|
e8ab1bc15b | ||
|
|
ef090c7dd4 | ||
|
|
65e49e2684 | ||
|
|
0ca2099113 | ||
|
|
9c99fd809c | ||
|
|
cd0d4e1f8a | ||
|
|
e1668804c9 | ||
|
|
efcede62e7 | ||
|
|
3c3f548ec1 | ||
|
|
0160bbe539 | ||
|
|
7837a04438 | ||
|
|
944e428630 | ||
|
|
aa4b1f06b4 | ||
|
|
b1087d3da0 | ||
|
|
fd8d69a129 | ||
|
|
379df75eda | ||
|
|
25707c7c47 | ||
|
|
cbb4ed985c | ||
|
|
d7d1074a60 | ||
|
|
79c2333b7f | ||
|
|
e3f78d73d8 | ||
|
|
e391973a70 | ||
|
|
5a2e3a20ee | ||
|
|
6e65118cd3 | ||
|
|
10c3662ff1 | ||
|
|
c7c6b479fc | ||
|
|
442e477a9a | ||
|
|
777166c06d | ||
|
|
8be94e7f86 | ||
|
|
07dd0bfdb1 | ||
|
|
3271972d97 | ||
|
|
915e8b5b6d | ||
|
|
6c32e47bea | ||
|
|
d4d90f0d4e | ||
|
|
3cf67355b4 | ||
|
|
66bc180c84 | ||
|
|
3c6a8c5118 | ||
|
|
8a088af570 | ||
|
|
cf5d6fc887 | ||
|
|
6052c88831 | ||
|
|
6272f4fd1f | ||
|
|
89070bfa44 | ||
|
|
4cae01a2f5 | ||
|
|
c2cc325f40 | ||
|
|
3143b24dff | ||
|
|
f0625d085e | ||
|
|
beddaa7cbb | ||
|
|
158212c130 | ||
|
|
1d5edc507d | ||
|
|
6f44a5d624 | ||
|
|
1c12bae0f5 | ||
|
|
fdcb73cb7a | ||
|
|
f2f685bd65 | ||
|
|
18d18361e8 | ||
|
|
45c3c00d1d | ||
|
|
45bc8c08da | ||
|
|
6676cdd513 | ||
|
|
eb93f112bd | ||
|
|
54a58881da | ||
|
|
568d0b2fa3 | ||
|
|
2d1be858c8 | ||
|
|
fa6ddf923f | ||
|
|
658b94e65a | ||
|
|
137ab5c885 | ||
|
|
83284d842a | ||
|
|
8993b0b174 | ||
|
|
1b2b66f1f3 | ||
|
|
0186afc0e6 | ||
|
|
36c68bd901 | ||
|
|
e8abed3fa7 | ||
|
|
b4af645560 | ||
|
|
8f65b7e6f4 | ||
|
|
3c3e3508ec | ||
|
|
01e5fd74e6 | ||
|
|
7c62c7c662 | ||
|
|
e92d5e2d40 | ||
|
|
3975020fd2 | ||
|
|
43dfe362b6 | ||
|
|
58524b25ee | ||
|
|
34805df73f | ||
|
|
7cb867782d | ||
|
|
63deb3e4f9 | ||
|
|
4dcc340301 | ||
|
|
8bd401fa8c | ||
|
|
338fc70f6d | ||
|
|
7ca1fabc12 | ||
|
|
0d2c6c457e | ||
|
|
2539470978 | ||
|
|
12b412aa87 | ||
|
|
c8b00be37e | ||
|
|
291b11fbd1 | ||
|
|
4635141dfa | ||
|
|
c761ec7b5e | ||
|
|
bbd877a3ba | ||
|
|
e830fb16e0 | ||
|
|
7625cab264 | ||
|
|
7726b7e7ff | ||
|
|
bca0eeaf09 | ||
|
|
8125a21a7e | ||
|
|
077242cfa0 | ||
|
|
ce79418c66 | ||
|
|
80350b2cb1 | ||
|
|
172f397019 | ||
|
|
a463d8a5c7 | ||
|
|
fbd5225aad | ||
|
|
acd4a879f2 | ||
|
|
e11fa135a0 | ||
|
|
f980863f6c | ||
|
|
07230a442f | ||
|
|
a34919f0af | ||
|
|
f4d451de80 | ||
|
|
1e887bc30f | ||
|
|
1442b9a35b | ||
|
|
41fd856578 | ||
|
|
621b60b19d | ||
|
|
b77e372f3e | ||
|
|
d4891a7307 | ||
|
|
199987276b | ||
|
|
5647b34594 | ||
|
|
439e463430 | ||
|
|
22921304a7 | ||
|
|
220abb1510 | ||
|
|
91ce9221e8 | ||
|
|
904d74fe4a | ||
|
|
f507644488 | ||
|
|
501b65a7a1 | ||
|
|
14862fb0dc | ||
|
|
2963369c8d | ||
|
|
1e20d5087a | ||
|
|
4b312b369f | ||
|
|
afbef743de | ||
|
|
864fdeca84 | ||
|
|
541add925c | ||
|
|
2e7df30267 | ||
|
|
0a578c8803 | ||
|
|
b227caee98 | ||
|
|
72b74fbdb4 | ||
|
|
01d36ad363 | ||
|
|
548cb9cdf5 | ||
|
|
79430da42e | ||
|
|
0620843f4c | ||
|
|
34d063302a | ||
|
|
0abc3389db | ||
|
|
ebb8ec66af | ||
|
|
f55ced97a5 | ||
|
|
8458345535 | ||
|
|
66d6c2fe4b | ||
|
|
053808685c | ||
|
|
4f2a6b70d8 | ||
|
|
d8004e471a | ||
|
|
e2b46eac8e | ||
|
|
1b3173b17b | ||
|
|
d5f4275055 | ||
|
|
6abae7cc14 | ||
|
|
f32ffcd2a2 | ||
|
|
824bb897cf | ||
|
|
2843a76e28 | ||
|
|
155ff3319a | ||
|
|
34dddb811b | ||
|
|
5fead67bf3 | ||
|
|
48dc9372db | ||
|
|
747446fc76 | ||
|
|
5862ea735e | ||
|
|
3a8a349771 | ||
|
|
70bc2245c7 | ||
|
|
89f6b6357d | ||
|
|
354af456d3 | ||
|
|
e50582e93e | ||
|
|
65deefa53c | ||
|
|
54ef137b49 | ||
|
|
d4df542117 | ||
|
|
8c1e56a7bf | ||
|
|
dff2172173 | ||
|
|
e8ccd3df7e | ||
|
|
b593355537 | ||
|
|
09420cbca5 | ||
|
|
45e82a51b8 | ||
|
|
305e282e19 | ||
|
|
ad7ae84987 | ||
|
|
df100bde73 | ||
|
|
0724851934 | ||
|
|
34cf88d209 | ||
|
|
6322d1df66 | ||
|
|
8a414b3249 | ||
|
|
58f614acca | ||
|
|
d598ad7190 | ||
|
|
439ee3f707 | ||
|
|
c2c85fbd5c | ||
|
|
59aa7bc824 | ||
|
|
0beb50ebca | ||
|
|
649b440d9d | ||
|
|
a74e8f3863 | ||
|
|
46318b1605 | ||
|
|
4bf91a3c51 | ||
|
|
3cde4f2a41 | ||
|
|
d75c7613b2 | ||
|
|
d17e81d712 | ||
|
|
3b4a19efcf | ||
|
|
94bbdb4e06 | ||
|
|
4b87cdb55f | ||
|
|
52de557ec1 | ||
|
|
642fa718f6 | ||
|
|
215f0767d7 | ||
|
|
a0befaf186 | ||
|
|
167f563f6d | ||
|
|
0ee6b84a06 | ||
|
|
9a65837e45 | ||
|
|
4c6587612a | ||
|
|
4aa36db1cb | ||
|
|
01401a3c97 | ||
|
|
d1e528fd95 | ||
|
|
5f5be9ec1e | ||
|
|
c66cda28da | ||
|
|
e565116a4a | ||
|
|
cf50c9f849 | ||
|
|
9fa47227a7 | ||
|
|
547111ed5b | ||
|
|
0d6aa9feaa | ||
|
|
3a0f2b3fa8 | ||
|
|
712e6bf6e4 | ||
|
|
58319dd447 | ||
|
|
cf89b8eeac | ||
|
|
a8c6add82b | ||
|
|
d000e4904d | ||
|
|
c99d6cbf4a | ||
|
|
0b1fb8ff1f | ||
|
|
dcde43e97e | ||
|
|
96da23cc88 | ||
|
|
78182c3573 | ||
|
|
02f8f5a1c1 | ||
|
|
16ee591f49 | ||
|
|
5378156833 | ||
|
|
7b76078792 | ||
|
|
e2af1af4f9 | ||
|
|
c9513d0bf4 | ||
|
|
6fbd5ee06a | ||
|
|
d8773827fb | ||
|
|
bc1843f767 | ||
|
|
07aefe3b88 | ||
|
|
76d449a974 | ||
|
|
dd39fa328c | ||
|
|
0cf568c38b | ||
|
|
a8b8dfc58e | ||
|
|
41dde6b574 | ||
|
|
ae35ca256c | ||
|
|
38142f24a5 | ||
|
|
b16f70993a | ||
|
|
ad1accb2e3 | ||
|
|
d255c6ac4e | ||
|
|
d67ef7b0ca | ||
|
|
198ccb8430 | ||
|
|
ce1b5bdefa | ||
|
|
45aa928369 | ||
|
|
93dc356fa1 | ||
|
|
a0aa132682 | ||
|
|
1a4dd64862 | ||
|
|
3481f70ad7 | ||
|
|
024f21e896 | ||
|
|
c30ccd472e | ||
|
|
28ec03ebc8 | ||
|
|
ab3a31367a | ||
|
|
148bae88d8 | ||
|
|
ca4a38ba85 | ||
|
|
f297765a13 | ||
|
|
4657ccbfff | ||
|
|
db222d4591 | ||
|
|
bafad1c625 | ||
|
|
7f1e95c6e7 | ||
|
|
ca7a87112c | ||
|
|
d542da0a1c | ||
|
|
6fa51d2a22 | ||
|
|
ca53b06af2 | ||
|
|
b5dc96d2c2 | ||
|
|
5e5fe0085f | ||
|
|
0c2a8d774f | ||
|
|
804906dcd8 | ||
|
|
d575743d3a | ||
|
|
42c1faed75 | ||
|
|
42b494f853 | ||
|
|
9bf8a653a3 | ||
|
|
01cff578e5 | ||
|
|
a0b1305258 | ||
|
|
0cfdfa2181 | ||
|
|
a2e8ea32d2 | ||
|
|
8a7e6ba343 | ||
|
|
7a9233f4a7 | ||
|
|
71aef25812 | ||
|
|
33189a81ac | ||
|
|
e3923f87c2 | ||
|
|
08c040a57d | ||
|
|
981c3309ce | ||
|
|
2e0af66d8a | ||
|
|
a789c91213 | ||
|
|
91c95600f3 | ||
|
|
99acf4102c | ||
|
|
86f2dc12f4 | ||
|
|
b9fdde2d09 | ||
|
|
5c494962e1 | ||
|
|
772c5be034 | ||
|
|
ee3483eb1b | ||
|
|
9182c51962 | ||
|
|
eb035fe370 | ||
|
|
b96e6acd2e | ||
|
|
7b62649c39 | ||
|
|
10aff6cf7b | ||
|
|
cfa8924025 | ||
|
|
713e9b8a09 | ||
|
|
8ab6f80e4f | ||
|
|
a5221c236a | ||
|
|
cdd32b5294 | ||
|
|
54c8d4078d | ||
|
|
3302870983 | ||
|
|
0f25ee86a0 | ||
|
|
28686b5bd5 | ||
|
|
8cc143915c |
17
.cspell/custom-words.txt
Normal file
17
.cspell/custom-words.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
# Custom words
|
||||
fullscreen
|
||||
gamepad
|
||||
gantt
|
||||
kanban
|
||||
pilcrow
|
||||
squircle
|
||||
strikethrough
|
||||
touchpad
|
||||
ungroup
|
||||
pilcrow
|
||||
toc
|
||||
|
||||
# Brands
|
||||
codepen
|
||||
codesandbox
|
||||
dribbble
|
||||
@@ -6,6 +6,5 @@ tests
|
||||
node_modules
|
||||
.eslintrc.js
|
||||
docs/images
|
||||
docs/guide/basics/examples
|
||||
docs/guide/advanced/examples
|
||||
docs/**/examples/
|
||||
packages/lucide-react/dynamicIconImports.js
|
||||
|
||||
19
.eslintrc.js
19
.eslintrc.js
@@ -1,4 +1,4 @@
|
||||
const DEFAULT_ATTRS = require('./scripts/render/default-attrs.json');
|
||||
const DEFAULT_ATTRS = require('./tools/build-icons/render/default-attrs.json');
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
@@ -15,7 +15,9 @@ module.exports = {
|
||||
'no-use-before-define': 'off',
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{ devDependencies: ['**/*.test.js', '**/*.spec.js', './scripts/**'] },
|
||||
{
|
||||
devDependencies: ['**/*.test.js', '**/*.spec.js', '**/scripts/**'],
|
||||
},
|
||||
],
|
||||
'import/extensions': [
|
||||
'error',
|
||||
@@ -42,12 +44,15 @@ module.exports = {
|
||||
'@html-eslint/no-duplicate-attrs': 'error',
|
||||
'@html-eslint/no-inline-styles': 'error',
|
||||
'@html-eslint/require-attrs': [
|
||||
'error',
|
||||
...Object.entries(DEFAULT_ATTRS)
|
||||
.map(([attr, value]) => ({ tag: 'svg', attr, value: String(value) }))
|
||||
'error',
|
||||
...Object.entries(DEFAULT_ATTRS).map(([attr, value]) => ({
|
||||
tag: 'svg',
|
||||
attr,
|
||||
value: String(value),
|
||||
})),
|
||||
],
|
||||
'@html-eslint/indent': ['error', 2],
|
||||
"@html-eslint/no-multiple-empty-lines": ["error", { "max": 0 }],
|
||||
'@html-eslint/no-multiple-empty-lines': ['error', { max: 0 }],
|
||||
'@html-eslint/no-extra-spacing-attrs': [
|
||||
'error',
|
||||
{
|
||||
@@ -64,7 +69,7 @@ module.exports = {
|
||||
'@html-eslint/element-newline': 'error',
|
||||
'@html-eslint/no-trailing-spaces': 'error',
|
||||
'@html-eslint/quotes': 'error',
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
13
.github/ISSUE_TEMPLATE/02_bug_report.yml
vendored
13
.github/ISSUE_TEMPLATE/02_bug_report.yml
vendored
@@ -35,6 +35,16 @@ body:
|
||||
placeholder: e.g. 0.289.1
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: can-reproduce-in-latest-version
|
||||
attributes:
|
||||
label: Can you reproduce this in the latest version?
|
||||
description: i.e. after running `npm install lucide-react@latest`
|
||||
options:
|
||||
- label: 'Yes'
|
||||
- label: 'No'
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
id: browsers
|
||||
attributes:
|
||||
@@ -59,6 +69,9 @@ body:
|
||||
- label: Windows
|
||||
- label: Linux
|
||||
- label: macOS
|
||||
- label: ChromeOS
|
||||
- label: iOS
|
||||
- label: Android
|
||||
- label: Other/not relevant
|
||||
- type: textarea
|
||||
id: description
|
||||
|
||||
@@ -30,6 +30,9 @@ body:
|
||||
- label: Windows
|
||||
- label: Linux
|
||||
- label: macOS
|
||||
- label: ChromeOS
|
||||
- label: iOS
|
||||
- label: Android
|
||||
- label: Other/not relevant
|
||||
- type: textarea
|
||||
id: description
|
||||
|
||||
6
.github/actions/build-and-test.yml
vendored
6
.github/actions/build-and-test.yml
vendored
@@ -1,5 +1,5 @@
|
||||
name: "Build and Test"
|
||||
description: "Builds and test a package"
|
||||
name: 'Build and Test'
|
||||
description: 'Builds and test a package'
|
||||
|
||||
inputs:
|
||||
name:
|
||||
@@ -7,7 +7,7 @@ inputs:
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
|
||||
6
.github/actions/check-icons.yml
vendored
6
.github/actions/check-icons.yml
vendored
@@ -1,5 +1,5 @@
|
||||
name: "Check icons"
|
||||
description: "Cross-checks icon and category references in JSON descriptors"
|
||||
name: 'Check icons'
|
||||
description: 'Cross-checks icon and category references in JSON descriptors'
|
||||
|
||||
inputs:
|
||||
name:
|
||||
@@ -7,7 +7,7 @@ inputs:
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
|
||||
96
.github/labeler.yml
vendored
96
.github/labeler.yml
vendored
@@ -1,92 +1,92 @@
|
||||
# For changed dependencies
|
||||
📦 dependencies:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- pnpm-lock.yaml
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- pnpm-lock.yaml
|
||||
|
||||
# For changes in documentation
|
||||
📖 documentation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- docs/*.md
|
||||
- docs/**/*.md
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- docs/*.md
|
||||
- docs/**/*.md
|
||||
|
||||
# For changes in the site, but not markdown files
|
||||
🌍 site:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'docs/**'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'docs/**'
|
||||
|
||||
# For changes in the metadata
|
||||
🫧 metadata:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'icons/*.json'
|
||||
- categories/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'icons/*.json'
|
||||
- categories/*
|
||||
|
||||
# For changes or added icons
|
||||
🎨 icon:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'icons/*.svg'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'icons/*.svg'
|
||||
|
||||
# For changes in the lucide package
|
||||
🧳 lucide package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide/*'
|
||||
|
||||
# For changes in the lucide React package
|
||||
⚛️ react package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-react/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-react/*'
|
||||
|
||||
# For changes in the lucide React Native package
|
||||
⚛️ react native package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-react-native/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-react-native/*'
|
||||
|
||||
# For changes in the lucide vue packages
|
||||
💎 vue package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-vue/*'
|
||||
- 'packages/lucide-vue-next/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-vue/*'
|
||||
- 'packages/lucide-vue-next/*'
|
||||
|
||||
# For changes in the lucide angular package
|
||||
🅰️ angular package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-angular/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-angular/*'
|
||||
|
||||
# For changes in the lucide preact package
|
||||
⚛️ preact package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-preact/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-preact/*'
|
||||
|
||||
# For changes in the lucide svelte package
|
||||
🧣 svelte package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-svelte/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-svelte/*'
|
||||
|
||||
# For changes in the lucide solid package
|
||||
🪝 solid package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-solid/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-solid/*'
|
||||
|
||||
# For changes in the lucide static package
|
||||
🪨 static package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-static/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-static/*'
|
||||
|
||||
# For changes in the lucide flutter package
|
||||
🏹 flutter package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-flutter/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-flutter/*'
|
||||
|
||||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -47,7 +47,7 @@
|
||||
- [ ] I've made sure that the icons look sharp on low DPI displays.
|
||||
- [ ] I've made sure that the icons look consistent with the icon set in size, optical volume and density.
|
||||
- [ ] I've made sure that the icons are visually centered.
|
||||
- [ ] I've correctly optimized all icons to two points of precision.
|
||||
- [ ] I've correctly optimized all icons to three points of precision.
|
||||
|
||||
## Before Submitting <!-- For every PR! -->
|
||||
<!-- All of these requirements must be fulfilled. -->
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -17,8 +17,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
35
.github/workflows/close-issue-with-banned-phrases.yml
vendored
Normal file
35
.github/workflows/close-issue-with-banned-phrases.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Close Issue with Banned Phrases
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
block_phrases:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check for blocked phrases in issue title
|
||||
run: |
|
||||
ISSUE_TITLE=$(jq -r '.issue.title' "$GITHUB_EVENT_PATH")
|
||||
BLOCKED_PHRASES=("twitter" "whatsapp" "logo" "google" "tiktok" "facebook" "slack" "discord" "bluesky")
|
||||
|
||||
# Check title and body for blocked phrases
|
||||
for PHRASE in "${BLOCKED_PHRASES[@]}"
|
||||
do
|
||||
if echo "$ISSUE_TITLE" | grep -i "$PHRASE"; then
|
||||
gh issue close ${{ github.event.issue.number }} --reason "not planned" --comment "This looks like a duplicate, use the [search](https://github.com/lucide-icons/lucide/issues?q=is%3Aissue+$PHRASE) to find similar issues.
|
||||
|
||||
Read more about brand guideline rules at [We're not accepting new Brand icons #670](https://github.com/lucide-icons/lucide/issues/670).
|
||||
|
||||
Always happy to help on [Discord](https://discord.gg/EH6nSts)."
|
||||
gh issue lock ${{ github.event.issue.number }} --reason spam
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
5
.github/workflows/close-stale-prs.yml
vendored
5
.github/workflows/close-stale-prs.yml
vendored
@@ -1,11 +1,13 @@
|
||||
name: Close stale issues and PR
|
||||
on:
|
||||
schedule:
|
||||
- cron: "45 1 * * *"
|
||||
- cron: '45 1 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
@@ -14,4 +16,5 @@ jobs:
|
||||
close-pr-message: This PR was closed because it has been stalled for 5 days with no activity.
|
||||
close-pr-label: 🧶 stale
|
||||
days-before-stale: 30
|
||||
days-before-issue-stale: -1
|
||||
days-before-close: -1
|
||||
|
||||
6
.github/workflows/labeler.yml
vendored
6
.github/workflows/labeler.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: "Pull Request Labeler"
|
||||
name: 'Pull Request Labeler'
|
||||
on:
|
||||
- pull_request_target
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
@@ -9,4 +9,4 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
- uses: actions/labeler@v5
|
||||
|
||||
2
.github/workflows/linting.yml
vendored
2
.github/workflows/linting.yml
vendored
@@ -11,8 +11,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
17
.github/workflows/lucide-angular.yml
vendored
17
.github/workflows/lucide-angular.yml
vendored
@@ -8,13 +8,11 @@ on:
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
lucide-angular:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
@@ -26,5 +24,18 @@ jobs:
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-angular build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-angular test
|
||||
|
||||
4
.github/workflows/lucide-font.yml
vendored
4
.github/workflows/lucide-font.yml
vendored
@@ -13,8 +13,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
@@ -29,7 +27,7 @@ jobs:
|
||||
- name: Create font in ./lucide-font
|
||||
run: pnpm build:font
|
||||
|
||||
- name: "Upload to Artifacts"
|
||||
- name: 'Upload to Artifacts'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lucide-font
|
||||
|
||||
6
.github/workflows/lucide-preact.yml
vendored
6
.github/workflows/lucide-preact.yml
vendored
@@ -4,6 +4,7 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/lucide-preact/**
|
||||
- packages/shared/**
|
||||
- tools/build-icons/**
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
@@ -14,8 +15,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
@@ -24,8 +23,5 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-preact build
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-preact test
|
||||
|
||||
6
.github/workflows/lucide-react-native.yml
vendored
6
.github/workflows/lucide-react-native.yml
vendored
@@ -4,6 +4,7 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/lucide-react-native/**
|
||||
- packages/shared/**
|
||||
- tools/build-icons/**
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
@@ -14,8 +15,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
@@ -24,8 +23,5 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-react-native build
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-react-native test
|
||||
|
||||
18
.github/workflows/lucide-react.yml
vendored
18
.github/workflows/lucide-react.yml
vendored
@@ -4,19 +4,18 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/lucide-react/**
|
||||
- packages/shared/**
|
||||
- tools/build-icons/**
|
||||
- tools/rollup-plugins/**
|
||||
- scripts/generateNextJSAliases.mjs
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
lucide-react:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
@@ -28,5 +27,18 @@ jobs:
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-react build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-react test
|
||||
|
||||
24
.github/workflows/lucide-shared.yml
vendored
Normal file
24
.github/workflows/lucide-shared.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Lucide Shared Checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/shared/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-react test
|
||||
18
.github/workflows/lucide-solid.yml
vendored
18
.github/workflows/lucide-solid.yml
vendored
@@ -4,18 +4,17 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/lucide-solid/**
|
||||
- packages/shared/**
|
||||
- tools/build-icons/**
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
lucide-solid:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
@@ -27,5 +26,18 @@ jobs:
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-solid build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-solid test
|
||||
|
||||
2
.github/workflows/lucide-static.yml
vendored
2
.github/workflows/lucide-static.yml
vendored
@@ -13,8 +13,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
18
.github/workflows/lucide-svelte.yml
vendored
18
.github/workflows/lucide-svelte.yml
vendored
@@ -4,18 +4,17 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/lucide-svelte/**
|
||||
- packages/shared/**
|
||||
- tools/build-icons/**
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
lucide-svelte:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
@@ -27,5 +26,18 @@ jobs:
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-svelte build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-svelte test
|
||||
|
||||
18
.github/workflows/lucide-vue-next.yml
vendored
18
.github/workflows/lucide-vue-next.yml
vendored
@@ -4,18 +4,17 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/lucide-vue-next/**
|
||||
- packages/shared/**
|
||||
- tools/build-icons/**
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
lucide-vue-next:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
@@ -27,5 +26,18 @@ jobs:
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-vue-next build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-vue-next test
|
||||
|
||||
18
.github/workflows/lucide-vue.yml
vendored
18
.github/workflows/lucide-vue.yml
vendored
@@ -4,18 +4,17 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/lucide-vue/**
|
||||
- packages/shared/**
|
||||
- tools/build-icons/**
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
lucide-vue:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
@@ -27,5 +26,18 @@ jobs:
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-vue build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-vue test
|
||||
|
||||
17
.github/workflows/lucide.yml
vendored
17
.github/workflows/lucide.yml
vendored
@@ -9,13 +9,11 @@ on:
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
lucide:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
@@ -27,5 +25,18 @@ jobs:
|
||||
- name: Build
|
||||
run: pnpm --filter lucide build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide test
|
||||
|
||||
16
.github/workflows/pull-request.yml
vendored
16
.github/workflows/pull-request.yml
vendored
@@ -3,7 +3,7 @@ name: Add Changed Icons comment
|
||||
on:
|
||||
pull_request_target:
|
||||
paths:
|
||||
- 'icons/*.svg'
|
||||
- 'icons/*'
|
||||
branches:
|
||||
- main
|
||||
- fix-icon-preview
|
||||
@@ -68,6 +68,16 @@ jobs:
|
||||
# input: +++ b/icons/accessibility.json%0A@@ -2,0 +3 @@%0A+ "contributors": ["hi"],%0A@@ -13 +14 @@%0A+}%0A
|
||||
# output: ::$ANNOTATION_SEVERITY file=icons/accessibility.json,line=2,endLine=3,title=$ANNOTATION_TITLE::$ANNOTATION_DESCRIPTION%0A%0A+ "contributors": ["hi"],%0A@@ -13 +14 @@%0A+}%0A
|
||||
|
||||
lint-aliases:
|
||||
name: Check Uniqueness of Aliases
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check Uniqueness of Aliases
|
||||
run: "! cat <(printf \"%s\\n\" icons/*.json | while read -r name; do basename \"$name\" .json; done) <(jq -cr 'select(.aliases) | .aliases[] | if type==\"string\" then . else .name end' icons/*.json) | sort | uniq -c | grep -ve '^\\s*1 '"
|
||||
|
||||
generate-changed-icons-comment:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@@ -94,6 +104,10 @@ jobs:
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: Added or changed icons
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- name: Install svgson for code preview (safer and faster than installing all deps)
|
||||
run: npm install svgson
|
||||
|
||||
- name: Generate comment markup
|
||||
run: node ./scripts/generateChangedIconsCommentMarkup.mjs >> comment-markup.md
|
||||
id: comment-markup
|
||||
|
||||
36
.github/workflows/release.yml
vendored
36
.github/workflows/release.yml
vendored
@@ -41,22 +41,21 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package: [
|
||||
'lucide',
|
||||
'lucide-react',
|
||||
'lucide-react-native',
|
||||
'lucide-vue',
|
||||
'lucide-vue-next',
|
||||
'lucide-angular',
|
||||
'lucide-preact',
|
||||
'lucide-solid',
|
||||
'lucide-svelte',
|
||||
]
|
||||
package:
|
||||
[
|
||||
'lucide',
|
||||
'lucide-react',
|
||||
'lucide-react-native',
|
||||
'lucide-vue',
|
||||
'lucide-vue-next',
|
||||
'lucide-angular',
|
||||
'lucide-preact',
|
||||
'lucide-solid',
|
||||
'lucide-svelte',
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
@@ -88,8 +87,6 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
@@ -120,8 +117,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
@@ -136,7 +131,7 @@ jobs:
|
||||
- name: Create font in ./lucide-font
|
||||
run: pnpm build:font
|
||||
|
||||
- name: "Upload to Artifacts"
|
||||
- name: 'Upload to Artifacts'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lucide-font
|
||||
@@ -145,10 +140,7 @@ jobs:
|
||||
post-release:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [
|
||||
pre-release,
|
||||
lucide-font,
|
||||
]
|
||||
needs: [pre-release, lucide-font]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,7 +16,9 @@ outlined
|
||||
packages/**/src/icons/*.js
|
||||
packages/**/src/icons/*.ts
|
||||
packages/**/src/icons/*.tsx
|
||||
packages/**/src/aliases/*.ts
|
||||
packages/**/src/aliases.ts
|
||||
!packages/**/src/aliases/index.ts
|
||||
packages/**/src/dynamicIconImports.ts
|
||||
packages/**/dynamicIconImports.js
|
||||
packages/**/dynamicIconImports.d.ts
|
||||
@@ -34,6 +36,7 @@ docs/.vitepress/data/iconNodes
|
||||
docs/.vitepress/data/iconMetaData.ts
|
||||
docs/.vitepress/data/releaseMetaData.json
|
||||
docs/.vitepress/data/releaseMetaData
|
||||
docs/.vitepress/data/categoriesData.json
|
||||
docs/.vitepress/data/iconDetails
|
||||
docs/.vitepress/data/relatedIcons.json
|
||||
docs/.vercel
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
pnpm-lock.yaml
|
||||
|
||||
# docs examples
|
||||
docs/**/examples/
|
||||
docs/.vitepress/.temp
|
||||
docs/.vitepress/cache
|
||||
docs/.vitepress/data
|
||||
docs/.nitro
|
||||
|
||||
# lucide-angular
|
||||
packages/lucide-angular/.angular/cache
|
||||
|
||||
|
||||
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@@ -1,13 +1,6 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"devs",
|
||||
"preact",
|
||||
"Preact"
|
||||
],
|
||||
"cSpell.words": ["devs", "preact", "Preact"],
|
||||
"eslint.enable": true,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"svg"
|
||||
],
|
||||
"eslint.validate": ["javascript", "svg"],
|
||||
"svg.preview.background": "transparent"
|
||||
}
|
||||
|
||||
2
.vscode/svg.code-snippets
vendored
2
.vscode/svg.code-snippets
vendored
@@ -49,7 +49,7 @@
|
||||
"circle",
|
||||
"<circle"
|
||||
],
|
||||
"body": "<circle cx=\"${2:12}\" cy=\"${3:$2}\" r=\"${1|10,2,.5|}\" />"
|
||||
"body": "<circle cx=\"${2:12}\" cy=\"${3:$2}\" r=\"${1|10,2,.5\" fill=\"currentColor|}\" />"
|
||||
},
|
||||
"Ellipse": {
|
||||
"scope": "xml",
|
||||
|
||||
@@ -39,6 +39,8 @@ You can also [download an Adobe Illustrator template](https://github.com/lucide-
|
||||
|
||||
#### [Figma Guide](https://lucide.dev/docs/figma-guide)
|
||||
|
||||
#### [Affinity Designer Guide](https://lucide.dev/guide/design/affinity-designer-guide)
|
||||
|
||||
### Submitting Multiple Icons
|
||||
|
||||
If you want submit multiple icons, please separate the icons and group them. That makes reviewing the icons easier and keep the thread clean and scoped.
|
||||
@@ -70,7 +72,7 @@ pnpm install # Install dependencies, including the workspace packages
|
||||
|
||||
### Packages -> PNPM Workspaces
|
||||
|
||||
To distribute different packages we use PNPM workspaces. Before you start make sure you are familiar with this concept. The concept of working in workspaces is created by Yarn, they have a well written introduction: [yarn workspaces](https://classic.yarnpkg.com/lang/enhttps://lucide.dev/docs/workspaces).
|
||||
To distribute different packages we use [PNPM workspaces](https://pnpm.io/workspaces). Before you start make sure you are familiar with this concept. The concept of working in workspaces is created by Yarn, they have a well written introduction: [yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces).
|
||||
|
||||
The configured directory for workspaces is the [packages](https://github.com/lucide-icons/lucide/tree/main/packages) directory, located in the root directory. There you will find all the current packages from lucide.
|
||||
There are more workspaces defined, see [`pnpm-workspace.yaml`](https://github.com/lucide-icons/lucide/blob/main/pnpm-workspace.yaml).
|
||||
|
||||
273
README.md
273
README.md
@@ -1,185 +1,46 @@
|
||||
<p align=center><img width="480" src="https://lucide.dev/lucide-logo-repo.svg" alt="Lucide Logo"></p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/lucide-icons/lucide#gh-light-mode-only">
|
||||
<img src="https://lucide.dev/lucide-logo-repo.svg#gh-light-mode-only" alt="Lucide - Beautiful & consistent icon toolkit made by the community. Open-source project and a fork of Feather Icons." width="480">
|
||||
</a>
|
||||
<a href="https://github.com/lucide-icons/lucide#gh-dark-mode-only">
|
||||
<img src="https://lucide.dev/lucide-logo-repo-dark.svg#gh-dark-mode-only" alt="Lucide - Beautiful & consistent icon toolkit made by the community. Open-source project and a fork of Feather Icons." width="480">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/lucide-icons/lucide/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/lucide" alt="license"></a>
|
||||
<a href="https://www.npmjs.com/package/lucide"><img src="https://img.shields.io/npm/v/lucide" alt="npm package"></a>
|
||||
<a href="https://www.figma.com/community/plugin/939567362549682242/Lucide-Icons"><img src="https://img.shields.io/endpoint?logo=figma&label=installs&url=https://yuanqing.github.io/figma-plugins-stats/plugin/939567362549682242/installs.json" alt="figma installs"></a>
|
||||
<a href="https://www.figma.com/community/plugin/939567362549682242/Lucide-Icons"><img src="https://img.shields.io/badge/Figma-F24E1E?logo=figma&logoColor=white" alt="figma installs"></a>
|
||||
<a href="https://github.com/lucide-icons/lucide/actions/workflows/release.yml"><img src="https://github.com/lucide-icons/lucide/actions/workflows/release.yml/badge.svg" alt="build status"></a>
|
||||
<a href="https://discord.gg/EH6nSts"><img src="https://img.shields.io/discord/723074157486800936?label=chat&logo=discord&logoColor=%23ffffff&colorB=%237289DA" alt="discord chat"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://lucide.dev/icons/">Icons</a>
|
||||
·
|
||||
<a href="https://lucide.dev/guide/">Guide</a>
|
||||
·
|
||||
<a href="https://lucide.dev/packages">Packages</a>
|
||||
·
|
||||
<a href="https://lucide.dev/license">License</a>
|
||||
·
|
||||
<a href="https://lucide.dev/showcase">Showcase</a>
|
||||
</p>
|
||||
|
||||
# Lucide
|
||||
|
||||
Community-run fork of [Feather Icons](https://github.com/feathericons/feather), open for anyone to contribute icons.
|
||||
Lucide is an open-source icon library that provides 1000+ vector (svg) files for displaying icons and symbols in digital and non-digital projects. The library aims to make it easier for designers and developers to incorporate icons into their projects by providing several official [packages](https://lucide.dev/packages) to make it easier to use these icons in your project.
|
||||
|
||||
It began after growing dissatisfaction with the [Feather Icons](https://github.com/feathericons/feather) project moderation. With over 300+ open issues and over 100+ open PRs, the Feather Icons project has been abandoned. This unfortunately means that hundreds of developers and designers wasted their time contributing to Feather Icons with no chance of PRs being accepted.
|
||||
## Packages
|
||||
|
||||
Lucide is trying to expand the icon set as much as possible while staying faithful to the original simplistic design language. We do this as a community of devs and designers and hope that you'll join us!
|
||||
|
||||
### Why choose Lucide over Feather Icons
|
||||
|
||||
- More icons to work with: Lucide already has hundreds of icons more than Feather does.
|
||||
- Official libraries and integrations with popular frameworks and design tools.
|
||||
- Well maintained code base.
|
||||
- Active community, regularly growing and improving the set.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Usage](#usage)
|
||||
- [Web](#web)
|
||||
- [React](#react)
|
||||
- [React Native](#react-native)
|
||||
- [Vue 2](#vue-2)
|
||||
- [Vue 3](#vue-3)
|
||||
- [Angular](#angular)
|
||||
- [Preact](#preact)
|
||||
- [Static (svg sprite, font, icons ..)](#static-svg-sprite-font-icons-)
|
||||
- [Figma](#figma)
|
||||
- [Laravel](#laravel)
|
||||
- [Svelte](#svelte)
|
||||
- [Solid](#solid)
|
||||
- [Hyva](#hyva)
|
||||
- [Eleventy](#eleventy)
|
||||
- [Contributing](#contributing)
|
||||
- [Community](#community)
|
||||
- [License](#license)
|
||||
- [Credits](#credits)
|
||||
- [Sponsors](#sponsors)
|
||||
|
||||
## Usage
|
||||
|
||||
At its core, Lucide is a collection of [SVG](https://svgontheweb.com/#svg) files. This means that you can use Lucide icons in all the same ways you can use SVGs (e.g. `img`, `background-image`, `inline`, `object`, `embed`, `iframe`). Here's a helpful article detailing the many ways SVGs can be used on the web: [SVG on the Web – Implementation Options](https://svgontheweb.com/#implementation)
|
||||
|
||||
The following are additional ways you can use Lucide.
|
||||
With the Javascript library you can easily incorporate the icon you want in your webpage.
|
||||
|
||||
### Web
|
||||
|
||||
Implementation of the lucide icon library for web applications.
|
||||
|
||||
```sh
|
||||
npm install lucide
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
yarn add lucide
|
||||
```
|
||||
|
||||
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide#lucide).
|
||||
|
||||
### React
|
||||
|
||||
Implementation of the lucide icon library for react applications.
|
||||
|
||||
```sh
|
||||
yarn add lucide-react
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
npm install lucide-react
|
||||
```
|
||||
|
||||
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide-react#lucide-react).
|
||||
|
||||
### React Native
|
||||
|
||||
Implementation of the lucide icon library for React Native applications.
|
||||
|
||||
```sh
|
||||
yarn add lucide-react-native
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
npm install lucide-react-native
|
||||
```
|
||||
|
||||
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide-react-native#lucide-react-native).
|
||||
|
||||
### Vue 2
|
||||
|
||||
Implementation of the lucide icon library for vue applications.
|
||||
|
||||
```sh
|
||||
yarn add lucide-vue
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
npm install lucide-vue
|
||||
```
|
||||
|
||||
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide-vue#lucide-vue).
|
||||
|
||||
### Vue 3
|
||||
|
||||
Implementation of the lucide icon library for vue applications.
|
||||
|
||||
```sh
|
||||
yarn add lucide-vue-next
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
npm install lucide-vue-next
|
||||
```
|
||||
|
||||
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide-vue-next#lucide-vue-next).
|
||||
|
||||
### Angular
|
||||
|
||||
```sh
|
||||
yarn add lucide-angular
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
npm install lucide-angular
|
||||
```
|
||||
|
||||
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide-angular#lucide-angular).
|
||||
|
||||
### Preact
|
||||
|
||||
Implementation of the lucide icon library for preact applications.
|
||||
|
||||
```sh
|
||||
yarn add lucide-preact
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
npm install lucide-preact
|
||||
```
|
||||
|
||||
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide-preact#lucide-preact).
|
||||
|
||||
### Static (svg sprite, font, icons ..)
|
||||
|
||||
Assets:
|
||||
[Font Files](https://github.com/lucide-icons/lucide/releases/latest)
|
||||
[SVG Files](https://github.com/lucide-icons/lucide/releases/latest)
|
||||
[SVG Sprite](https://cdn.jsdelivr.net/npm/lucide-static@latest/sprite.svg)
|
||||
|
||||
NPM package
|
||||
|
||||
```sh
|
||||
yarn add lucide-static
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
npm install lucide-static
|
||||
```
|
||||
| | Package | Version & Downloads | Links |
|
||||
| --- | --- | --- | --- |
|
||||
| <img src="https://lucide.dev/framework-logos/js.svg" alt="JS logo" width="48"> | `lucide` | [](https://www.npmjs.com/package/lucide)  | [Docs](https://lucide.dev/guide/packages/lucide) [Source](./packages/lucide) |
|
||||
| <img src="https://lucide.dev/framework-logos/react.svg" alt="React logo" width="48"> | `lucide-react` | [](https://www.npmjs.com/package/lucide-react)  | [Docs](https://lucide.dev/guide/packages/lucide-react) [Source](./packages/lucide-react) |
|
||||
| <img src="https://lucide.dev/framework-logos/vue.svg" alt="Vue logo" width="48"> | `lucide-vue-next` | [](https://www.npmjs.com/package/lucide-vue-next)  | [Docs](https://lucide.dev/guide/packages/lucide-vue-next) [Source](./packages/lucide-vue-next) |
|
||||
| <img src="https://lucide.dev/framework-logos/svelte.svg" alt="Svelte logo" width="48"> | `lucide-svelte` | [](https://www.npmjs.com/package/lucide-svelte)  | [Docs](https://lucide.dev/guide/packages/lucide-svelte) [Source](./packages/lucide-svelte) |
|
||||
| <img src="https://lucide.dev/framework-logos/solid.svg" alt="Solid logo" width="48"> | `lucide-solid` | [](https://www.npmjs.com/package/lucide-solid)  | [Docs](https://lucide.dev/guide/packages/lucide-solid) [Source](./packages/lucide-solid) |
|
||||
| <img src="https://lucide.dev/framework-logos/preact.svg" alt="Preact logo" width="48"> | `lucide-preact` | [](https://www.npmjs.com/package/lucide-preact)  | [Docs](https://lucide.dev/guide/packages/lucide-preact) [Source](./packages/lucide-preact) |
|
||||
| <img src="https://lucide.dev/framework-logos/react-native.svg" alt="React Native logo" width="48"> | `lucide-react-native` | [](https://www.npmjs.com/package/lucide-react-native)  | [Docs](https://lucide.dev/guide/packages/lucide-react-native) [Source](./packages/lucide-react-native) |
|
||||
| <img src="https://lucide.dev/framework-logos/angular.svg" alt="Angular logo" width="48"> | `lucide-angular` | [](https://www.npmjs.com/package/lucide-angular)  | [Docs](https://lucide.dev/guide/packages/lucide-angular) [Source](./packages/lucide-angular) |
|
||||
| <img src="https://lucide.dev/framework-logos/svg.svg" alt="SVG logo" width="48"> | `lucide-static` | [](https://www.npmjs.com/package/lucide-static)  | [Docs](https://lucide.dev/guide/packages/lucide-static) [Source](./packages/lucide-static) |
|
||||
|
||||
### Figma
|
||||
|
||||
@@ -189,68 +50,6 @@ Visit [Figma community page](https://www.figma.com/community/plugin/939567362549
|
||||
|
||||
<img width="420" src="https://www.figma.com/community/plugin/939567362549682242/thumbnail" alt="Figma Lucide Cover">
|
||||
|
||||
### Laravel
|
||||
|
||||
Implementation of Lucide icon's using `blade-icons` for Laravel based projects.
|
||||
|
||||
```sh
|
||||
composer require mallardduck/blade-lucide-icons
|
||||
```
|
||||
|
||||
For more details, see the [documentation](https://github.com/mallardduck/blade-lucide-icons/blob/main/README.md).
|
||||
|
||||
### Svelte
|
||||
|
||||
Implementation of the lucide icon library for Svelte applications.
|
||||
|
||||
```sh
|
||||
yarn add lucide-svelte
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
npm install lucide-svelte
|
||||
```
|
||||
|
||||
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide-svelte#lucide-svelte).
|
||||
|
||||
### Solid
|
||||
|
||||
Implementation of the lucide icon library for solid applications.
|
||||
|
||||
```sh
|
||||
yarn add lucide-solid
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
npm install lucide-solid
|
||||
```
|
||||
|
||||
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide-solid#lucide-solid).
|
||||
|
||||
### Hyva
|
||||
|
||||
Implementation of Lucide icon's using Hyvä's svg php viewmodal to render icons for Magento 2 Hyva theme based projects.
|
||||
|
||||
```sh
|
||||
composer require siteation/magento2-hyva-icons-lucide
|
||||
```
|
||||
|
||||
For more details, see the [documentation](https://github.com/Siteation/magento2-hyva-icons-lucide/blob/main/README.md).
|
||||
|
||||
### Eleventy
|
||||
|
||||
Using this plugin, Eleventy projects can incorporate Lucide icons. it makes it simple to use Lucide icons into your themes via shortcodes, improving your website's overall usability and visual appeal.
|
||||
|
||||
```sh
|
||||
npm install @grimlink/eleventy-plugin-lucide-icons
|
||||
```
|
||||
|
||||
For more details, see the [documentation](https://github.com/GrimLink/eleventy-plugin-lucide-icons/blob/main/README.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
For more info on how to contribute please see the [contribution guidelines](https://github.com/lucide-icons/lucide/blob/main/CONTRIBUTING.md).
|
||||
@@ -274,9 +73,13 @@ Thank you to all the people who contributed to Lucide!
|
||||
|
||||
## Sponsors
|
||||
|
||||
|
||||
<a href="https://vercel.com?utm_source=lucide&utm_campaign=oss">
|
||||
<img src="docs/public/vercel.svg" alt="Powered by Vercel" width="200" />
|
||||
</a>
|
||||
|
||||
<a href="https://www.digitalocean.com/?refcode=b0877a2caebd&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge"><img src="docs/public/digitalocean.svg" width="200" alt="DigitalOcean Referral Badge" /></a>
|
||||
|
||||
### Awesome backers 🍺
|
||||
|
||||
<a href="https://www.scipress.io?utm_source=lucide"><img src="docs/public/sponsors/scipress.svg" width="180" alt="Scipress sponsor badge" /></a>
|
||||
<a href="https://github.com/pdfme/pdfme"><img src="docs/public/sponsors/pdfme.svg" width="180" alt="pdfme sponsor badge" /></a>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Charts",
|
||||
"icon": "pie-chart"
|
||||
"icon": "chart-pie"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Coding & development",
|
||||
"icon": "code-2"
|
||||
"icon": "code-xml"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Home",
|
||||
"icon": "home"
|
||||
"icon": "house"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Multimedia",
|
||||
"icon": "play-circle"
|
||||
"icon": "circle-play"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Notifications",
|
||||
"icon": "alert-triangle"
|
||||
"icon": "triangle-alert"
|
||||
}
|
||||
10
cspell.json
Normal file
10
cspell.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"dictionaries": ["en-us", "custom-words"],
|
||||
"dictionaryDefinitions": [
|
||||
{
|
||||
"name": "custom-words",
|
||||
"path": "./.cspell/custom-words.txt",
|
||||
"addWords": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
import { eventHandler, setResponseHeader } from 'h3'
|
||||
import iconMetaData from '../../data/iconMetaData'
|
||||
import iconMetaData from '../../data/iconMetaData';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(iconMetaData).map(([name, { categories }]) => [ name, categories ])
|
||||
)
|
||||
})
|
||||
Object.entries(iconMetaData).map(([name, { categories }]) => [name, categories]),
|
||||
);
|
||||
});
|
||||
|
||||
40
docs/.vitepress/api/figma/data.ts
Normal file
40
docs/.vitepress/api/figma/data.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import iconNodes from '../../data/iconNodes/index.ts';
|
||||
import { IconNodeWithKeys } from '../../theme/types';
|
||||
import iconMetaData from '../../data/iconMetaData';
|
||||
import releaseMeta from '../../data/releaseMetaData.json';
|
||||
import categories from '../../data/categoriesData.json';
|
||||
|
||||
const dataResponse = {
|
||||
icons: Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
|
||||
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
|
||||
return [name, attrs];
|
||||
});
|
||||
|
||||
acc[name] = {
|
||||
iconNode: newIconNode,
|
||||
aliases: (iconMetaData[name]?.aliases ?? []).map((alias) =>
|
||||
typeof alias === 'string' ? alias : alias.name,
|
||||
),
|
||||
tags: iconMetaData[name].tags ?? [],
|
||||
categories: iconMetaData[name].categories ?? [],
|
||||
...releaseMeta[name],
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
aliases: Object.entries(iconNodes).reduce((acc, [name]) => {
|
||||
for (const alias of iconMetaData[name]?.aliases ?? []) {
|
||||
acc[typeof alias === 'string' ? alias : alias.name] = name;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
categories,
|
||||
};
|
||||
|
||||
export default eventHandler((event) => {
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
return dataResponse;
|
||||
});
|
||||
@@ -13,7 +13,10 @@ export default eventHandler((event) => {
|
||||
const data = pathData.at(-1).slice(0, -4);
|
||||
const [name] = pathData;
|
||||
|
||||
const src = Buffer.from(data, 'base64').toString('utf8');
|
||||
const src = Buffer.from(data, 'base64')
|
||||
.toString('utf8')
|
||||
.replaceAll('\n', '')
|
||||
.replace(/<svg[^>]*>|<\/svg>/g, '');
|
||||
|
||||
const children = [];
|
||||
|
||||
@@ -25,25 +28,25 @@ export default eventHandler((event) => {
|
||||
.map((_, idx, arr) => arr.slice(0, idx + 1).join('-'))
|
||||
.reverse()
|
||||
.find((groupName) => groupName in iconNodes);
|
||||
if (backdropName) {
|
||||
if (!(name in iconNodes) && backdropName) {
|
||||
const iconNode = iconNodes[backdropName];
|
||||
|
||||
const LucideIcon = createLucideIcon(backdropName, iconNode);
|
||||
const svg = renderToStaticMarkup(createElement(LucideIcon));
|
||||
const backdropString = svg.replace(/<svg[^>]*>|<\/svg>/g, '');
|
||||
const backdropString = svg.replaceAll('\n', '').replace(/<svg[^>]*>|<\/svg>/g, '');
|
||||
|
||||
children.push(
|
||||
createElement(Backdrop, {
|
||||
backdropString,
|
||||
src,
|
||||
color: name in iconNodes ? 'red' : '#777',
|
||||
})
|
||||
color: '#777',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const svg = Buffer.from(
|
||||
// We can't use jsx here, is not supported here by nitro.
|
||||
renderToString(createElement(SvgPreview, { src, showGrid: true }, children))
|
||||
renderToString(createElement(SvgPreview, { src, showGrid: true }, children)),
|
||||
).toString('utf8');
|
||||
|
||||
defaultContentType(event, 'image/svg+xml');
|
||||
|
||||
37
docs/.vitepress/api/gh-icon/diff/[...data].get.ts
Normal file
37
docs/.vitepress/api/gh-icon/diff/[...data].get.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
|
||||
import { renderToString, renderToStaticMarkup } from 'react-dom/server';
|
||||
import { createElement } from 'react';
|
||||
import Diff from '../../../lib/SvgPreview/Diff.tsx';
|
||||
import iconNodes from '../../../data/iconNodes';
|
||||
import createLucideIcon from 'lucide-react/src/createLucideIcon';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const { params } = event.context;
|
||||
|
||||
const pathData = params.data.split('/');
|
||||
const data = pathData.at(-1).slice(0, -4);
|
||||
const [name] = pathData;
|
||||
|
||||
const newSrc = Buffer.from(data, 'base64')
|
||||
.toString('utf8')
|
||||
.replaceAll('\n', '')
|
||||
.replace(/<svg[^>]*>|<\/svg>/g, '');
|
||||
|
||||
const children = [];
|
||||
|
||||
const oldSrc = iconNodes[name]
|
||||
? renderToStaticMarkup(createElement(createLucideIcon(name, iconNodes[name])))
|
||||
.replaceAll('\n', '')
|
||||
.replace(/<svg[^>]*>|<\/svg>/g, '')
|
||||
: '';
|
||||
|
||||
const svg = Buffer.from(
|
||||
// We can't use jsx here, is not supported here by nitro.
|
||||
renderToString(createElement(Diff, { oldSrc, newSrc, showGrid: true }, children)),
|
||||
).toString('utf8');
|
||||
|
||||
defaultContentType(event, 'image/svg+xml');
|
||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
|
||||
|
||||
return svg;
|
||||
});
|
||||
@@ -28,7 +28,7 @@ export default eventHandler(async (event) => {
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
`
|
||||
`,
|
||||
);
|
||||
|
||||
const resvg = new Resvg(svg, { background: '#000' });
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { eventHandler, setResponseHeader, defaultContentType } from 'h3'
|
||||
import { renderToString } from 'react-dom/server'
|
||||
import { createElement } from 'react'
|
||||
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
import { createElement } from 'react';
|
||||
import SvgPreview from '../../../lib/SvgPreview/index.tsx';
|
||||
import createLucideIcon, { IconNode } from 'lucide-react/src/createLucideIcon'
|
||||
import createLucideIcon, { IconNode } from 'lucide-react/src/createLucideIcon';
|
||||
import { parseSync } from 'svgson';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const { params } = event.context
|
||||
const { params } = event.context;
|
||||
|
||||
const [strokeWidth, svgData] = params.data.split('/');
|
||||
const data = svgData.slice(0, -4);
|
||||
@@ -16,8 +16,8 @@ export default eventHandler((event) => {
|
||||
const Icon = createLucideIcon(
|
||||
'icon',
|
||||
parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`).children.map(
|
||||
({ name, attributes }) => [name, attributes]
|
||||
) as IconNode
|
||||
({ name, attributes }) => [name, attributes],
|
||||
) as IconNode,
|
||||
);
|
||||
|
||||
const svg = Buffer.from(
|
||||
@@ -33,12 +33,12 @@ export default eventHandler((event) => {
|
||||
@media screen and (prefers-color-scheme: dark) {
|
||||
svg { stroke: #fff; fill: transparent !important; }
|
||||
}
|
||||
</style>`
|
||||
)
|
||||
</style>`,
|
||||
),
|
||||
).toString('utf8');
|
||||
|
||||
defaultContentType(event, 'image/svg+xml')
|
||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000')
|
||||
defaultContentType(event, 'image/svg+xml');
|
||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
|
||||
|
||||
return svg
|
||||
})
|
||||
return svg;
|
||||
});
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import { eventHandler, getQuery, setResponseHeader } from 'h3'
|
||||
import iconNodes from '../../data/iconNodes'
|
||||
import { IconNodeWithKeys } from '../../theme/types'
|
||||
import { eventHandler, getQuery, setResponseHeader } from 'h3';
|
||||
import iconNodes from '../../data/iconNodes';
|
||||
import { IconNodeWithKeys } from '../../theme/types';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const query = getQuery(event)
|
||||
const query = getQuery(event);
|
||||
|
||||
const withUniqueKeys = query.withUniqueKeys === 'true'
|
||||
const withUniqueKeys = query.withUniqueKeys === 'true';
|
||||
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
if (withUniqueKeys) {
|
||||
return iconNodes
|
||||
return iconNodes;
|
||||
}
|
||||
|
||||
return Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
|
||||
if (withUniqueKeys) {
|
||||
return [name, iconNode]
|
||||
return [name, iconNode];
|
||||
}
|
||||
|
||||
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs}]) => {
|
||||
return [name, attrs]
|
||||
})
|
||||
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
|
||||
return [name, attrs];
|
||||
});
|
||||
|
||||
acc[name] = newIconNode
|
||||
acc[name] = newIconNode;
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
})
|
||||
return acc;
|
||||
}, {});
|
||||
});
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import { eventHandler, getQuery, setResponseHeader, createError } from 'h3'
|
||||
import iconNodes from '../../data/iconNodes'
|
||||
import createLucideIcon from 'lucide-react/src/createLucideIcon'
|
||||
import { renderToString } from 'react-dom/server'
|
||||
import { createElement } from 'react'
|
||||
import { eventHandler, getQuery, setResponseHeader, createError } from 'h3';
|
||||
import iconNodes from '../../data/iconNodes';
|
||||
import createLucideIcon from 'lucide-react/src/createLucideIcon';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
import { createElement } from 'react';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const { params } = event.context
|
||||
const { params } = event.context;
|
||||
|
||||
const iconNode = iconNodes[params.iconName]
|
||||
const iconNode = iconNodes[params.iconName];
|
||||
|
||||
if (iconNode == null) {
|
||||
const error = createError({
|
||||
statusCode: 404,
|
||||
message: `Icon "${params.iconName}" not found`,
|
||||
})
|
||||
});
|
||||
|
||||
return sendError(event, error)
|
||||
return sendError(event, error);
|
||||
}
|
||||
|
||||
const width = getQuery(event).width || undefined
|
||||
const height = getQuery(event).height || undefined
|
||||
const color = getQuery(event).color || undefined
|
||||
const strokeWidth = getQuery(event).strokeWidth || undefined
|
||||
const width = getQuery(event).width || undefined;
|
||||
const height = getQuery(event).height || undefined;
|
||||
const color = getQuery(event).color || undefined;
|
||||
const strokeWidth = getQuery(event).strokeWidth || undefined;
|
||||
|
||||
const LucideIcon = createLucideIcon(params.iconName, iconNode)
|
||||
const LucideIcon = createLucideIcon(params.iconName, iconNode);
|
||||
|
||||
const svg = Buffer.from(
|
||||
renderToString(
|
||||
@@ -32,14 +32,13 @@ export default eventHandler((event) => {
|
||||
height,
|
||||
color: color ? `#${color}` : undefined,
|
||||
strokeWidth,
|
||||
}
|
||||
))
|
||||
}),
|
||||
),
|
||||
).toString('utf8');
|
||||
|
||||
defaultContentType(event, 'image/svg+xml')
|
||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000')
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||
defaultContentType(event, 'image/svg+xml');
|
||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
return svg
|
||||
|
||||
})
|
||||
return svg;
|
||||
});
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { eventHandler, setResponseHeader } from 'h3'
|
||||
import iconMetaData from '../../data/iconMetaData'
|
||||
import iconMetaData from '../../data/iconMetaData';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(iconMetaData).map(([name, { tags }]) => [ name, tags ])
|
||||
)
|
||||
})
|
||||
return Object.fromEntries(Object.entries(iconMetaData).map(([name, { tags }]) => [name, tags]));
|
||||
});
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export default eventHandler(() => {
|
||||
return { nitro: 'Is Awesome! asda' }
|
||||
})
|
||||
return { nitro: 'Is Awesome! asda' };
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import { defineConfig } from 'vitepress'
|
||||
import { fileURLToPath, URL } from 'node:url';
|
||||
import { defineConfig } from 'vitepress';
|
||||
import sidebar from './sidebar';
|
||||
|
||||
const title = "Lucide";
|
||||
const socialTitle = "Lucide Icons";
|
||||
const description = "Beautiful & consistent icon toolkit made by the community."
|
||||
const title = 'Lucide';
|
||||
const socialTitle = 'Lucide Icons';
|
||||
const description = 'Beautiful & consistent icon toolkit made by the community.';
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
@@ -19,86 +19,135 @@ export default defineConfig({
|
||||
{
|
||||
find: /^.*\/VPIconAlignLeft\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/overrides/VPIconAlignLeft.vue', import.meta.url)
|
||||
)
|
||||
new URL('./theme/components/overrides/VPIconAlignLeft.vue', import.meta.url),
|
||||
),
|
||||
},
|
||||
{
|
||||
find: /^.*\/VPFooter\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/overrides/VPFooter.vue', import.meta.url)
|
||||
)
|
||||
}
|
||||
]
|
||||
new URL('./theme/components/overrides/VPFooter.vue', import.meta.url),
|
||||
),
|
||||
},
|
||||
{
|
||||
find: '~/.vitepress',
|
||||
replacement: fileURLToPath(new URL('./', import.meta.url)),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
head: [
|
||||
[ 'script', {
|
||||
src: 'https://analytics.lucide.dev/js/script.js',
|
||||
'data-domain': 'lucide.dev',
|
||||
defer: ''
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:locale",
|
||||
content:"en_US"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:type",
|
||||
content:"website"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:site_name",
|
||||
content: title,
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:title",
|
||||
content: socialTitle,
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:description",
|
||||
content: description
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:url",
|
||||
content:"https://lucide.dev"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:image",
|
||||
content: "https://lucide.dev/og.png"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:image:width",
|
||||
content:"1200"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:image:height",
|
||||
content:"630"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:image:type",
|
||||
content:"image/png"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"twitter:card",
|
||||
content:"summary_large_image"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"twitter:title",
|
||||
content: socialTitle,
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"twitter:description",
|
||||
content: description
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"twitter:image",
|
||||
content:"https://lucide.dev/og.png"
|
||||
}],
|
||||
[
|
||||
'script',
|
||||
{
|
||||
src: 'https://analytics.lucide.dev/js/script.js',
|
||||
'data-domain': 'lucide.dev',
|
||||
defer: '',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:locale',
|
||||
content: 'en_US',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:type',
|
||||
content: 'website',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:site_name',
|
||||
content: title,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:title',
|
||||
content: socialTitle,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:description',
|
||||
content: description,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:url',
|
||||
content: 'https://lucide.dev',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image',
|
||||
content: 'https://lucide.dev/og.png',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image:width',
|
||||
content: '1200',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image:height',
|
||||
content: '630',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image:type',
|
||||
content: 'image/png',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:card',
|
||||
content: 'summary_large_image',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:title',
|
||||
content: socialTitle,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:description',
|
||||
content: description,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:image',
|
||||
content: 'https://lucide.dev/og.png',
|
||||
},
|
||||
],
|
||||
],
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
logo: {
|
||||
light: '/logo.light.svg',
|
||||
dark: '/logo.dark.svg'
|
||||
dark: '/logo.dark.svg',
|
||||
},
|
||||
nav: [
|
||||
{ text: 'Icons', link: '/icons/' },
|
||||
@@ -110,21 +159,21 @@ export default defineConfig({
|
||||
sidebar,
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://github.com/lucide-icons/lucide' },
|
||||
{ icon: 'discord', link: 'https://discord.gg/EH6nSts' }
|
||||
{ icon: 'discord', link: 'https://discord.gg/EH6nSts' },
|
||||
],
|
||||
footer: {
|
||||
message: 'Released under the ISC License.',
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`,
|
||||
},
|
||||
editLink: {
|
||||
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path'
|
||||
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path',
|
||||
},
|
||||
carbonAds: {
|
||||
code: 'CWYIC53U',
|
||||
placement: 'lucidedev'
|
||||
}
|
||||
placement: 'lucidedev',
|
||||
},
|
||||
},
|
||||
sitemap: {
|
||||
hostname: 'https://lucide.dev/'
|
||||
}
|
||||
})
|
||||
hostname: 'https://lucide.dev/',
|
||||
},
|
||||
});
|
||||
|
||||
186
docs/.vitepress/data/categoriesData.json
Normal file
186
docs/.vitepress/data/categoriesData.json
Normal file
@@ -0,0 +1,186 @@
|
||||
[
|
||||
{
|
||||
"name": "accessibility",
|
||||
"title": "Accessibility"
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"title": "Accounts & access"
|
||||
},
|
||||
{
|
||||
"name": "animals",
|
||||
"title": "Animals"
|
||||
},
|
||||
{
|
||||
"name": "arrows",
|
||||
"title": "Arrows"
|
||||
},
|
||||
{
|
||||
"name": "brands",
|
||||
"title": "Brands"
|
||||
},
|
||||
{
|
||||
"name": "buildings",
|
||||
"title": "Buildings"
|
||||
},
|
||||
{
|
||||
"name": "charts",
|
||||
"title": "Charts"
|
||||
},
|
||||
{
|
||||
"name": "communication",
|
||||
"title": "Communication"
|
||||
},
|
||||
{
|
||||
"name": "connectivity",
|
||||
"title": "Connectivity"
|
||||
},
|
||||
{
|
||||
"name": "currency",
|
||||
"title": "Currency"
|
||||
},
|
||||
{
|
||||
"name": "cursors",
|
||||
"title": "Cursors"
|
||||
},
|
||||
{
|
||||
"name": "design",
|
||||
"title": "Design"
|
||||
},
|
||||
{
|
||||
"name": "development",
|
||||
"title": "Coding & development"
|
||||
},
|
||||
{
|
||||
"name": "devices",
|
||||
"title": "Devices"
|
||||
},
|
||||
{
|
||||
"name": "emoji",
|
||||
"title": "Emoji"
|
||||
},
|
||||
{
|
||||
"name": "files",
|
||||
"title": "File icons"
|
||||
},
|
||||
{
|
||||
"name": "food-beverage",
|
||||
"title": "Food & beverage"
|
||||
},
|
||||
{
|
||||
"name": "furniture",
|
||||
"title": "Furniture"
|
||||
},
|
||||
{
|
||||
"name": "gaming",
|
||||
"title": "Gaming"
|
||||
},
|
||||
{
|
||||
"name": "home",
|
||||
"title": "Home"
|
||||
},
|
||||
{
|
||||
"name": "layout",
|
||||
"title": "Layout"
|
||||
},
|
||||
{
|
||||
"name": "mail",
|
||||
"title": "Mail"
|
||||
},
|
||||
{
|
||||
"name": "maps",
|
||||
"title": "Maps"
|
||||
},
|
||||
{
|
||||
"name": "maths",
|
||||
"title": "Maths"
|
||||
},
|
||||
{
|
||||
"name": "medical",
|
||||
"title": "Medical"
|
||||
},
|
||||
{
|
||||
"name": "money",
|
||||
"title": "Money"
|
||||
},
|
||||
{
|
||||
"name": "multimedia",
|
||||
"title": "Multimedia"
|
||||
},
|
||||
{
|
||||
"name": "nature",
|
||||
"title": "Nature"
|
||||
},
|
||||
{
|
||||
"name": "navigation",
|
||||
"title": "Navigation"
|
||||
},
|
||||
{
|
||||
"name": "notifications",
|
||||
"title": "Notifications"
|
||||
},
|
||||
{
|
||||
"name": "people",
|
||||
"title": "People"
|
||||
},
|
||||
{
|
||||
"name": "photography",
|
||||
"title": "Photography"
|
||||
},
|
||||
{
|
||||
"name": "science",
|
||||
"title": "Science"
|
||||
},
|
||||
{
|
||||
"name": "seasons",
|
||||
"title": "Seasons"
|
||||
},
|
||||
{
|
||||
"name": "security",
|
||||
"title": "Security"
|
||||
},
|
||||
{
|
||||
"name": "shapes",
|
||||
"title": "Shapes"
|
||||
},
|
||||
{
|
||||
"name": "shopping",
|
||||
"title": "Shopping"
|
||||
},
|
||||
{
|
||||
"name": "social",
|
||||
"title": "Social"
|
||||
},
|
||||
{
|
||||
"name": "sports",
|
||||
"title": "Sports"
|
||||
},
|
||||
{
|
||||
"name": "sustainability",
|
||||
"title": "Sustainability"
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"title": "Text formatting"
|
||||
},
|
||||
{
|
||||
"name": "time",
|
||||
"title": "Time & calendar"
|
||||
},
|
||||
{
|
||||
"name": "tools",
|
||||
"title": "Tools"
|
||||
},
|
||||
{
|
||||
"name": "transportation",
|
||||
"title": "Transportation"
|
||||
},
|
||||
{
|
||||
"name": "travel",
|
||||
"title": "Travel"
|
||||
},
|
||||
{
|
||||
"name": "weather",
|
||||
"title": "Weather"
|
||||
}
|
||||
]
|
||||
@@ -3,87 +3,144 @@
|
||||
"order": 0,
|
||||
"icon": "js",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide", "href": "https://www.npmjs.com/package/lucide" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide", "href": "https://www.npmjs.com/package/lucide" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide",
|
||||
"href": "https://www.npmjs.com/package/lucide"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide",
|
||||
"href": "https://www.npmjs.com/package/lucide"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-react": {
|
||||
"order": 1,
|
||||
"icon": "react",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-react", "href": "https://www.npmjs.com/package/lucide-react" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-react", "href": "https://www.npmjs.com/package/lucide-react" }
|
||||
]
|
||||
},
|
||||
"lucide-vue": {
|
||||
"order": 2,
|
||||
"icon": "vue",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-vue", "href": "https://www.npmjs.com/package/lucide-vue" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-vue", "href": "https://www.npmjs.com/package/lucide-vue" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-react",
|
||||
"href": "https://www.npmjs.com/package/lucide-react"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-react",
|
||||
"href": "https://www.npmjs.com/package/lucide-react"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-vue-next": {
|
||||
"order": 3,
|
||||
"order": 2,
|
||||
"icon": "vue-next",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-vue-next", "href": "https://www.npmjs.com/package/lucide-vue-next" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-vue-next", "href": "https://www.npmjs.com/package/lucide-vue-next" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-vue-next",
|
||||
"href": "https://www.npmjs.com/package/lucide-vue-next"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-vue-next",
|
||||
"href": "https://www.npmjs.com/package/lucide-vue-next"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-svelte": {
|
||||
"order": 4,
|
||||
"order": 3,
|
||||
"icon": "svelte",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-svelte", "href": "https://www.npmjs.com/package/lucide-svelte" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-svelte", "href": "https://www.npmjs.com/package/lucide-svelte" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-svelte",
|
||||
"href": "https://www.npmjs.com/package/lucide-svelte"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-svelte",
|
||||
"href": "https://www.npmjs.com/package/lucide-svelte"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-solid": {
|
||||
"order": 4,
|
||||
"icon": "solid",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-solid", "href": "https://www.npmjs.com/package/lucide-solid" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-solid", "href": "https://www.npmjs.com/package/lucide-solid" }
|
||||
]
|
||||
},
|
||||
"lucide-preact": {
|
||||
"order": 5,
|
||||
"icon": "preact",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-preact", "href": "https://www.npmjs.com/package/lucide-preact" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-preact", "href": "https://www.npmjs.com/package/lucide-preact" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-solid",
|
||||
"href": "https://www.npmjs.com/package/lucide-solid"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-solid",
|
||||
"href": "https://www.npmjs.com/package/lucide-solid"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-react-native": {
|
||||
"order": 6,
|
||||
"order": 5,
|
||||
"icon": "react-native",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-react-native", "href": "https://www.npmjs.com/package/lucide-react-native" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-react-native", "href": "https://www.npmjs.com/package/lucide-react-native" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-react-native",
|
||||
"href": "https://www.npmjs.com/package/lucide-react-native"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-react-native",
|
||||
"href": "https://www.npmjs.com/package/lucide-react-native"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-angular": {
|
||||
"order": 7,
|
||||
"order": 6,
|
||||
"icon": "angular",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-angular", "href": "https://www.npmjs.com/package/lucide-angular" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-angular", "href": "https://www.npmjs.com/package/lucide-angular" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-angular",
|
||||
"href": "https://www.npmjs.com/package/lucide-angular"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-angular",
|
||||
"href": "https://www.npmjs.com/package/lucide-angular"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-preact": {
|
||||
"order": 7,
|
||||
"icon": "preact",
|
||||
"shields": [
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-preact",
|
||||
"href": "https://www.npmjs.com/package/lucide-preact"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-preact",
|
||||
"href": "https://www.npmjs.com/package/lucide-preact"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-static": {
|
||||
"order": 8,
|
||||
"icon": "svg",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-static", "href": "https://www.npmjs.com/package/lucide-static" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-static", "href": "https://www.npmjs.com/package/lucide-static" }
|
||||
]
|
||||
},
|
||||
"lucide-flutter": {
|
||||
"order": 9,
|
||||
"icon": "flutter",
|
||||
"shields": [
|
||||
{ "alt": "flutter", "src": "https://img.shields.io/pub/v/lucide_icons", "href": "https://img.shields.io/pub/v/lucide_icons" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-static",
|
||||
"href": "https://www.npmjs.com/package/lucide-static"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-static",
|
||||
"href": "https://www.npmjs.com/package/lucide-static"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,5 +76,24 @@
|
||||
],
|
||||
"source": "https://github.com/swisnl/nuxt-lucide-icons",
|
||||
"documentation": "https://github.com/swisnl/nuxt-lucide-icons/blob/main/README.md"
|
||||
},
|
||||
{
|
||||
"name": "lucide-lustre",
|
||||
"description": "A library providing https://lucide.dev icons to lustre",
|
||||
"icon": "/framework-logos/lustre.webp",
|
||||
"shields": [
|
||||
{
|
||||
"alt": "Latest Stable Version",
|
||||
"src": "https://img.shields.io/hexpm/v/lucide_lustre",
|
||||
"href": "https://hex.pm/packages/lucide_lustre"
|
||||
},
|
||||
{
|
||||
"alt": "Total Downloads",
|
||||
"src": "https://img.shields.io/hexpm/dw/lucide_lustre",
|
||||
"href": "https://hex.pm/packages/lucide_lustre"
|
||||
}
|
||||
],
|
||||
"source": "https://github.com/dinkelspiel/lucide_lustre",
|
||||
"documentation": "https://github.com/dinkelspiel/lucide_lustre/blob/master/README.md"
|
||||
}
|
||||
]
|
||||
|
||||
48
docs/.vitepress/data/teamData.json
Normal file
48
docs/.vitepress/data/teamData.json
Normal file
@@ -0,0 +1,48 @@
|
||||
[
|
||||
{
|
||||
"name": "Eric Fennis",
|
||||
"title": "Creator of Lucide & Software engineer @nedap",
|
||||
"image": "https://github.com/ericfennis.png?size=192",
|
||||
"sponsor": "https://github.com/sponsors/ericfennis",
|
||||
"socialLinks": [
|
||||
{
|
||||
"icon": "github",
|
||||
"link": "https://github.com/ericfennis"
|
||||
},
|
||||
{
|
||||
"icon": "x",
|
||||
"link": "https://x.com/ericfennis"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Karsa Rigó",
|
||||
"title": "Maintainer of Lucide & Software engineer @sztaki",
|
||||
"image": "https://github.com/karsa-mistmere.png?size=192",
|
||||
"socialLinks": [
|
||||
{
|
||||
"icon": "github",
|
||||
"link": "https://github.com/karsa-mistmere"
|
||||
},
|
||||
{
|
||||
"icon": "linkedin",
|
||||
"link": "https://www.linkedin.com/in/karsamistmere"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Jakob Guddas",
|
||||
"title": "Maintainer of Lucide & Software engineer @LEGO",
|
||||
"image": "https://github.com/jguddas.png?size=192",
|
||||
"socialLinks": [
|
||||
{
|
||||
"icon": "github",
|
||||
"link": "https://github.com/jguddas"
|
||||
},
|
||||
{
|
||||
"icon": "linkedin",
|
||||
"link": "https://www.linkedin.com/in/jguddas"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -3,55 +3,92 @@ import React from 'react';
|
||||
interface BackdropProps {
|
||||
src: string;
|
||||
color?: string;
|
||||
outline?: boolean;
|
||||
backdropString: string;
|
||||
}
|
||||
|
||||
const Backdrop = ({ src, color = 'red', backdropString }: BackdropProps): JSX.Element => {
|
||||
const Backdrop = ({
|
||||
src,
|
||||
color = 'red',
|
||||
outline = true,
|
||||
backdropString,
|
||||
}: BackdropProps): JSX.Element => {
|
||||
const id = React.useId();
|
||||
return (
|
||||
<>
|
||||
<defs xmlns="http://www.w3.org/2000/svg">
|
||||
<pattern
|
||||
id="pattern"
|
||||
id={`pattern-${id}`}
|
||||
width=".1"
|
||||
height=".1"
|
||||
patternUnits="userSpaceOnUse"
|
||||
patternTransform="rotate(45 50 50)"
|
||||
>
|
||||
<line stroke={color} strokeWidth={0.1} y2={1} />
|
||||
<line stroke={color} strokeWidth={0.1} y2={1} />
|
||||
<line
|
||||
stroke={color}
|
||||
strokeWidth={0.1}
|
||||
y2={1}
|
||||
/>
|
||||
<line
|
||||
stroke={color}
|
||||
strokeWidth={0.1}
|
||||
y2={1}
|
||||
/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<mask id="svg-preview-backdrop-mask-outline" maskUnits="userSpaceOnUse">
|
||||
<g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} />
|
||||
<g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} />
|
||||
<mask
|
||||
id={`svg-preview-backdrop-mask-${id}`}
|
||||
maskUnits="userSpaceOnUse"
|
||||
>
|
||||
<g
|
||||
stroke="#fff"
|
||||
dangerouslySetInnerHTML={{ __html: backdropString }}
|
||||
/>
|
||||
<g dangerouslySetInnerHTML={{ __html: src }} />
|
||||
</mask>
|
||||
<mask id="svg-preview-backdrop-mask-fill" maskUnits="userSpaceOnUse">
|
||||
<g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} />
|
||||
<g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} />
|
||||
<g strokeWidth={1.75} dangerouslySetInnerHTML={{ __html: backdropString }} />
|
||||
<mask
|
||||
id={`svg-preview-backdrop-mask-outline-${id}`}
|
||||
maskUnits="userSpaceOnUse"
|
||||
>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="#fff"
|
||||
stroke="none"
|
||||
/>
|
||||
<g
|
||||
strokeWidth={1.75}
|
||||
dangerouslySetInnerHTML={{ __html: backdropString }}
|
||||
/>
|
||||
</mask>
|
||||
<g strokeWidth={2.25} stroke="url(#pattern)" mask={'url(#svg-preview-backdrop-mask-outline)'}>
|
||||
<rect x="0" y="0" width="24" height="24" fill="url(#pattern)" opacity={0.5} stroke="none" />
|
||||
<g mask={`url(#svg-preview-backdrop-mask-${id})`}>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
opacity={0.5}
|
||||
fill={`url(#pattern-${id})`}
|
||||
stroke="none"
|
||||
/>
|
||||
<g
|
||||
stroke={color}
|
||||
strokeWidth={2.25}
|
||||
opacity={0.75}
|
||||
dangerouslySetInnerHTML={{ __html: src }}
|
||||
/>
|
||||
{outline && (
|
||||
<g
|
||||
stroke={color}
|
||||
strokeWidth={2.25}
|
||||
opacity={0.75}
|
||||
mask={`url(#svg-preview-backdrop-mask-outline-${id})`}
|
||||
dangerouslySetInnerHTML={{ __html: backdropString }}
|
||||
/>
|
||||
)}
|
||||
</g>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="url(#pattern)"
|
||||
stroke="none"
|
||||
mask={'url(#svg-preview-backdrop-mask-fill)'}
|
||||
/>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
fill={color}
|
||||
opacity={0.5}
|
||||
stroke="none"
|
||||
mask={'url(#svg-preview-backdrop-mask-fill)'}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
71
docs/.vitepress/lib/SvgPreview/Diff.tsx
Normal file
71
docs/.vitepress/lib/SvgPreview/Diff.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import React from 'react';
|
||||
import Backdrop from './Backdrop.tsx';
|
||||
import { darkModeCss, Grid } from './index.tsx';
|
||||
|
||||
const SvgPreview = React.forwardRef<
|
||||
SVGSVGElement,
|
||||
{
|
||||
oldSrc: string;
|
||||
newSrc: string;
|
||||
} & React.SVGProps<SVGSVGElement>
|
||||
>(({ oldSrc, newSrc, children, ...props }, ref) => {
|
||||
return (
|
||||
<svg
|
||||
ref={ref}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<style>{darkModeCss}</style>
|
||||
<Grid
|
||||
strokeWidth={0.1}
|
||||
stroke="#777"
|
||||
strokeOpacity={0.3}
|
||||
radius={1}
|
||||
/>
|
||||
<mask
|
||||
id="gray"
|
||||
maskUnits="userSpaceOnUse"
|
||||
>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="#000"
|
||||
stroke="none"
|
||||
/>
|
||||
<g
|
||||
stroke="#fff"
|
||||
dangerouslySetInnerHTML={{ __html: oldSrc }}
|
||||
/>
|
||||
</mask>
|
||||
<Backdrop
|
||||
src=""
|
||||
outline={false}
|
||||
backdropString={`<g mask="url('#gray')">${newSrc}</g>`}
|
||||
color="#777"
|
||||
/>
|
||||
<Backdrop
|
||||
src={oldSrc}
|
||||
backdropString={newSrc}
|
||||
color="lime"
|
||||
/>
|
||||
<Backdrop
|
||||
src={newSrc}
|
||||
backdropString={oldSrc}
|
||||
color="red"
|
||||
/>
|
||||
{children}
|
||||
</svg>
|
||||
);
|
||||
});
|
||||
|
||||
export default SvgPreview;
|
||||
@@ -2,7 +2,23 @@ import React from 'react';
|
||||
import { PathProps, Path } from './types';
|
||||
import { getPaths, assert } from './utils';
|
||||
|
||||
const Grid = ({
|
||||
export const darkModeCss = `
|
||||
@media screen and (prefers-color-scheme: light) {
|
||||
.svg-preview-grid-rect { fill: none }
|
||||
}
|
||||
@media screen and (prefers-color-scheme: dark) {
|
||||
.svg-preview-grid-rect { fill: none }
|
||||
.svg
|
||||
.svg-preview-grid-group,
|
||||
.svg-preview-radii-group,
|
||||
.svg-preview-shadow-mask-group,
|
||||
.svg-preview-shadow-group {
|
||||
stroke: #fff;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const Grid = ({
|
||||
radius,
|
||||
fill = '#fff',
|
||||
...props
|
||||
@@ -10,7 +26,11 @@ const Grid = ({
|
||||
strokeWidth: number;
|
||||
radius: number;
|
||||
} & PathProps<'stroke', 'strokeWidth'>) => (
|
||||
<g className="svg-preview-grid-group" strokeLinecap="butt" {...props}>
|
||||
<g
|
||||
className="svg-preview-grid-group"
|
||||
strokeLinecap="butt"
|
||||
{...props}
|
||||
>
|
||||
<rect
|
||||
className="svg-preview-grid-rect"
|
||||
width={24 - props.strokeWidth}
|
||||
@@ -21,11 +41,29 @@ const Grid = ({
|
||||
fill={fill}
|
||||
/>
|
||||
<path
|
||||
strokeDasharray={'0 0.1 ' + '0.1 0.15 '.repeat(11) + '0 0.15'}
|
||||
strokeWidth={0.1}
|
||||
d={
|
||||
props.d ||
|
||||
new Array(Math.floor(24 - 1))
|
||||
.fill(null)
|
||||
.flatMap((_, i) => [
|
||||
.map((_, i) => i)
|
||||
.filter((i) => i % 3 !== 2)
|
||||
.flatMap((i) => [
|
||||
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
|
||||
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
|
||||
])
|
||||
.join('')
|
||||
}
|
||||
/>
|
||||
<path
|
||||
d={
|
||||
props.d ||
|
||||
new Array(Math.floor(24 - 1))
|
||||
.fill(null)
|
||||
.map((_, i) => i)
|
||||
.filter((i) => i % 3 === 2)
|
||||
.flatMap((i) => [
|
||||
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
|
||||
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
|
||||
])
|
||||
@@ -44,15 +82,21 @@ const Shadow = ({
|
||||
paths: Path[];
|
||||
} & PathProps<'stroke' | 'strokeWidth' | 'strokeOpacity', 'd'>) => {
|
||||
const groupedPaths = Object.entries(
|
||||
paths.reduce((groups, val) => {
|
||||
const key = val.c.id;
|
||||
groups[key] = [...(groups[key] || []), val];
|
||||
return groups;
|
||||
}, {} as Record<number, Path[]>)
|
||||
paths.reduce(
|
||||
(groups, val) => {
|
||||
const key = val.c.id;
|
||||
groups[key] = [...(groups[key] || []), val];
|
||||
return groups;
|
||||
},
|
||||
{} as Record<number, Path[]>,
|
||||
),
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<g className="svg-preview-shadow-mask-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-shadow-mask-group"
|
||||
{...props}
|
||||
>
|
||||
{groupedPaths.map(([id, paths]) => (
|
||||
<mask
|
||||
id={`svg-preview-shadow-mask-${id}`}
|
||||
@@ -61,7 +105,15 @@ const Shadow = ({
|
||||
strokeWidth={props.strokeWidth}
|
||||
stroke="#000"
|
||||
>
|
||||
<rect x={0} y={0} width={24} height={24} fill="#fff" stroke="none" rx={radius} />
|
||||
<rect
|
||||
x={0}
|
||||
y={0}
|
||||
width={24}
|
||||
height={24}
|
||||
fill="#fff"
|
||||
stroke="none"
|
||||
rx={radius}
|
||||
/>
|
||||
<path
|
||||
d={paths
|
||||
.flatMap(({ prev, next }) => [
|
||||
@@ -74,9 +126,16 @@ const Shadow = ({
|
||||
</mask>
|
||||
))}
|
||||
</g>
|
||||
<g className="svg-preview-shadow-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-shadow-group"
|
||||
{...props}
|
||||
>
|
||||
{paths.map(({ d, c: { id } }, i) => (
|
||||
<path key={i} mask={`url(#svg-preview-shadow-mask-${id})`} d={d} />
|
||||
<path
|
||||
key={i}
|
||||
mask={`url(#svg-preview-shadow-mask-${id})`}
|
||||
d={d}
|
||||
/>
|
||||
))}
|
||||
<path
|
||||
d={paths
|
||||
@@ -94,9 +153,16 @@ const ColoredPath = ({
|
||||
paths,
|
||||
...props
|
||||
}: { paths: Path[]; colors: string[] } & PathProps<never, 'd' | 'stroke'>) => (
|
||||
<g className="svg-preview-colored-path-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-colored-path-group"
|
||||
{...props}
|
||||
>
|
||||
{paths.map(({ d, c }, i) => (
|
||||
<path key={i} d={d} stroke={colors[(c.name === 'path' ? i : c.id) % colors.length]} />
|
||||
<path
|
||||
key={i}
|
||||
d={d}
|
||||
stroke={colors[(c.name === 'path' ? i : c.id) % colors.length]}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
);
|
||||
@@ -138,7 +204,15 @@ const ControlPath = ({
|
||||
key={i}
|
||||
maskUnits="userSpaceOnUse"
|
||||
>
|
||||
<rect x="0" y="0" width="24" height="24" fill="#fff" stroke="none" rx={radius} />
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="#fff"
|
||||
stroke="none"
|
||||
rx={radius}
|
||||
/>
|
||||
<path d={`M${prev.x} ${prev.y}h.01`} />
|
||||
<path d={`M${next.x} ${next.y}h.01`} />
|
||||
</mask>
|
||||
@@ -146,7 +220,10 @@ const ControlPath = ({
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
<g className="svg-preview-control-path-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-control-path-group"
|
||||
{...props}
|
||||
>
|
||||
{controlPaths.map(({ d, showMarker }, i) => (
|
||||
<path
|
||||
key={i}
|
||||
@@ -155,18 +232,33 @@ const ControlPath = ({
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
<g className="svg-preview-control-path-marker-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-control-path-marker-group"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d={controlPaths
|
||||
.flatMap(({ prev, next, showMarker }) =>
|
||||
showMarker ? [`M${prev.x} ${prev.y}h.01`, `M${next.x} ${next.y}h.01`] : []
|
||||
showMarker ? [`M${prev.x} ${prev.y}h.01`, `M${next.x} ${next.y}h.01`] : [],
|
||||
)
|
||||
.join('')}
|
||||
/>
|
||||
{controlPaths.map(({ d, prev, next, startMarker, endMarker }, i) => (
|
||||
<React.Fragment key={i}>
|
||||
{startMarker && <circle cx={prev.x} cy={prev.y} r={pointSize / 2} />}
|
||||
{endMarker && <circle cx={next.x} cy={next.y} r={pointSize / 2} />}
|
||||
{startMarker && (
|
||||
<circle
|
||||
cx={prev.x}
|
||||
cy={prev.y}
|
||||
r={pointSize / 2}
|
||||
/>
|
||||
)}
|
||||
{endMarker && (
|
||||
<circle
|
||||
cx={next.x}
|
||||
cy={next.y}
|
||||
r={pointSize / 2}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</g>
|
||||
@@ -182,15 +274,16 @@ const Radii = ({
|
||||
any
|
||||
>) => {
|
||||
return (
|
||||
<g className="svg-preview-radii-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-radii-group"
|
||||
{...props}
|
||||
>
|
||||
{paths.map(
|
||||
({ c, prev, next, circle }, i) =>
|
||||
circle && (
|
||||
<React.Fragment key={i}>
|
||||
{c.name !== "circle" && (
|
||||
<path
|
||||
d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`}
|
||||
/>
|
||||
{c.name !== 'circle' && (
|
||||
<path d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`} />
|
||||
)}
|
||||
<circle
|
||||
cy={circle.y}
|
||||
@@ -200,7 +293,7 @@ const Radii = ({
|
||||
stroke={
|
||||
(Math.round(circle.x * 100) / 100) % 1 !== 0 ||
|
||||
(Math.round(circle.y * 100) / 100) % 1 !== 0
|
||||
? "red"
|
||||
? 'red'
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
@@ -208,11 +301,7 @@ const Radii = ({
|
||||
cy={circle.y}
|
||||
cx={circle.x}
|
||||
r={circle.r}
|
||||
stroke={
|
||||
(Math.round(circle.r * 1000) / 1000) % 1 !== 0
|
||||
? "red"
|
||||
: undefined
|
||||
}
|
||||
stroke={(Math.round(circle.r * 1000) / 1000) % 1 !== 0 ? 'red' : undefined}
|
||||
/>
|
||||
</React.Fragment>
|
||||
),
|
||||
@@ -228,15 +317,29 @@ const Handles = ({
|
||||
'strokeWidth' | 'stroke' | 'strokeDasharray' | 'strokeOpacity',
|
||||
any
|
||||
>) => {
|
||||
console.log(paths);
|
||||
return (
|
||||
<g className="svg-preview-handles-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-handles-group"
|
||||
{...props}
|
||||
>
|
||||
{paths.map(({ c, prev, next, cp1, cp2 }) => (
|
||||
<>
|
||||
{cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />}
|
||||
{cp1 && <circle cy={cp1.y} cx={cp1.x} r={0.25} />}
|
||||
{cp1 && (
|
||||
<circle
|
||||
cy={cp1.y}
|
||||
cx={cp1.x}
|
||||
r={0.25}
|
||||
/>
|
||||
)}
|
||||
{cp2 && <path d={`M${next.x} ${next.y} ${cp2.x} ${cp2.y}`} />}
|
||||
{cp2 && <circle cy={cp2.y} cx={cp2.x} r={0.25} />}
|
||||
{cp2 && (
|
||||
<circle
|
||||
cy={cp2.y}
|
||||
cx={cp2.x}
|
||||
r={0.25}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</g>
|
||||
@@ -252,19 +355,6 @@ const SvgPreview = React.forwardRef<
|
||||
>(({ src, children, showGrid = false, ...props }, ref) => {
|
||||
const paths = typeof src === 'string' ? getPaths(src) : src;
|
||||
|
||||
const darkModeCss = `@media screen and (prefers-color-scheme: light) {
|
||||
.svg-preview-grid-rect { fill: none }
|
||||
}
|
||||
@media screen and (prefers-color-scheme: dark) {
|
||||
.svg-preview-grid-rect { fill: none }
|
||||
.svg
|
||||
.svg-preview-grid-group,
|
||||
.svg-preview-radii-group,
|
||||
.svg-preview-shadow-mask-group,
|
||||
.svg-preview-shadow-group {
|
||||
stroke: #fff;
|
||||
}
|
||||
}`;
|
||||
return (
|
||||
<svg
|
||||
ref={ref}
|
||||
@@ -280,9 +370,27 @@ const SvgPreview = React.forwardRef<
|
||||
{...props}
|
||||
>
|
||||
<style>{darkModeCss}</style>
|
||||
{showGrid && <Grid strokeWidth={0.1} stroke="#777" strokeOpacity={0.3} radius={1} />}
|
||||
<Shadow paths={paths} strokeWidth={4} stroke="#777" radius={1} strokeOpacity={0.15} />
|
||||
<Handles paths={paths} strokeWidth={0.12} stroke="#777" strokeOpacity={0.6} />
|
||||
{showGrid && (
|
||||
<Grid
|
||||
strokeWidth={0.1}
|
||||
stroke="#777"
|
||||
strokeOpacity={0.3}
|
||||
radius={1}
|
||||
/>
|
||||
)}
|
||||
<Shadow
|
||||
paths={paths}
|
||||
strokeWidth={4}
|
||||
stroke="#777"
|
||||
radius={1}
|
||||
strokeOpacity={0.15}
|
||||
/>
|
||||
<Handles
|
||||
paths={paths}
|
||||
strokeWidth={0.12}
|
||||
stroke="#777"
|
||||
strokeOpacity={0.6}
|
||||
/>
|
||||
<ColoredPath
|
||||
paths={paths}
|
||||
colors={[
|
||||
@@ -307,8 +415,19 @@ const SvgPreview = React.forwardRef<
|
||||
stroke="#777"
|
||||
strokeOpacity={0.3}
|
||||
/>
|
||||
<ControlPath radius={1} paths={paths} pointSize={1} stroke="#fff" strokeWidth={0.125} />
|
||||
<Handles paths={paths} strokeWidth={0.12} stroke="#FFF" strokeOpacity={0.3} />
|
||||
<ControlPath
|
||||
radius={1}
|
||||
paths={paths}
|
||||
pointSize={1}
|
||||
stroke="#fff"
|
||||
strokeWidth={0.125}
|
||||
/>
|
||||
<Handles
|
||||
paths={paths}
|
||||
strokeWidth={0.12}
|
||||
stroke="#FFF"
|
||||
strokeOpacity={0.3}
|
||||
/>
|
||||
{children}
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,7 @@ export type Path = {
|
||||
|
||||
export type PathProps<
|
||||
RequiredProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
|
||||
NeverProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>
|
||||
NeverProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
|
||||
> = Required<Pick<React.SVGProps<SVGElement & SVGRectElement & SVGCircleElement>, RequiredProps>> &
|
||||
Omit<
|
||||
React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>,
|
||||
|
||||
@@ -51,7 +51,7 @@ export const getCommands = (src: string) =>
|
||||
getNodes(src)
|
||||
.map(convertToPathNode)
|
||||
.flatMap(({ d, name }, idx) =>
|
||||
new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name }))
|
||||
new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })),
|
||||
);
|
||||
|
||||
export const getPaths = (src: string) => {
|
||||
@@ -60,10 +60,10 @@ export const getPaths = (src: string) => {
|
||||
let prev: Point | undefined = undefined;
|
||||
let start: Point | undefined = undefined;
|
||||
const addPath = (
|
||||
c: typeof commands[number],
|
||||
c: (typeof commands)[number],
|
||||
next: Point,
|
||||
d?: string,
|
||||
extras?: { circle?: Path['circle']; cp1?: Path['cp1']; cp2?: Path['cp2'] }
|
||||
extras?: { circle?: Path['circle']; cp1?: Path['cp1']; cp2?: Path['cp2'] },
|
||||
) => {
|
||||
assert(prev);
|
||||
paths.push({
|
||||
@@ -153,7 +153,7 @@ export const getPaths = (src: string) => {
|
||||
{
|
||||
cp1: { x: prev.x - reflectedCp1.x, y: prev.y - reflectedCp1.y },
|
||||
cp2: { x: c.x2, y: c.y2 },
|
||||
}
|
||||
},
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -169,7 +169,7 @@ export const getPaths = (src: string) => {
|
||||
assert(prev);
|
||||
const backTrackCP = (
|
||||
index: number,
|
||||
currentPoint: { x: number; y: number }
|
||||
currentPoint: { x: number; y: number },
|
||||
): { x: number; y: number } => {
|
||||
const previousCommand = commands[index - 1];
|
||||
if (!previousCommand) {
|
||||
@@ -211,7 +211,7 @@ export const getPaths = (src: string) => {
|
||||
{
|
||||
cp1: { x: prevCP.x, y: prevCP.y },
|
||||
cp2: { x: prevCP.x, y: prevCP.y },
|
||||
}
|
||||
},
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -226,13 +226,13 @@ export const getPaths = (src: string) => {
|
||||
c.lArcFlag,
|
||||
c.sweepFlag,
|
||||
c.x,
|
||||
c.y
|
||||
c.y,
|
||||
);
|
||||
addPath(
|
||||
c,
|
||||
c,
|
||||
`M ${prev.x} ${prev.y} A${c.rX} ${c.rY} ${c.xRot} ${c.lArcFlag} ${c.sweepFlag} ${c.x} ${c.y}`,
|
||||
{ circle: c.rX === c.rY ? { ...center, r: c.rX } : undefined }
|
||||
{ circle: c.rX === c.rY ? { ...center, r: c.rX } : undefined },
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -253,7 +253,7 @@ export const arcEllipseCenter = (
|
||||
fa: number,
|
||||
fs: number,
|
||||
x2: number,
|
||||
y2: number
|
||||
y2: number,
|
||||
) => {
|
||||
const phi = (a * Math.PI) / 180;
|
||||
|
||||
@@ -280,7 +280,7 @@ export const arcEllipseCenter = (
|
||||
sign *
|
||||
Math.sqrt(
|
||||
Math.max(rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p, 0) /
|
||||
(rx * rx * y1p * y1p + ry * ry * x1p * x1p)
|
||||
(rx * rx * y1p * y1p + ry * ry * x1p * x1p),
|
||||
);
|
||||
|
||||
const V2 = [(rx * y1p) / ry, (-ry * x1p) / rx];
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import {Category, IconEntity} from "../theme/types";
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { Category, IconEntity } from '../theme/types';
|
||||
|
||||
const directory = path.join(process.cwd(), "../categories");
|
||||
const directory = path.join(process.cwd(), '../categories');
|
||||
|
||||
export function getAllCategoryFiles(): Category[] {
|
||||
const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json');
|
||||
|
||||
return fileNames.map((fileName) => {
|
||||
const name = path.basename(fileName, '.json')
|
||||
const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8')
|
||||
const name = path.basename(fileName, '.json');
|
||||
const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8');
|
||||
|
||||
const parsedFileContent = JSON.parse(fileContent)
|
||||
const parsedFileContent = JSON.parse(fileContent);
|
||||
|
||||
return {
|
||||
name,
|
||||
title: parsedFileContent.title,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function mapCategoryIconCount(categories: Category[], icons: { categories: IconEntity['categories'] }[]) {
|
||||
export function mapCategoryIconCount(
|
||||
categories: Category[],
|
||||
icons: { categories: IconEntity['categories'] }[],
|
||||
) {
|
||||
return categories.map((category) => ({
|
||||
...category,
|
||||
iconCount: icons.reduce((acc, curr) => (curr.categories.includes(category.name) ? ++acc : acc), 0)
|
||||
}))
|
||||
iconCount: icons.reduce(
|
||||
(acc, curr) => (curr.categories.includes(category.name) ? ++acc : acc),
|
||||
0,
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import {
|
||||
bundledLanguages,
|
||||
type ThemeRegistration
|
||||
} from 'shikiji'
|
||||
import {
|
||||
getHighlighter,
|
||||
} from 'shikiji'
|
||||
|
||||
import { bundledLanguages, type ThemeRegistration } from 'shikiji';
|
||||
import { getHighlighter } from 'shikiji';
|
||||
|
||||
type CodeExampleType = {
|
||||
title: string,
|
||||
language: string,
|
||||
code: string,
|
||||
}[]
|
||||
title: string;
|
||||
language: string;
|
||||
code: string;
|
||||
}[];
|
||||
|
||||
const getIconCodes = (): CodeExampleType => {
|
||||
return [
|
||||
{
|
||||
language: 'html',
|
||||
title: 'HTML',
|
||||
code: `<i data-lucide="Name"></i>`
|
||||
language: 'js',
|
||||
title: 'Vanilla',
|
||||
code: `\
|
||||
import { createIcons, icons } from 'lucide';
|
||||
|
||||
createIcons({ icons });
|
||||
|
||||
document.body.append('<i data-lucide="$Name"></i>');\
|
||||
`,
|
||||
},
|
||||
{
|
||||
language: 'tsx',
|
||||
title: 'React',
|
||||
code: `import { PascalCase } from 'lucide-react';
|
||||
code: `import { $PascalCase } from 'lucide-react';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<PascalCase />
|
||||
<$PascalCase />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -38,11 +38,11 @@ export default App;
|
||||
language: 'vue',
|
||||
title: 'Vue',
|
||||
code: `<script setup>
|
||||
import { PascalCase } from 'lucide-vue-next';
|
||||
import { $PascalCase } from 'lucide-vue-next';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PascalCase />
|
||||
<$PascalCase />
|
||||
</template>
|
||||
`,
|
||||
},
|
||||
@@ -50,20 +50,20 @@ export default App;
|
||||
language: 'svelte',
|
||||
title: 'Svelte',
|
||||
code: `<script>
|
||||
import { PascalCase } from 'lucide-svelte';
|
||||
import { $PascalCase } from 'lucide-svelte';
|
||||
</script>
|
||||
|
||||
<PascalCase />
|
||||
<$PascalCase />
|
||||
`,
|
||||
},
|
||||
{
|
||||
language: 'tsx',
|
||||
title: 'Preact',
|
||||
code: `import { PascalCase } from 'lucide-preact';
|
||||
code: `import { $PascalCase } from 'lucide-preact';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<PascalCase />
|
||||
<$PascalCase />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -73,11 +73,11 @@ export default App;
|
||||
{
|
||||
language: 'tsx',
|
||||
title: 'Solid',
|
||||
code: `import { PascalCase } from 'lucide-solid';
|
||||
code: `import { $PascalCase } from 'lucide-solid';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<PascalCase />
|
||||
<$PascalCase />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -88,16 +88,16 @@ export default App;
|
||||
language: 'tsx',
|
||||
title: 'Angular',
|
||||
code: `// app.module.ts
|
||||
import { LucideAngularModule, PascalCase } from 'lucide-angular';
|
||||
import { LucideAngularModule, $PascalCase } from 'lucide-angular';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
LucideAngularModule.pick({ PascalCase })
|
||||
LucideAngularModule.pick({ $PascalCase })
|
||||
],
|
||||
})
|
||||
|
||||
// app.component.html
|
||||
<lucide-icon name="Name"></lucide-icon>
|
||||
<lucide-icon name="$Name"></lucide-icon>
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -107,38 +107,39 @@ import { LucideAngularModule, PascalCase } from 'lucide-angular';
|
||||
@import ('~lucide-static/font/Lucide.css');
|
||||
</style>
|
||||
|
||||
<div class="icon-Name"></div>
|
||||
<div class="icon-$Name"></div>
|
||||
`,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export type ThemeOptions =
|
||||
| ThemeRegistration
|
||||
| { light: ThemeRegistration; dark: ThemeRegistration }
|
||||
| { light: ThemeRegistration; dark: ThemeRegistration };
|
||||
|
||||
const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
||||
const highlighter = await getHighlighter({
|
||||
themes: ['github-light', 'github-dark'],
|
||||
langs: Object.keys(bundledLanguages)
|
||||
})
|
||||
langs: Object.keys(bundledLanguages),
|
||||
});
|
||||
|
||||
const highlightedCode = highlighter.codeToHtml(code, {
|
||||
lang,
|
||||
themes: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark'
|
||||
},
|
||||
defaultColor: false
|
||||
}).replace('shiki-themes', 'shiki-themes vp-code')
|
||||
const highlightedCode = highlighter
|
||||
.codeToHtml(code, {
|
||||
lang,
|
||||
themes: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark',
|
||||
},
|
||||
defaultColor: false,
|
||||
})
|
||||
.replace('shiki-themes', 'shiki-themes vp-code');
|
||||
|
||||
return `<div class="language-${lang} ${active ? 'active' : ''}">
|
||||
<button title="Copy Code" class="copy"></button>
|
||||
<span class="lang">${lang}</span>
|
||||
${highlightedCode}
|
||||
</div>`
|
||||
}
|
||||
|
||||
</div>`;
|
||||
};
|
||||
|
||||
export default async function createCodeExamples() {
|
||||
const codes = getIconCodes();
|
||||
@@ -153,7 +154,7 @@ export default async function createCodeExamples() {
|
||||
language: language,
|
||||
code: codeString,
|
||||
};
|
||||
})
|
||||
});
|
||||
|
||||
return Promise.all(codeExamplePromises);
|
||||
}
|
||||
161
docs/.vitepress/lib/codeExamples/createLabCodeExamples.ts
Normal file
161
docs/.vitepress/lib/codeExamples/createLabCodeExamples.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { bundledLanguages, type ThemeRegistration } from 'shikiji';
|
||||
import { getHighlighter } from 'shikiji';
|
||||
|
||||
type CodeExampleType = {
|
||||
title: string;
|
||||
language: string;
|
||||
code: string;
|
||||
}[];
|
||||
|
||||
const getIconCodes = (): CodeExampleType => {
|
||||
return [
|
||||
{
|
||||
language: 'js',
|
||||
title: 'Vanilla',
|
||||
code: `\
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { $CamelCase } from '@lucide/lab';
|
||||
|
||||
createIcons({
|
||||
icons: {
|
||||
$CamelCase
|
||||
}
|
||||
});
|
||||
|
||||
document.body.append('<i data-lucide="$Name"></i>');\
|
||||
`,
|
||||
},
|
||||
{
|
||||
language: 'tsx',
|
||||
title: 'React',
|
||||
code: `import { Icon } from 'lucide-react';
|
||||
import { $CamelCase } from '@lucide/lab';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Icon iconNode={$CamelCase} />
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
`,
|
||||
},
|
||||
{
|
||||
language: 'vue',
|
||||
title: 'Vue',
|
||||
code: `<script setup>
|
||||
import { Icon } from 'lucide-vue-next';
|
||||
import { $CamelCase } from '@lucide/lab';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Icon :iconNode="burger" />
|
||||
</template>
|
||||
`,
|
||||
},
|
||||
{
|
||||
language: 'svelte',
|
||||
title: 'Svelte',
|
||||
code: `<script>
|
||||
import { Icon } from 'lucide-svelte';
|
||||
import { $CamelCase } from '@lucide/lab';
|
||||
</script>
|
||||
|
||||
<Icon iconNode={burger} />
|
||||
`,
|
||||
},
|
||||
{
|
||||
language: 'tsx',
|
||||
title: 'Preact',
|
||||
code: `import { Icon } from 'lucide-preact';
|
||||
import { $CamelCase } from '@lucide/lab';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Icon iconNode={$CamelCase} />
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
`,
|
||||
},
|
||||
{
|
||||
language: 'tsx',
|
||||
title: 'Solid',
|
||||
code: `import { Icon } from 'lucide-solid';
|
||||
import { $CamelCase } from '@lucide/lab';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Icon iconNode={$CamelCase} />
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
`,
|
||||
},
|
||||
{
|
||||
language: 'tsx',
|
||||
title: 'Angular',
|
||||
code: `// app.module.ts
|
||||
import { LucideAngularModule } from 'lucide-angular';
|
||||
import { $CamelCase } from '@lucide/lab';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
LucideAngularModule.pick({ $CamelCase })
|
||||
],
|
||||
})
|
||||
|
||||
// app.component.html
|
||||
<lucide-icon name="$CamelCase"></lucide-icon>
|
||||
`,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export type ThemeOptions =
|
||||
| ThemeRegistration
|
||||
| { light: ThemeRegistration; dark: ThemeRegistration };
|
||||
|
||||
const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
||||
const highlighter = await getHighlighter({
|
||||
themes: ['github-light', 'github-dark'],
|
||||
langs: Object.keys(bundledLanguages),
|
||||
});
|
||||
|
||||
const highlightedCode = highlighter
|
||||
.codeToHtml(code, {
|
||||
lang,
|
||||
themes: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark',
|
||||
},
|
||||
defaultColor: false,
|
||||
})
|
||||
.replace('shiki-themes', 'shiki-themes vp-code');
|
||||
|
||||
return `<div class="language-${lang} ${active ? 'active' : ''}">
|
||||
<button title="Copy Code" class="copy"></button>
|
||||
<span class="lang">${lang}</span>
|
||||
${highlightedCode}
|
||||
</div>`;
|
||||
};
|
||||
|
||||
export default async function createCodeExamples() {
|
||||
const codes = getIconCodes();
|
||||
|
||||
const codeExamplePromises = codes.map(async ({ title, language, code }, index) => {
|
||||
const isFirst = index === 0;
|
||||
|
||||
const codeString = await highLightCode(code, language, isFirst);
|
||||
|
||||
return {
|
||||
title,
|
||||
language: language,
|
||||
code: codeString,
|
||||
};
|
||||
});
|
||||
|
||||
return Promise.all(codeExamplePromises);
|
||||
}
|
||||
32
docs/.vitepress/lib/codeExamples/highLightCode.ts
Normal file
32
docs/.vitepress/lib/codeExamples/highLightCode.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { bundledLanguages, type ThemeRegistration } from 'shikiji';
|
||||
import { getHighlighter } from 'shikiji';
|
||||
|
||||
export type ThemeOptions =
|
||||
| ThemeRegistration
|
||||
| { light: ThemeRegistration; dark: ThemeRegistration };
|
||||
|
||||
const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
||||
const highlighter = await getHighlighter({
|
||||
themes: ['github-light', 'github-dark'],
|
||||
langs: Object.keys(bundledLanguages),
|
||||
});
|
||||
|
||||
const highlightedCode = highlighter
|
||||
.codeToHtml(code, {
|
||||
lang,
|
||||
themes: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark',
|
||||
},
|
||||
defaultColor: false,
|
||||
})
|
||||
.replace('shiki-themes', 'shiki-themes vp-code');
|
||||
|
||||
return `<div class="language-${lang} ${active ? 'active' : ''}">
|
||||
<button title="Copy Code" class="copy"></button>
|
||||
<span class="lang">${lang}</span>
|
||||
${highlightedCode}
|
||||
</div>`;
|
||||
};
|
||||
|
||||
export default highLightCode;
|
||||
5
docs/.vitepress/lib/codeExamples/types.ts
Normal file
5
docs/.vitepress/lib/codeExamples/types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export type CodeExampleType = {
|
||||
title: string;
|
||||
language: string;
|
||||
code: string;
|
||||
}[];
|
||||
@@ -1,38 +1,36 @@
|
||||
import { promises as fs, constants } from 'fs';
|
||||
import path from 'path';
|
||||
import yaml from 'js-yaml'
|
||||
import { PackageItem } from '../theme/types';
|
||||
|
||||
const fileExist = (filePath) => fs.access(filePath, constants.F_OK).then(() => true).catch(() => false)
|
||||
const fileExist = (filePath) =>
|
||||
fs
|
||||
.access(filePath, constants.F_OK)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
const fetchPackages = async (): Promise<PackageItem[]> => {
|
||||
const docsDir = path.resolve(process.cwd(), '../packages');
|
||||
const fileNames = await (await fs.readdir(docsDir)).map(filename => ({filename, directory: docsDir}))
|
||||
const fileNames = await (
|
||||
await fs.readdir(docsDir)
|
||||
).map((filename) => ({ filename, directory: docsDir }));
|
||||
|
||||
const packageJsons = await Promise.all(fileNames.map( async ({filename, directory}) => {
|
||||
const filePath = path.resolve(directory, filename)
|
||||
const fileStat = await fs.lstat(filePath);
|
||||
const packageJsons = await Promise.all(
|
||||
fileNames.map(async ({ filename, directory }) => {
|
||||
const filePath = path.resolve(directory, filename);
|
||||
const fileStat = await fs.lstat(filePath);
|
||||
|
||||
if(!fileStat.isDirectory()) return null;
|
||||
if (!fileStat.isDirectory()) return null;
|
||||
|
||||
const jsonFilePath = path.resolve(filePath, 'package.json')
|
||||
if (await fileExist(jsonFilePath)) {
|
||||
return JSON.parse(
|
||||
await fs.readFile(jsonFilePath, 'utf-8')
|
||||
)
|
||||
}
|
||||
const jsonFilePath = path.resolve(filePath, 'package.json');
|
||||
if (await fileExist(jsonFilePath)) {
|
||||
return JSON.parse(await fs.readFile(jsonFilePath, 'utf-8'));
|
||||
}
|
||||
|
||||
const ymlFilePath = path.resolve(filePath, 'pubspec.yaml')
|
||||
if(await fileExist(ymlFilePath)) {
|
||||
return yaml.load(
|
||||
await fs.readFile(ymlFilePath, 'utf-8')
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
);
|
||||
|
||||
return null
|
||||
}))
|
||||
|
||||
return packageJsons
|
||||
}
|
||||
return packageJsons;
|
||||
};
|
||||
|
||||
export default fetchPackages;
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
export type IconContent = [icon: string, src:string];
|
||||
export type IconContent = [icon: string, src: string];
|
||||
|
||||
async function generateZip(icons: IconContent[]) {
|
||||
const JSZip = (await import('jszip')).default
|
||||
const JSZip = (await import('jszip')).default;
|
||||
|
||||
const zip = new JSZip();
|
||||
|
||||
const addingZipPromises = icons.map(([name, src]) =>
|
||||
zip.file(`${name}.svg`, src),
|
||||
);
|
||||
const addingZipPromises = icons.map(([name, src]) => zip.file(`${name}.svg`, src));
|
||||
|
||||
await Promise.all(addingZipPromises)
|
||||
await Promise.all(addingZipPromises);
|
||||
|
||||
return zip.generateAsync({ type: 'blob' });
|
||||
}
|
||||
|
||||
export default generateZip
|
||||
export default generateZip;
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { createLucideIcon } from "lucide-react/src/lucide-react"
|
||||
import { type LucideProps, type IconNode } from "lucide-react/src/createLucideIcon"
|
||||
import { IconEntity } from "../theme/types"
|
||||
import { createLucideIcon } from 'lucide-react/src/lucide-react';
|
||||
import { type LucideProps, type IconNode } from 'lucide-react/src/createLucideIcon';
|
||||
import { IconEntity } from '../theme/types';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import { IconContent } from "./generateZip";
|
||||
import { IconContent } from './generateZip';
|
||||
|
||||
const getFallbackZip = (icons: IconEntity[], params: LucideProps) => {
|
||||
return icons
|
||||
.map<IconContent>((icon) => {
|
||||
const Icon = createLucideIcon(icon.name, icon.iconNode as IconNode)
|
||||
const src = renderToStaticMarkup(<Icon {...params} />)
|
||||
return [icon.name, src]
|
||||
})
|
||||
}
|
||||
return icons.map<IconContent>((icon) => {
|
||||
const Icon = createLucideIcon(icon.name, icon.iconNode as IconNode);
|
||||
const src = renderToStaticMarkup(<Icon {...params} />);
|
||||
return [icon.name, src];
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export default getFallbackZip
|
||||
export default getFallbackZip;
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { IconNodeWithKeys } from "../theme/types";
|
||||
import iconNodes from '../data/iconNodes'
|
||||
import releaseMeta from "../data/releaseMetaData.json";
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { IconNodeWithKeys } from '../theme/types';
|
||||
import iconNodes from '../data/iconNodes';
|
||||
import releaseMeta from '../data/releaseMetaData.json';
|
||||
|
||||
const DATE_OF_FORK = '2020-06-08T16:39:52+0100';
|
||||
|
||||
const directory = path.join(process.cwd(), "../icons");
|
||||
const directory = path.join(process.cwd(), '../icons');
|
||||
|
||||
export interface GetDataOptions {
|
||||
withChildKeys?: boolean
|
||||
withChildKeys?: boolean;
|
||||
}
|
||||
|
||||
export async function getData(name: string) {
|
||||
const jsonPath = path.join(directory, `${name}.json`);
|
||||
const jsonContent = fs.readFileSync(jsonPath, "utf8");
|
||||
const jsonContent = fs.readFileSync(jsonPath, 'utf8');
|
||||
const { tags, categories, contributors } = JSON.parse(jsonContent);
|
||||
|
||||
const iconNode = iconNodes[name]
|
||||
const iconNode = iconNodes[name];
|
||||
|
||||
const releaseData = releaseMeta?.[name] ?? {
|
||||
"createdRelease": {
|
||||
"version": "0.0.0",
|
||||
"date": DATE_OF_FORK
|
||||
createdRelease: {
|
||||
version: '0.0.0',
|
||||
date: DATE_OF_FORK,
|
||||
},
|
||||
"changedRelease": {
|
||||
"version": "0.0.0",
|
||||
"date": DATE_OF_FORK
|
||||
}
|
||||
}
|
||||
changedRelease: {
|
||||
version: '0.0.0',
|
||||
date: DATE_OF_FORK,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
name,
|
||||
@@ -36,11 +36,11 @@ export async function getData(name: string) {
|
||||
categories,
|
||||
iconNode,
|
||||
contributors,
|
||||
...releaseData
|
||||
...releaseData,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getAllData(): Promise<{ name: string, iconNode: IconNodeWithKeys}[]> {
|
||||
export async function getAllData(): Promise<{ name: string; iconNode: IconNodeWithKeys }[]> {
|
||||
const names = Object.keys(iconNodes);
|
||||
|
||||
return Promise.all(names.map((name) => getData(name)));
|
||||
|
||||
@@ -1,50 +1,54 @@
|
||||
import { DefaultTheme, UserConfig } from "vitepress"
|
||||
import { DefaultTheme, UserConfig } from 'vitepress';
|
||||
|
||||
const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
|
||||
'guide':[
|
||||
guide: [
|
||||
{
|
||||
text: 'Introduction',
|
||||
items: [
|
||||
{ text: 'What is lucide?', link: '/guide/' },
|
||||
{ text: 'Installation', link: '/guide/installation' },
|
||||
{ text: 'Comparison', link: '/guide/comparison' }
|
||||
]
|
||||
{ text: 'Comparison', link: '/guide/comparison' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Basics',
|
||||
items: [
|
||||
{
|
||||
text: 'Color',
|
||||
link: '/guide/basics/color'
|
||||
link: '/guide/basics/color',
|
||||
},
|
||||
{
|
||||
text: 'Sizing',
|
||||
link: '/guide/basics/sizing'
|
||||
link: '/guide/basics/sizing',
|
||||
},
|
||||
{
|
||||
text: 'Stroke width',
|
||||
link: '/guide/basics/stroke-width'
|
||||
link: '/guide/basics/stroke-width',
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
// TODO: Add this section
|
||||
{
|
||||
text: 'Advanced',
|
||||
items: [
|
||||
// {
|
||||
// text: 'Accessibility',
|
||||
// link: '/guide/advanced/accessibility'
|
||||
// },
|
||||
{
|
||||
text: 'Accessibility',
|
||||
link: '/guide/advanced/accessibility',
|
||||
},
|
||||
{
|
||||
text: 'Global styling',
|
||||
link: '/guide/advanced/global-styling'
|
||||
link: '/guide/advanced/global-styling',
|
||||
},
|
||||
// {
|
||||
// text: 'Animations',
|
||||
// },
|
||||
{
|
||||
text: 'Filled icons',
|
||||
link: '/guide/advanced/filled-icons'
|
||||
link: '/guide/advanced/filled-icons',
|
||||
},
|
||||
{
|
||||
text: 'Aliased Names',
|
||||
link: '/guide/advanced/aliased-names',
|
||||
},
|
||||
// {
|
||||
// text: 'Combining icons',
|
||||
@@ -55,75 +59,77 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
|
||||
// {
|
||||
// text: 'Auto importing'
|
||||
// },
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Packages',
|
||||
items: [
|
||||
{
|
||||
text: 'Lucide',
|
||||
link: '/guide/packages/lucide'
|
||||
link: '/guide/packages/lucide',
|
||||
},
|
||||
{
|
||||
text: 'Lucide React',
|
||||
link: '/guide/packages/lucide-react'
|
||||
},
|
||||
{
|
||||
text: 'Lucide React Native',
|
||||
link: '/guide/packages/lucide-react-native'
|
||||
link: '/guide/packages/lucide-react',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Vue',
|
||||
link: '/guide/packages/lucide-vue-next'
|
||||
link: '/guide/packages/lucide-vue-next',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Svelte',
|
||||
link: '/guide/packages/lucide-svelte'
|
||||
link: '/guide/packages/lucide-svelte',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Solid',
|
||||
link: '/guide/packages/lucide-solid'
|
||||
link: '/guide/packages/lucide-solid',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Preact',
|
||||
link: '/guide/packages/lucide-preact'
|
||||
text: 'Lucide React Native',
|
||||
link: '/guide/packages/lucide-react-native',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Angular',
|
||||
link: '/guide/packages/lucide-angular'
|
||||
link: '/guide/packages/lucide-angular',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Preact',
|
||||
link: '/guide/packages/lucide-preact',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Static',
|
||||
link: '/guide/packages/lucide-static'
|
||||
link: '/guide/packages/lucide-static',
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Contributing',
|
||||
items: [
|
||||
{
|
||||
text: 'Icon Design Principles',
|
||||
link: '/guide/design/icon-design-guide'
|
||||
link: '/guide/design/icon-design-guide',
|
||||
},
|
||||
{
|
||||
text: 'Designing in Illustrator',
|
||||
link: '/guide/design/illustrator-guide'
|
||||
link: '/guide/design/illustrator-guide',
|
||||
},
|
||||
{
|
||||
text: 'Designing in InkScape',
|
||||
link: '/guide/design/inkscape-guide'
|
||||
link: '/guide/design/inkscape-guide',
|
||||
},
|
||||
{
|
||||
text: 'Designing in Figma',
|
||||
link: '/guide/design/figma-guide'
|
||||
link: '/guide/design/figma-guide',
|
||||
},
|
||||
]
|
||||
{
|
||||
text: 'Designing in Affinity Designer',
|
||||
link: '/guide/design/affinity-designer-guide',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
// This should be here to keep the sidebar shown on the icons page
|
||||
'icons': [
|
||||
{ text: '', link: '/' },
|
||||
],
|
||||
}
|
||||
icons: [{ text: '', link: '/' }],
|
||||
};
|
||||
|
||||
export default sidebar
|
||||
export default sidebar;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useRouter } from 'vitepress';
|
||||
|
||||
const { go } = useRouter()
|
||||
@@ -8,7 +7,16 @@ const props = defineProps<{
|
||||
href?: string
|
||||
}>()
|
||||
|
||||
const isExternal = computed(() => props.href?.startsWith('http') ?? false)
|
||||
|
||||
const component = computed(() => props.href ? 'a' : 'div')
|
||||
const target = computed(() => isExternal.value ? '_blank' : undefined)
|
||||
const rel = computed(() => isExternal.value ? 'noreferrer noopener' : undefined)
|
||||
|
||||
const onClick = computed(() => {
|
||||
if(!props.href || isExternal) return
|
||||
return go(props.href)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -16,7 +24,9 @@ const component = computed(() => props.href ? 'a' : 'div')
|
||||
:is="component"
|
||||
:href="href"
|
||||
class="badge"
|
||||
@click="props?.href ? go(href) : undefined"
|
||||
:target="target"
|
||||
:rel="rel"
|
||||
@click="onClick"
|
||||
>
|
||||
<slot/>
|
||||
</component>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="grid">
|
||||
<div class="card-grid-flex">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.grid {
|
||||
<style>
|
||||
.card-grid-flex {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: stretch;
|
||||
@@ -15,20 +15,20 @@
|
||||
margin: -8px;
|
||||
}
|
||||
|
||||
.grid > * {
|
||||
.card-grid-flex > * {
|
||||
flex-basis: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.grid > * {
|
||||
.card-grid-flex > * {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.grid > * {
|
||||
.card-grid-flex > * {
|
||||
flex-basis: 33.33%;
|
||||
}
|
||||
}
|
||||
|
||||
90
docs/.vitepress/theme/components/base/Checkbox.vue
Normal file
90
docs/.vitepress/theme/components/base/Checkbox.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
label: string
|
||||
id: string
|
||||
value: string
|
||||
modelValue: string | string[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['change', 'input', 'update:modelValue'])
|
||||
|
||||
const model = computed({
|
||||
get: () => {
|
||||
if (Array.isArray(props.modelValue)) {
|
||||
return props.modelValue.includes(props.value)
|
||||
}
|
||||
return props.modelValue === props.value
|
||||
|
||||
},
|
||||
set: (value: string) => {
|
||||
if (Array.isArray(props.modelValue)) {
|
||||
const newValue = [...props.modelValue]
|
||||
const index = newValue.indexOf(props.value)
|
||||
if (value) {
|
||||
if (index === -1) {
|
||||
newValue.push(props.value)
|
||||
}
|
||||
} else {
|
||||
if (index !== -1) {
|
||||
newValue.splice(index, 1)
|
||||
}
|
||||
}
|
||||
emit('update:modelValue', newValue)
|
||||
} else {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="checkbox-wrapper">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
ref="input"
|
||||
:id="id"
|
||||
v-model="model"
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
<label :for="id" class="checkbox-label">
|
||||
{{ label }}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.checkbox-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
line-height: 20px;
|
||||
font-size: 13px;
|
||||
color: var(--vt-c-text-1);
|
||||
transition: color .5s;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--vp-input-border-color);
|
||||
background-color: var(--vp-input-switch-bg-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.checkbox:checked {
|
||||
border-color: transparent;
|
||||
background: url("data:image/svg+xml,%3Csvg width='12px' height='12px' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='4' stroke-linecap='round' stroke-linejoin='round' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E")
|
||||
center no-repeat var(--vp-c-brand);;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -24,40 +24,10 @@ const headingElement = computed(() => `h${props.headingLevel}`)
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-bottom: 96px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
align-content: space-evenly;
|
||||
box-sizing: border-box;
|
||||
margin: -8px;
|
||||
}
|
||||
|
||||
.grid > * {
|
||||
flex-basis: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.grid > * {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.grid > * {
|
||||
flex-basis: 33.33%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
@@ -42,7 +42,7 @@ onMounted(() => {
|
||||
font-weight: 400;
|
||||
background: var(--vp-c-brand-dark);
|
||||
color: white;
|
||||
z-index: 10;
|
||||
z-index: 99;
|
||||
white-space: nowrap;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
export default {
|
||||
async load() {
|
||||
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest').then(res => {
|
||||
if (res.ok) {
|
||||
const releaseData = res.json() as Promise<{ tag_name: string }>
|
||||
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest')
|
||||
.then((res) => {
|
||||
if (res.ok) {
|
||||
const releaseData = res.json() as Promise<{ tag_name: string }>;
|
||||
|
||||
return releaseData
|
||||
}
|
||||
return null
|
||||
}).then(res => res.tag_name)
|
||||
return releaseData;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.then((res) => res.tag_name);
|
||||
|
||||
return {
|
||||
version
|
||||
}
|
||||
}
|
||||
}
|
||||
version,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -8,8 +8,6 @@ import { data } from './HomeHeroBefore.data'
|
||||
<HomeContainer class="container">
|
||||
<Badge
|
||||
:href="`https://github.com/lucide-icons/lucide/releases/tag/${data.version}`"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>v{{ data.version }}</Badge>
|
||||
</HomeContainer>
|
||||
</template>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import iconNodes from '../../../data/iconNodes'
|
||||
import iconNodes from '../../../data/iconNodes';
|
||||
|
||||
const getRandomItem = <Item>(items: Item[]): Item => items[Math.floor(Math.random()*items.length)];
|
||||
const getRandomItem = <Item>(items: Item[]): Item =>
|
||||
items[Math.floor(Math.random() * items.length)];
|
||||
|
||||
export default {
|
||||
async load() {
|
||||
const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode }))
|
||||
const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode }));
|
||||
|
||||
const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons))
|
||||
const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons));
|
||||
|
||||
return {
|
||||
icons: randomIcons,
|
||||
iconsCount: icons.length,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -28,8 +28,6 @@ function insert() {
|
||||
const replaceIndex = random(0, 48)
|
||||
const newIcon = getRandomNewIcon()
|
||||
|
||||
// items.value.splice(replaceIndex, 0, newIcon);
|
||||
|
||||
items.value[replaceIndex] = newIcon
|
||||
}
|
||||
|
||||
@@ -76,7 +74,6 @@ onBeforeUnmount(() => {
|
||||
|
||||
<style scoped>
|
||||
.card-wrapper {
|
||||
/* padding: 0 24px; */
|
||||
margin-left: auto;
|
||||
margin-bottom: auto;
|
||||
margin-top: 48px;
|
||||
@@ -87,13 +84,10 @@ onBeforeUnmount(() => {
|
||||
border-radius: 8px;
|
||||
width: 100%;
|
||||
height:100%;
|
||||
/* box-shadow: var(--vp-shadow-2); */
|
||||
max-height: 220px;
|
||||
max-width: 560px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
/* max-height: 240px; */
|
||||
/* margin-top: 96px; */
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
@@ -107,7 +101,6 @@ onBeforeUnmount(() => {
|
||||
max-width: 512px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
/* white-space: nowrap; */
|
||||
}
|
||||
|
||||
.list-enter-active {
|
||||
|
||||
@@ -171,16 +171,14 @@ watch(absoluteStrokeWidth, (enabled) => {
|
||||
margin-top: 32px;
|
||||
padding: 0;
|
||||
background: none;
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
|
||||
.card {
|
||||
display: grid;
|
||||
grid-template-columns: 8fr 10fr;
|
||||
}
|
||||
/*
|
||||
/*
|
||||
.card-column {
|
||||
flex: 1;
|
||||
} */
|
||||
|
||||
@@ -47,7 +47,7 @@ export default {
|
||||
logo: '/framework-logos/flutter.svg',
|
||||
label: 'Lucide documentation for Flutter',
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import HomeContainer from './HomeContainer.vue'
|
||||
import HomeSectionTitle from './HomeSectionTitle.vue'
|
||||
import { useRouter } from 'vitepress';
|
||||
import { data } from './HomePackagesSection.data'
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||
@@ -9,7 +10,7 @@ const { go } = useRouter()
|
||||
|
||||
<template>
|
||||
<HomeContainer>
|
||||
<h2 class="section-title">Available For:</h2>
|
||||
<HomeSectionTitle>Available For:</HomeSectionTitle>
|
||||
<div class="packages-list">
|
||||
<a
|
||||
v-for="{ name, logo } in data.packages"
|
||||
@@ -34,14 +35,6 @@ const { go } = useRouter()
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.section-title {
|
||||
color: var(--vp-c-text-2);
|
||||
font-weight: 500;
|
||||
line-height: 32px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.packages-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
26
docs/.vitepress/theme/components/home/HomeSectionTitle.vue
Normal file
26
docs/.vitepress/theme/components/home/HomeSectionTitle.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
headingLevel: 1 | 2 | 3 | 4 | 5 | 6,
|
||||
}>()
|
||||
|
||||
const headingElement = computed(() => `h${props.headingLevel ?? 2}`)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="headingElement" class="section-title">
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.section-title {
|
||||
color: var(--vp-c-text-2);
|
||||
font-weight: 500;
|
||||
line-height: 32px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
59
docs/.vitepress/theme/components/home/HomeSponsorCard.vue
Normal file
59
docs/.vitepress/theme/components/home/HomeSponsorCard.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import Card from '../base/Card.vue';
|
||||
import HomeSectionTitle from './HomeSectionTitle.vue';
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HomeSectionTitle :headingLevel="3"> Sponsor the Lucide maintainers </HomeSectionTitle>
|
||||
<Card class="sponsor-card">
|
||||
<img
|
||||
src="/company-logos/open-collective-light.svg"
|
||||
alt="Open Collective logo"
|
||||
class="logo light"
|
||||
width="242"
|
||||
height="42"
|
||||
/>
|
||||
<img
|
||||
src="/company-logos/open-collective-dark.svg"
|
||||
alt="Open Collective logo"
|
||||
class="logo dark"
|
||||
width="242"
|
||||
height="42"
|
||||
/>
|
||||
<VPButton
|
||||
href="https://opencollective.com/lucide-icons"
|
||||
class="sponsor-button"
|
||||
text="Become a sponsor"
|
||||
/>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sponsor-card {
|
||||
margin: 0 auto;
|
||||
max-width: 500px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.sponsor-button {
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
html.dark .logo.dark {
|
||||
display: none;
|
||||
}
|
||||
html:not(.dark) .logo.light {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.sponsor-card {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
91
docs/.vitepress/theme/components/home/HomeTeamSection.vue
Normal file
91
docs/.vitepress/theme/components/home/HomeTeamSection.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<script setup lang="ts">
|
||||
import { useData } from 'vitepress';
|
||||
import HomeContainer from './HomeContainer.vue'
|
||||
import GridSection from '../base/GridSection.vue'
|
||||
import TeamMemberCard, { TeamMember } from './TeamMemberCard.vue'
|
||||
import teamData from '../../../data/teamData.json'
|
||||
import HomeSponsorCard from './HomeSponsorCard.vue';
|
||||
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue'
|
||||
|
||||
|
||||
const { theme } = useData()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HomeContainer>
|
||||
<GridSection
|
||||
title="Meet the team"
|
||||
:headingLevel="2"
|
||||
class="team-members"
|
||||
>
|
||||
<TeamMemberCard
|
||||
v-for="teamMember in (teamData as TeamMember[])"
|
||||
v-bind="teamMember"
|
||||
/>
|
||||
</GridSection>
|
||||
<HomeSponsorCard />
|
||||
<VPDocAsideCarbonAds
|
||||
:carbon-ads="theme.carbonAds"
|
||||
class="ad-card"
|
||||
/>
|
||||
</HomeContainer>
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.team-members {
|
||||
gap: 24px;
|
||||
margin-top: 48px;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.team-members :deep(.card-grid > *) {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.team-members :deep(.card-grid > *) {
|
||||
flex-basis: 33.33%;
|
||||
}
|
||||
}
|
||||
|
||||
.ad-card {
|
||||
margin: 32px auto 0;
|
||||
width: 100%;;
|
||||
max-width: 500px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.ad-card {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.ad-card :deep(.VPCarbonAds) {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.ad-card :deep(.VPCarbonAds .carbon-wrap) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.ad-card :deep(.VPCarbonAds .carbon-text) {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.ad-card :deep(.VPCarbonAds .carbon-poweredby) {
|
||||
text-align: right;
|
||||
margin-top: -24px;
|
||||
}
|
||||
</style>
|
||||
92
docs/.vitepress/theme/components/home/TeamMemberCard.vue
Normal file
92
docs/.vitepress/theme/components/home/TeamMemberCard.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<script lang="ts">
|
||||
export interface TeamMember {
|
||||
name: string
|
||||
title: string
|
||||
image: string
|
||||
sponsor?: string
|
||||
socialLinks: DefaultTheme.SocialLink[]
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DefaultTheme } from 'vitepress';
|
||||
import Card from '../base/Card.vue'
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
|
||||
import VPSocialLinks from 'vitepress/dist/client/theme-default/components/VPSocialLinks.vue'
|
||||
|
||||
defineProps<TeamMember>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Card class="member-card">
|
||||
<img :src="image" :alt="name" class="member-image"/>
|
||||
<h3 class="member-name">
|
||||
{{name}}
|
||||
</h3>
|
||||
<p class="member-title">
|
||||
{{title}}
|
||||
</p>
|
||||
<div class="member-links">
|
||||
<VPButton
|
||||
v-if="sponsor"
|
||||
:href="sponsor"
|
||||
text="Sponsor"
|
||||
theme="sponsor"
|
||||
class="sponsor-button"
|
||||
size="medium"
|
||||
/>
|
||||
<VPSocialLinks
|
||||
:links="socialLinks"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.member-card {
|
||||
flex-basis: 100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
.member-image {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 32px;
|
||||
margin: 0 auto;
|
||||
background-color: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.member-name {
|
||||
text-align: center;
|
||||
margin-top: 16px;
|
||||
font-size: 21px;
|
||||
font-weight: 500;
|
||||
color: var(--textColor);
|
||||
}
|
||||
|
||||
.member-title {
|
||||
flex-grow: 1;
|
||||
padding-top: 8px;
|
||||
line-height: 24px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
text-align: center;
|
||||
text-wrap: balance;
|
||||
margin-bottom: 16px;;
|
||||
}
|
||||
|
||||
.sponsor-button {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.member-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
83
docs/.vitepress/theme/components/icons/CarbonAdOverlay.vue
Normal file
83
docs/.vitepress/theme/components/icons/CarbonAdOverlay.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<script setup lang="ts">
|
||||
import { useData } from 'vitepress';
|
||||
import { useSessionStorage } from '@vueuse/core';
|
||||
import IconButton from '../base/IconButton.vue';
|
||||
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue'
|
||||
import { x } from '../../../data/iconNodes'
|
||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||
|
||||
const { theme } = useData()
|
||||
const showAd = useSessionStorage('show-carbon-ads',true)
|
||||
|
||||
defineProps<{
|
||||
drawerOpen: boolean
|
||||
}>()
|
||||
|
||||
const CloseIcon = createLucideIcon('Close', x)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'drawer-open': drawerOpen,
|
||||
'hide-ad': !showAd
|
||||
}"
|
||||
class="floating-ad"
|
||||
v-if="theme.carbonAds"
|
||||
>
|
||||
<IconButton @click="showAd = false" class="hide-button">
|
||||
<component :is="CloseIcon" :size="20" absoluteStrokeWidth />
|
||||
</IconButton>
|
||||
<VPDocAsideCarbonAds
|
||||
:carbon-ads="theme.carbonAds"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.floating-ad {
|
||||
display: none;
|
||||
position: fixed;
|
||||
bottom: 32px;
|
||||
width: 224px;
|
||||
right: 32px;
|
||||
transition: opacity 0.5s, transform 0.25s ease;
|
||||
}
|
||||
|
||||
.floating-ad.drawer-open {
|
||||
transform: translateY(-288px);
|
||||
}
|
||||
|
||||
.floating-ad.hide-ad {
|
||||
transform: translateX(224px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.floating-ad.drawer-open.hide-ad {
|
||||
transform: translateY(-288px) translateX(224px);
|
||||
}
|
||||
|
||||
.floating-ad.drawer-open, .floating-ad.hide-ad {
|
||||
transition: opacity 0.25s, transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.floating-ad {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.floating-ad {
|
||||
right: calc(((100% - (var(--vp-layout-max-width) - var(--vp-sidebar-width))) - 272px) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
.hide-button {
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
@@ -1,16 +1,15 @@
|
||||
import { getAllData } from '../../../lib/icons';
|
||||
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
|
||||
import iconsMetaData from '../../../data/iconMetaData'
|
||||
|
||||
import iconsMetaData from '../../../data/iconMetaData';
|
||||
|
||||
export default {
|
||||
async load() {
|
||||
let categories = getAllCategoryFiles()
|
||||
let categories = getAllCategoryFiles();
|
||||
|
||||
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData))
|
||||
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData));
|
||||
|
||||
return {
|
||||
categories,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useData } from 'vitepress'
|
||||
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
|
||||
import { isActive } from 'vitepress/dist/client/shared'
|
||||
import { useActiveAnchor } from '../../composables/useActiveAnchor'
|
||||
import { data } from './CategoryList.data'
|
||||
import CategoryListItem from './CategoryListItem.vue'
|
||||
import SidebarTitle from './SidebarTitle.vue'
|
||||
import { useCategoryView } from '../../composables/useCategoryView'
|
||||
|
||||
const { page } = useData()
|
||||
const { categoryCounts } = useCategoryView();
|
||||
|
||||
const categoriesIsActive = computed(() => {
|
||||
return isActive(page.value.relativePath, '/icons/categories');
|
||||
@@ -25,22 +28,32 @@ const headers = computed(() => {
|
||||
level: 2,
|
||||
link: `${linkPrefix}#${name}`,
|
||||
title,
|
||||
iconCount
|
||||
iconCount: categoryCounts.value[name] ?? iconCount,
|
||||
name
|
||||
}))
|
||||
})
|
||||
|
||||
const container = ref()
|
||||
const marker = ref()
|
||||
|
||||
useActiveAnchor(container, marker)
|
||||
const { setActiveLinkDebounced } = useActiveAnchor(container, marker)
|
||||
|
||||
watch(headers, () => {
|
||||
setTimeout(() => {
|
||||
setActiveLinkDebounced()
|
||||
}, 200)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="category-list" ref="container">
|
||||
<VPLink class="sidebar-title" href="/icons/" :class="{ 'active': overviewIsActive } ">
|
||||
<SidebarTitle>
|
||||
View
|
||||
</SidebarTitle>
|
||||
<VPLink class="sidebar-link sidebar-text" href="/icons/" :class="{ 'active': overviewIsActive } ">
|
||||
All
|
||||
</VPLink>
|
||||
<VPLink class="sidebar-title" href="/icons/categories" :class="{ 'active': categoriesIsActive } ">
|
||||
<VPLink class="sidebar-link sidebar-text" href="/icons/categories" :class="{ 'active': categoriesIsActive } ">
|
||||
Categories
|
||||
</VPLink>
|
||||
<div class="content">
|
||||
@@ -53,17 +66,20 @@ useActiveAnchor(container, marker)
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sidebar-title {
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 6px;
|
||||
.sidebar-text {
|
||||
line-height: 24px;
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
transition: color 0.25s;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.sidebar-title:hover, .sidebar-title.active {
|
||||
.sidebar-link {
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.sidebar-link:hover, .sidebar-link.active {
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
.content {
|
||||
|
||||
@@ -7,6 +7,7 @@ interface Header {
|
||||
slug: string;
|
||||
iconCount: number;
|
||||
link: string;
|
||||
name: string;
|
||||
children: Header[];
|
||||
}
|
||||
|
||||
@@ -14,36 +15,35 @@ type MenuItem = Omit<Header, 'slug' | 'children'> & {
|
||||
children?: MenuItem[];
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
headers: MenuItem[];
|
||||
root?: boolean;
|
||||
}>();
|
||||
|
||||
const { selectedCategory } = useCategoryView();
|
||||
|
||||
function onClick(event: Event) {
|
||||
const target =
|
||||
(event.target as HTMLElement).nodeName === 'span'
|
||||
? (event.target as HTMLElement).parentNode
|
||||
: (event.target as HTMLElement);
|
||||
const href = (target as HTMLAnchorElement)?.href;
|
||||
function onClick(categoryName: string) {
|
||||
selectedCategory.value = categoryName;
|
||||
|
||||
if (href) {
|
||||
const id = '#' + href.split('#')[1];
|
||||
const decodedId = decodeURIComponent(id);
|
||||
const heading = document.querySelector<HTMLAnchorElement>(categoryName);
|
||||
heading?.focus();
|
||||
|
||||
selectedCategory.value = decodedId.replace('#', '');
|
||||
|
||||
const heading = document.querySelector<HTMLAnchorElement>(decodedId);
|
||||
heading?.focus();
|
||||
}
|
||||
window.history.pushState({}, '', `/icons/categories#${categoryName}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul :class="root ? 'root' : 'nested'">
|
||||
<li v-for="{ children, link, title, iconCount } in headers">
|
||||
<a class="outline-link" :href="link" @click="onClick" :title="title">
|
||||
<li v-for="{ children, link, title, iconCount, name } in headers">
|
||||
<a
|
||||
class="outline-link"
|
||||
:href="link"
|
||||
@click="onClick(name)"
|
||||
:title="title"
|
||||
:class="{
|
||||
inactive: iconCount === 0,
|
||||
}"
|
||||
>
|
||||
<span>
|
||||
{{ title }}
|
||||
</span>
|
||||
@@ -83,6 +83,10 @@ function onClick(event: Event) {
|
||||
transition: color 0.25s;
|
||||
}
|
||||
|
||||
.outline-link.inactive {
|
||||
color: var(--vp-c-text-4);
|
||||
}
|
||||
|
||||
.outline-link.nested {
|
||||
padding-left: 13px;
|
||||
}
|
||||
@@ -93,4 +97,8 @@ function onClick(event: Event) {
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.outline-link.inactive .icon-count {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,92 +1,98 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { startCase, camelCase } from 'lodash-es'
|
||||
import ButtonMenu from '../base/ButtonMenu.vue'
|
||||
import { toPascalCase } from '@lucide/shared';
|
||||
import ButtonMenu from '../base/ButtonMenu.vue';
|
||||
import { useIconStyleContext } from '../../composables/useIconStyle';
|
||||
import useConfetti from '../../composables/useConfetti';
|
||||
|
||||
const props = defineProps<{
|
||||
name: string
|
||||
popoverPosition?: 'top' | 'bottom'
|
||||
}>()
|
||||
const { size, color, strokeWidth, absoluteStrokeWidth } = useIconStyleContext()
|
||||
const { animate, confetti } = useConfetti()
|
||||
name: string;
|
||||
popoverPosition?: 'top' | 'bottom';
|
||||
}>();
|
||||
const { size, color, strokeWidth, absoluteStrokeWidth } = useIconStyleContext();
|
||||
const { animate, confetti } = useConfetti();
|
||||
const componentName = computed(() => {
|
||||
return startCase(camelCase(props.name)).replace(/\s/g, '')
|
||||
})
|
||||
return (toPascalCase(props.name) as string).replace(/\s/g, '');
|
||||
});
|
||||
|
||||
function copyJSX() {
|
||||
let attrs = ['']
|
||||
let attrs = [''];
|
||||
|
||||
if (size.value && size.value !== 24) {
|
||||
attrs.push(`size={${size.value}}`)
|
||||
attrs.push(`size={${size.value}}`);
|
||||
}
|
||||
|
||||
if (color.value && color.value !== 'currentColor') {
|
||||
attrs.push(`color="${color.value}"`)
|
||||
attrs.push(`color="${color.value}"`);
|
||||
}
|
||||
|
||||
if (strokeWidth.value && strokeWidth.value !== 2) {
|
||||
attrs.push(`strokeWidth={${strokeWidth.value}}`)
|
||||
attrs.push(`strokeWidth={${strokeWidth.value}}`);
|
||||
}
|
||||
|
||||
if (absoluteStrokeWidth.value) {
|
||||
attrs.push(`absoluteStrokeWidth`)
|
||||
attrs.push(`absoluteStrokeWidth`);
|
||||
}
|
||||
|
||||
const code = `<${componentName.value}${attrs.join(' ')} />`
|
||||
const code = `<${componentName.value}${attrs.join(' ')} />`;
|
||||
|
||||
navigator.clipboard.writeText(code)
|
||||
navigator.clipboard.writeText(code);
|
||||
}
|
||||
|
||||
function copyComponentName() {
|
||||
const code = componentName.value;
|
||||
|
||||
navigator.clipboard.writeText(code);
|
||||
}
|
||||
|
||||
function copyVue() {
|
||||
let attrs = ['']
|
||||
let attrs = [''];
|
||||
|
||||
if (size.value && size.value !== 24) {
|
||||
attrs.push(`:size="${size.value}"`)
|
||||
attrs.push(`:size="${size.value}"`);
|
||||
}
|
||||
|
||||
if (color.value && color.value !== 'currentColor') {
|
||||
attrs.push(`color="${color.value}"`)
|
||||
attrs.push(`color="${color.value}"`);
|
||||
}
|
||||
|
||||
if (strokeWidth.value && strokeWidth.value !== 2) {
|
||||
attrs.push(`:stroke-width="${strokeWidth.value}"`)
|
||||
attrs.push(`:stroke-width="${strokeWidth.value}"`);
|
||||
}
|
||||
|
||||
if (absoluteStrokeWidth.value) {
|
||||
attrs.push(`absoluteStrokeWidth`)
|
||||
attrs.push(`absoluteStrokeWidth`);
|
||||
}
|
||||
|
||||
const code = `<${componentName.value}${attrs.join(' ')} />`
|
||||
const code = `<${componentName.value}${attrs.join(' ')} />`;
|
||||
|
||||
navigator.clipboard.writeText(code)
|
||||
navigator.clipboard.writeText(code);
|
||||
}
|
||||
|
||||
function copyAngular() {
|
||||
let attrs = ['']
|
||||
let attrs = [''];
|
||||
|
||||
attrs.push(`name="${props.name}"`)
|
||||
attrs.push(`name="${props.name}"`);
|
||||
|
||||
if (size.value && size.value !== 24) {
|
||||
attrs.push(`[size]="${size.value}"`)
|
||||
attrs.push(`[size]="${size.value}"`);
|
||||
}
|
||||
|
||||
if (color.value && color.value !== 'currentColor') {
|
||||
attrs.push(`color="${color.value}"`)
|
||||
attrs.push(`color="${color.value}"`);
|
||||
}
|
||||
|
||||
if (strokeWidth.value && strokeWidth.value !== 2) {
|
||||
attrs.push(`[strokeWidth]="${strokeWidth.value}"`)
|
||||
attrs.push(`[strokeWidth]="${strokeWidth.value}"`);
|
||||
}
|
||||
|
||||
if (absoluteStrokeWidth.value) {
|
||||
attrs.push(`[absoluteStrokeWidth]="true"`)
|
||||
attrs.push(`[absoluteStrokeWidth]="true"`);
|
||||
}
|
||||
|
||||
const code = `<lucide-icon${attrs.join(' ')}></lucide-icon>`
|
||||
const code = `<lucide-icon${attrs.join(' ')}></lucide-icon>`;
|
||||
|
||||
navigator.clipboard.writeText(code)
|
||||
navigator.clipboard.writeText(code);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -100,10 +106,11 @@ function copyAngular() {
|
||||
data-confetti-text="Copied!"
|
||||
:popoverPosition="popoverPosition"
|
||||
:options="[
|
||||
{ text: 'Copy JSX' , onClick: copyJSX },
|
||||
{ text: 'Copy Vue' , onClick: copyVue },
|
||||
{ text: 'Copy Svelte' , onClick: copyJSX },
|
||||
{ text: 'Copy Angular' , onClick: copyAngular },
|
||||
{ text: 'Copy JSX', onClick: copyJSX },
|
||||
{ text: 'Copy Component Name', onClick: copyComponentName },
|
||||
{ text: 'Copy Vue', onClick: copyVue },
|
||||
{ text: 'Copy Svelte', onClick: copyJSX },
|
||||
{ text: 'Copy Angular', onClick: copyAngular },
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -11,14 +11,31 @@ import IconInfo from './IconInfo.vue';
|
||||
import Badge from '../base/Badge.vue';
|
||||
import { computedAsync } from '@vueuse/core';
|
||||
import { satisfies } from 'semver';
|
||||
import { useExternalLibs } from '../../composables/useExternalLibs';
|
||||
|
||||
const props = defineProps<{
|
||||
iconName: string
|
||||
iconName: string | null
|
||||
}>()
|
||||
|
||||
const { externalIconNodes } = useExternalLibs()
|
||||
|
||||
const { go } = useRouter()
|
||||
|
||||
const icon = computedAsync<IconEntity | null>(async () => {
|
||||
if (props.iconName) {
|
||||
return (await import(`../../../data/iconDetails/${props.iconName}.ts`)).default as IconEntity
|
||||
try {
|
||||
if (props.iconName.includes(':')) {
|
||||
const [library, name] = props.iconName.split(':')
|
||||
|
||||
return externalIconNodes.value[library].find((icon) => icon.name === name)
|
||||
} else {
|
||||
return (await import(`../../../data/iconDetails/${props.iconName}.ts`)).default as IconEntity
|
||||
}
|
||||
} catch (err) {
|
||||
if (!props.iconName.includes(':')) {
|
||||
go(`/icons/${props.iconName}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}, null)
|
||||
@@ -36,8 +53,6 @@ function onClose() {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const { go } = useRouter()
|
||||
|
||||
const CloseIcon = createLucideIcon('Close', x)
|
||||
const Expand = createLucideIcon('Expand', expand)
|
||||
</script>
|
||||
@@ -51,10 +66,8 @@ const Expand = createLucideIcon('Expand', expand)
|
||||
v-if="icon.createdRelease"
|
||||
class="version"
|
||||
:href="releaseTagLink(icon.createdRelease.version)"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>v{{ icon.createdRelease.version }}</Badge>
|
||||
<IconButton @click="go(`/icons/${icon.name}`)">
|
||||
<IconButton @click="go(icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}` : `/icons/${icon.name}`)">
|
||||
<component :is="Expand" />
|
||||
</IconButton>
|
||||
<IconButton @click="onClose">
|
||||
@@ -144,11 +157,11 @@ const Expand = createLucideIcon('Expand', expand)
|
||||
}
|
||||
|
||||
.drawer-enter-active {
|
||||
transition: all 0.2s cubic-bezier(.21,.8,.46,.9);
|
||||
transition: opacity 0.5s, transform 0.25s ease;
|
||||
}
|
||||
|
||||
.drawer-leave-active {
|
||||
transition: all 0.4s cubic-bezier(1, 0.5, 0.8, 1);
|
||||
transition: opacity 0.25s ease, transform 1.6s ease-out;
|
||||
}
|
||||
|
||||
.drawer-enter-from,
|
||||
|
||||
@@ -4,7 +4,7 @@ import IconItem from './IconItem.vue'
|
||||
|
||||
const emit = defineEmits(['setActiveIcon'])
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
icons: IconEntity[]
|
||||
activeIcon?: string
|
||||
overlayMode?: boolean
|
||||
@@ -25,8 +25,10 @@ function setActiveIcon(name: string) {
|
||||
:key="icon.name"
|
||||
>
|
||||
<IconItem
|
||||
v-bind="icon"
|
||||
@setActiveIcon="setActiveIcon(icon.name)"
|
||||
:iconNode="icon.iconNode"
|
||||
:name="icon.name"
|
||||
:externalLibrary="icon.externalLibrary"
|
||||
@setActiveIcon="setActiveIcon"
|
||||
:active="activeIcon === icon.name"
|
||||
customizable
|
||||
:overlayMode="overlayMode"
|
||||
@@ -40,7 +42,6 @@ function setActiveIcon(name: string) {
|
||||
.icons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
|
||||
/* padding: 32px 32px 96px; */
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import CopyCodeButton from './CopyCodeButton.vue';
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||
import {useData, useRouter} from 'vitepress';
|
||||
import { computed } from 'vue';
|
||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||
import { diamond } from '../../../data/iconNodes'
|
||||
|
||||
const props = defineProps<{
|
||||
icon: IconEntity
|
||||
@@ -20,13 +22,21 @@ const tags = computed(() => {
|
||||
if (!props.icon || !props?.icon?.tags) return []
|
||||
return props.icon.tags.join(' • ')
|
||||
})
|
||||
|
||||
const DiamondIcon = createLucideIcon('Diamond', diamond)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="icon-info">
|
||||
<IconDetailName class="icon-name">
|
||||
{{ icon.name }}
|
||||
</IconDetailName>
|
||||
<div class="icon-name-wrapper">
|
||||
<IconDetailName class="icon-name">
|
||||
{{ icon.name }}
|
||||
</IconDetailName>
|
||||
<div v-if="icon.externalLibrary" class="icon-external-lib">
|
||||
<DiamondIcon fill="currentColor" :size="12"/>
|
||||
{{ icon.externalLibrary }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tags-scroller" v-if="tags.length">
|
||||
<p class="icon-tags horizontal-scroller">
|
||||
{{ tags }}
|
||||
@@ -44,10 +54,10 @@ const tags = computed(() => {
|
||||
|
||||
<div class="group buttons">
|
||||
<VPButton
|
||||
v-if="!page?.relativePath?.startsWith?.(`icons/${icon.name}`)"
|
||||
:href="`/icons/${icon.name}`"
|
||||
v-if="!page?.relativePath?.startsWith?.(icon.externalLibrary ? `icons/${icon.externalLibrary}/${icon.name}`: `icons/${icon.name}`)"
|
||||
:href="icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}`: `/icons/${icon.name}`"
|
||||
text="See in action"
|
||||
@click="go(`/icons/${icon.name}`)"
|
||||
@click="go(icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}`: `/icons/${icon.name}`)"
|
||||
/>
|
||||
<CopySVGButton :name="icon.name" :popoverPosition="popoverPosition"/>
|
||||
<CopyCodeButton :name="icon.name" :popoverPosition="popoverPosition"/>
|
||||
@@ -67,9 +77,27 @@ const tags = computed(() => {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.icon-name {
|
||||
margin-right: -36px;
|
||||
}
|
||||
|
||||
.icon-name-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.icon-external-lib {
|
||||
color: var(--vp-c-brand-dark);
|
||||
padding: 4px 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 28px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-tags {
|
||||
font-size: 16px;
|
||||
color: var(--vp-c-text-2);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useRouter } from 'vitepress';
|
||||
import getSVGIcon from '../../utils/getSVGIcon';
|
||||
import useConfetti from '../../composables/useConfetti';
|
||||
import Tooltip from '../base/Tooltip.vue';
|
||||
import { diamond } from '../../../data/iconNodes'
|
||||
|
||||
const downloadText = 'Download!'
|
||||
const copiedText = 'Copied!'
|
||||
@@ -16,6 +17,7 @@ const props = defineProps<{
|
||||
name: string;
|
||||
iconNode: IconNode;
|
||||
active: boolean;
|
||||
externalLibrary?: string;
|
||||
customizable?: boolean;
|
||||
overlayMode?: boolean
|
||||
hideIcon?: boolean
|
||||
@@ -33,8 +35,9 @@ const icon = computed(() => {
|
||||
return createLucideIcon(props.name, props.iconNode)
|
||||
})
|
||||
|
||||
async function navigateToIcon(event) {
|
||||
const href = computed(() => props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`)
|
||||
|
||||
async function navigateToIcon(event) {
|
||||
if (event.shiftKey) {
|
||||
event.preventDefault()
|
||||
const svgString = getSVGIcon(event.target.firstChild, {
|
||||
@@ -50,14 +53,16 @@ async function navigateToIcon(event) {
|
||||
|
||||
if(props.overlayMode && showOverlay.value) {
|
||||
event.preventDefault()
|
||||
window.history.pushState({}, '', `/icons/${props.name}`)
|
||||
emit('setActiveIcon', props.name)
|
||||
}
|
||||
else {
|
||||
|
||||
window.history.pushState({}, '', props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`)
|
||||
emit('setActiveIcon', props.externalLibrary ? `${props.externalLibrary}:${props.name}`: props.name)
|
||||
} else {
|
||||
event.preventDefault()
|
||||
go(`/icons/${props.name}`)
|
||||
go(props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
const DiamondIcon = createLucideIcon('Diamond', diamond)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -67,7 +72,7 @@ async function navigateToIcon(event) {
|
||||
@click="navigateToIcon"
|
||||
:class="{ active, animate }"
|
||||
:aria-label="name"
|
||||
:href="`/icons/${props.name}`"
|
||||
|
||||
:data-confetti-text="confettiText"
|
||||
ref="ref"
|
||||
>
|
||||
@@ -81,6 +86,13 @@ async function navigateToIcon(event) {
|
||||
}"
|
||||
/>
|
||||
</KeepAlive>
|
||||
<div
|
||||
v-if="externalLibrary"
|
||||
class="floating-diamond"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<DiamondIcon fill="currentColor" :size="8"/>
|
||||
</div>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</template>
|
||||
@@ -89,6 +101,7 @@ async function navigateToIcon(event) {
|
||||
|
||||
<style scoped>
|
||||
.icon-button {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border: 1px solid transparent;
|
||||
text-align: center;
|
||||
@@ -105,6 +118,13 @@ async function navigateToIcon(event) {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.floating-diamond {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.confetti-button:before,
|
||||
.confetti-button:after {
|
||||
z-index: 100;
|
||||
|
||||
@@ -1,56 +1,53 @@
|
||||
<script lang="ts">
|
||||
import { IconEntity } from '../../types';
|
||||
|
||||
type CategoryNameRow = {
|
||||
type: 'category';
|
||||
title: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type CategoryIconsRow = {
|
||||
type: 'icons';
|
||||
icons: IconEntity[];
|
||||
};
|
||||
|
||||
export type CategoryRow = CategoryNameRow | CategoryIconsRow;
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Category } from '../../types';
|
||||
import IconGrid from './IconGrid.vue'
|
||||
import { vIntersectionObserver } from '@vueuse/components'
|
||||
|
||||
defineProps<{
|
||||
activeIconName: string
|
||||
category: Category
|
||||
activeIconName: string | null
|
||||
categoryRow: CategoryRow
|
||||
}>()
|
||||
|
||||
|
||||
const emit = defineEmits(['setActiveIcon'])
|
||||
|
||||
const showIcons = ref(false)
|
||||
|
||||
// Added intersection observer to improve performance
|
||||
const onIntersectionObserver: IntersectionObserverCallback = ([{ isIntersecting }]) => {
|
||||
showIcons.value = isIntersecting
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="category"
|
||||
:key="category.name"
|
||||
:id="category.name"
|
||||
v-intersection-observer="onIntersectionObserver"
|
||||
<h2
|
||||
v-if="categoryRow.type === 'category'"
|
||||
class="title"
|
||||
>
|
||||
<h2 class="title" >
|
||||
<a class="header-anchor" :href="`#${category.name}`" :aria-label="`Permalink to "${category.title}"`">​</a>
|
||||
{{ category.title }}
|
||||
</h2>
|
||||
<IconGrid
|
||||
:activeIcon="activeIconName"
|
||||
:icons="category.icons"
|
||||
@setActiveIcon="$event => $emit('setActiveIcon', $event)"
|
||||
overlayMode
|
||||
:hideIcons="!showIcons"
|
||||
/>
|
||||
</section>
|
||||
<a class="header-anchor" :href="`#${categoryRow.name}`" :aria-label="`Permalink to "${categoryRow.title}"`">​</a>
|
||||
{{ categoryRow.title }}
|
||||
</h2>
|
||||
<IconGrid
|
||||
v-else-if="categoryRow.type === 'icons'"
|
||||
:activeIcon="activeIconName"
|
||||
:icons="categoryRow.icons"
|
||||
@setActiveIcon="$event => $emit('setActiveIcon', $event)"
|
||||
overlayMode
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.title {
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 19px;
|
||||
font-weight: 500;
|
||||
padding-top: 86px;
|
||||
/* scroll-padding-top: 240px; */
|
||||
}
|
||||
|
||||
.category {
|
||||
margin-bottom: calc(-86px + 32px);
|
||||
padding: 24px 0 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, defineAsyncComponent } from 'vue';
|
||||
import { ref, computed, defineAsyncComponent, onMounted, watch, watchEffect } from 'vue';
|
||||
import type { IconEntity, Category } from '../../types';
|
||||
import useSearch from '../../composables/useSearch';
|
||||
import InputSearch from '../base/InputSearch.vue';
|
||||
@@ -8,6 +8,14 @@ import StickyBar from './StickyBar.vue';
|
||||
import IconsCategory from './IconsCategory.vue';
|
||||
import useFetchTags from '../../composables/useFetchTags';
|
||||
import useFetchCategories from '../../composables/useFetchCategories';
|
||||
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
||||
import chunkArray from '../../utils/chunkArray';
|
||||
import { CategoryRow } from './IconsCategory.vue';
|
||||
import useScrollToCategory from '../../composables/useScrollToCategory';
|
||||
import CarbonAdOverlay from './CarbonAdOverlay.vue';
|
||||
|
||||
const ICON_SIZE = 56;
|
||||
const ICON_GRID_GAP = 8;
|
||||
|
||||
const props = defineProps<{
|
||||
icons: IconEntity[];
|
||||
@@ -17,7 +25,6 @@ const props = defineProps<{
|
||||
|
||||
const activeIconName = ref(null);
|
||||
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
||||
|
||||
const isSearching = computed(() => !!searchQuery.value);
|
||||
|
||||
function setActiveIconName(name: string) {
|
||||
@@ -27,6 +34,13 @@ function setActiveIconName(name: string) {
|
||||
const { execute: fetchTags, data: tags } = useFetchTags();
|
||||
const { execute: fetchCategories, data: categoriesMap } = useFetchCategories();
|
||||
|
||||
const overviewEl = ref<HTMLElement | null>(null);
|
||||
const { width: containerWidth } = useElementSize(overviewEl)
|
||||
|
||||
const columnSize = computed(() => {
|
||||
return Math.floor((containerWidth.value) / ((ICON_SIZE + ICON_GRID_GAP)));
|
||||
});
|
||||
|
||||
const mappedIcons = computed(() => {
|
||||
if (tags.value == null) {
|
||||
return props.icons;
|
||||
@@ -43,9 +57,10 @@ const mappedIcons = computed(() => {
|
||||
});
|
||||
});
|
||||
|
||||
const searchResults = useSearch(searchQuery, mappedIcons, [
|
||||
{ name: 'name', weight: 2 },
|
||||
{ name: 'tags', weight: 1 },
|
||||
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
|
||||
{ name: 'name', weight: 3 },
|
||||
{ name: 'aliases', weight: 3 },
|
||||
{ name: 'tags', weight: 2 },
|
||||
]);
|
||||
|
||||
const categories = computed(() => {
|
||||
@@ -54,7 +69,7 @@ const categories = computed(() => {
|
||||
return props.categories
|
||||
.map(({ name, title }) => {
|
||||
const categoryIcons = props.icons.filter((icon) => {
|
||||
const iconCategories = props.iconCategories[icon.name];
|
||||
const iconCategories = icon?.externalLibrary ? icon.categories : props.iconCategories[icon.name]
|
||||
|
||||
return iconCategories?.includes(name);
|
||||
});
|
||||
@@ -71,9 +86,43 @@ const categories = computed(() => {
|
||||
icons: searchedCategoryIcons,
|
||||
};
|
||||
})
|
||||
.filter(({ icons }) => icons.length);
|
||||
});
|
||||
|
||||
const categoriesList = computed(() => {
|
||||
return categories.value
|
||||
.filter(({ icons }) => icons.length)
|
||||
.reduce<CategoryRow[]>((acc, category) => {
|
||||
acc.push({ type: 'category', title: category.title, name: category.name });
|
||||
|
||||
const categoryIcons = chunkArray(category.icons, columnSize.value);
|
||||
categoryIcons.forEach((icons) => {
|
||||
acc.push({ type: 'icons', icons });
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
});
|
||||
|
||||
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
|
||||
categoriesList,
|
||||
{
|
||||
itemHeight: ICON_SIZE + ICON_GRID_GAP,
|
||||
overscan: 10
|
||||
},
|
||||
)
|
||||
|
||||
useScrollToCategory({
|
||||
categories,
|
||||
categoriesList,
|
||||
scrollTo,
|
||||
searchQueryDebounced,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
containerProps.ref.value = document.documentElement;
|
||||
useEventListener(window, 'scroll', containerProps.onScroll)
|
||||
})
|
||||
|
||||
function onFocusSearchInput() {
|
||||
if (tags.value == null) {
|
||||
fetchTags();
|
||||
@@ -84,33 +133,54 @@ function onFocusSearchInput() {
|
||||
}
|
||||
|
||||
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
||||
|
||||
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
|
||||
|
||||
function handleCloseDrawer() {
|
||||
setActiveIconName('');
|
||||
|
||||
window.history.pushState({}, '', '/icons/categories');
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
|
||||
console.log(props.icons.find((icon) => icon.name === 'burger'));
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StickyBar class="search-bar category-search">
|
||||
<InputSearch
|
||||
:placeholder="`Search ${icons.length} icons ...`"
|
||||
v-model="searchQuery"
|
||||
class="input-wrapper"
|
||||
ref="searchInput"
|
||||
@focus="onFocusSearchInput"
|
||||
<div ref="overviewEl" class="overview-container">
|
||||
<StickyBar class="category-search">
|
||||
<InputSearch
|
||||
:placeholder="`Search ${icons.length} icons ...`"
|
||||
v-model="searchQuery"
|
||||
class="input-wrapper"
|
||||
ref="searchInput"
|
||||
@focus="onFocusSearchInput"
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="categories.length === 0"
|
||||
:searchQuery="searchQuery"
|
||||
@clear="searchQuery = ''"
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults v-if="categories.length === 0" :searchQuery="searchQuery" @clear="searchQuery = ''" />
|
||||
<IconsCategory
|
||||
v-for="category in categories"
|
||||
:key="category.name"
|
||||
:category="category"
|
||||
:activeIconName="activeIconName"
|
||||
@setActiveIcon="setActiveIconName"
|
||||
/>
|
||||
<div v-bind="wrapperProps">
|
||||
<IconsCategory
|
||||
v-for="{ index, data } in list"
|
||||
:categoryRow="data"
|
||||
:activeIconName="activeIconName"
|
||||
@setActiveIcon="setActiveIconName"
|
||||
:key="index"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<IconDetailOverlay
|
||||
v-if="activeIconName != null"
|
||||
:iconName="activeIconName"
|
||||
@close="setActiveIconName('')"
|
||||
@close="handleCloseDrawer"
|
||||
/>
|
||||
|
||||
<CarbonAdOverlay :drawerOpen="!!activeIconName" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -119,6 +189,21 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
|
||||
}
|
||||
|
||||
.search-bar.category-search {
|
||||
margin-bottom: -54px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 8px;
|
||||
font-size: 19px;
|
||||
font-weight: 500;
|
||||
padding: 24px 0 8px;
|
||||
}
|
||||
|
||||
.icons {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.overview-container {
|
||||
padding-bottom: 288px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, defineAsyncComponent } from 'vue';
|
||||
import { ref, computed, defineAsyncComponent, onMounted, watch } from 'vue';
|
||||
import type { IconEntity } from '../../types';
|
||||
import { useMediaQuery, useOffsetPagination } from '@vueuse/core';
|
||||
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
||||
import IconGrid from './IconGrid.vue';
|
||||
import InputSearch from '../base/InputSearch.vue';
|
||||
import useSearch from '../../composables/useSearch';
|
||||
import EndOfPage from '../base/EndOfPage.vue';
|
||||
import useSearchInput from '../../composables/useSearchInput';
|
||||
import StickyBar from './StickyBar.vue';
|
||||
import useFetchTags from '../../composables/useFetchTags';
|
||||
import useFetchCategories from '../../composables/useFetchCategories';
|
||||
import chunkArray from '../../utils/chunkArray';
|
||||
import CarbonAdOverlay from './CarbonAdOverlay.vue';
|
||||
|
||||
const ICON_SIZE = 56;
|
||||
const ICON_GRID_GAP = 8;
|
||||
|
||||
const props = defineProps<{
|
||||
icons: IconEntity[];
|
||||
@@ -17,32 +21,16 @@ const props = defineProps<{
|
||||
|
||||
const activeIconName = ref(null);
|
||||
|
||||
const isExtraLargeScreen = useMediaQuery('(min-width: 1440px)');
|
||||
const isLargeScreen = useMediaQuery('(min-width: 1280px)');
|
||||
const isMediumScreen = useMediaQuery('(min-width: 960px)');
|
||||
const isSmallScreen = useMediaQuery('(min-width: 640px)');
|
||||
|
||||
const pageSize = computed(() => {
|
||||
if (isExtraLargeScreen.value) {
|
||||
return 16 * 20;
|
||||
}
|
||||
if (isLargeScreen.value) {
|
||||
return 16 * 12;
|
||||
}
|
||||
if (isMediumScreen.value) {
|
||||
return 13 * 12;
|
||||
}
|
||||
|
||||
if (isSmallScreen.value) {
|
||||
return 10 * 10;
|
||||
}
|
||||
|
||||
return 10 * 5;
|
||||
});
|
||||
|
||||
const { execute: fetchTags, data: tags } = useFetchTags();
|
||||
const { execute: fetchCategories, data: categories } = useFetchCategories();
|
||||
|
||||
const overviewEl = ref<HTMLElement | null>(null);
|
||||
const { width: containerWidth } = useElementSize(overviewEl)
|
||||
|
||||
const columnSize = computed(() => {
|
||||
return Math.floor((containerWidth.value) / ((ICON_SIZE + ICON_GRID_GAP)));
|
||||
});
|
||||
|
||||
const mappedIcons = computed(() => {
|
||||
if (tags.value == null) {
|
||||
return props.icons;
|
||||
@@ -63,26 +51,33 @@ const mappedIcons = computed(() => {
|
||||
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
||||
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
|
||||
{ name: 'name', weight: 3 },
|
||||
{ name: 'aliases', weight: 3 },
|
||||
{ name: 'tags', weight: 2 },
|
||||
{ name: 'categories', weight: 1 },
|
||||
]);
|
||||
|
||||
const { next, currentPage } = useOffsetPagination({ pageSize });
|
||||
|
||||
const paginatedIcons = computed(() => {
|
||||
const end = pageSize.value * currentPage.value;
|
||||
|
||||
return searchResults.value.slice(0, end);
|
||||
const chunkedIcons = computed(() => {
|
||||
return chunkArray(searchResults.value, columnSize.value);
|
||||
});
|
||||
|
||||
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
|
||||
chunkedIcons,
|
||||
{
|
||||
itemHeight: ICON_SIZE + ICON_GRID_GAP,
|
||||
overscan: 10
|
||||
},
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
containerProps.ref.value = document.documentElement;
|
||||
useEventListener(window, 'scroll', containerProps.onScroll)
|
||||
})
|
||||
|
||||
|
||||
function setActiveIconName(name: string) {
|
||||
activeIconName.value = name;
|
||||
}
|
||||
|
||||
watch(searchQueryDebounced, (searchString) => {
|
||||
currentPage.value = 1;
|
||||
});
|
||||
|
||||
function onFocusSearchInput() {
|
||||
if (tags.value == null) {
|
||||
fetchTags();
|
||||
@@ -95,43 +90,57 @@ function onFocusSearchInput() {
|
||||
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
||||
|
||||
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
|
||||
|
||||
watch(searchQueryDebounced, () => {
|
||||
scrollTo(0)
|
||||
})
|
||||
|
||||
function handleCloseDrawer() {
|
||||
setActiveIconName('');
|
||||
|
||||
window.history.pushState({}, '', '/icons/');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StickyBar>
|
||||
<InputSearch
|
||||
:placeholder="`Search ${icons.length} icons ...`"
|
||||
v-model="searchQuery"
|
||||
ref="searchInput"
|
||||
class="input-wrapper"
|
||||
@focus="onFocusSearchInput"
|
||||
<div ref="overviewEl" class="overview-container">
|
||||
<StickyBar>
|
||||
<InputSearch
|
||||
:placeholder="`Search ${icons.length} icons ...`"
|
||||
v-model="searchQuery"
|
||||
ref="searchInput"
|
||||
class="input-wrapper"
|
||||
@focus="onFocusSearchInput"
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="list.length === 0"
|
||||
:searchQuery="searchQuery"
|
||||
@clear="searchQuery = ''"
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="paginatedIcons.length === 0"
|
||||
:searchQuery="searchQuery"
|
||||
@clear="searchQuery = ''"
|
||||
/>
|
||||
<IconGrid
|
||||
overlayMode
|
||||
:activeIcon="activeIconName"
|
||||
:icons="paginatedIcons"
|
||||
@setActiveIcon="setActiveIconName"
|
||||
/>
|
||||
<EndOfPage @end-of-page="next" class="bottom-page" />
|
||||
<div v-bind="wrapperProps" class="icon">
|
||||
<IconGrid
|
||||
v-for="{ index, data: icons } in list"
|
||||
:key="index"
|
||||
overlayMode
|
||||
:icons="icons"
|
||||
:activeIcon="activeIconName"
|
||||
@setActiveIcon="setActiveIconName"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IconDetailOverlay
|
||||
v-if="activeIconName != null"
|
||||
:iconName="activeIconName"
|
||||
@close="setActiveIconName('')"
|
||||
@close="handleCloseDrawer"
|
||||
/>
|
||||
|
||||
<CarbonAdOverlay :drawerOpen="!!activeIconName" />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.icons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@@ -142,7 +151,7 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bottom-page {
|
||||
height: 288px;
|
||||
.overview-container {
|
||||
padding-bottom: 288px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import {ref} from 'vue'
|
||||
import {bird} from '../../../data/iconNodes'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { bird, squirrel, rabbit } from '../../../data/iconNodes'
|
||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
||||
import {useEventListener} from '@vueuse/core'
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
|
||||
import { IconNode } from '../../types'
|
||||
|
||||
defineProps<{
|
||||
searchQuery: string
|
||||
@@ -11,32 +12,48 @@ defineProps<{
|
||||
|
||||
defineEmits(['clear'])
|
||||
|
||||
const birdIcon = ref<HTMLElement>()
|
||||
const Bird = createLucideIcon('bird', bird)
|
||||
const animalIcon = ref<HTMLElement>()
|
||||
const randomAnimal = computed<IconNode>(() => {
|
||||
return Math.random() > 0.5 ? squirrel : Math.random() > 0.5 ? rabbit : bird
|
||||
})
|
||||
const animalComponent = computed(() => createLucideIcon('animal', randomAnimal.value))
|
||||
const flip = ref(false)
|
||||
|
||||
useEventListener(document, 'mousemove', (mouseEvent) => {
|
||||
const {width, height, x, y} = birdIcon.value.getBoundingClientRect()
|
||||
onMounted(() => {
|
||||
useEventListener(document, 'mousemove', (mouseEvent) => {
|
||||
const {width, height, x, y} = animalIcon.value.getBoundingClientRect()
|
||||
|
||||
const centerX = (width / 2) + x
|
||||
const centerX = (width / 2) + x
|
||||
|
||||
flip.value = mouseEvent.x < centerX
|
||||
flip.value = mouseEvent.x < centerX
|
||||
})
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="no-results">
|
||||
<Bird class="bird-icon" ref="birdIcon" :class="{ flip }" :strokeWidth="1"/>
|
||||
<component
|
||||
:is="animalComponent"
|
||||
class="animal-icon"
|
||||
ref="animalIcon"
|
||||
:class="{ flip }"
|
||||
:strokeWidth="1"
|
||||
/>
|
||||
<h2 class="no-results-text">
|
||||
No icons found for '{{ searchQuery }}'
|
||||
</h2>
|
||||
<VPButton text="Clear your search and try again" theme="alt" @click="$emit('clear')"/>
|
||||
<VPButton
|
||||
text="Clear your search and try again"
|
||||
theme="alt"
|
||||
@click="$emit('clear')"
|
||||
/>
|
||||
<span class="text-divider">or</span>
|
||||
<VPButton text="Search on Github issues"
|
||||
theme="alt"
|
||||
:href="`https://github.com/lucide-icons/lucide/issues?q=is%3Aopen+${searchQuery}`"
|
||||
target="_blank"
|
||||
<VPButton
|
||||
text="Search on Github issues"
|
||||
theme="alt"
|
||||
:href="`https://github.com/lucide-icons/lucide/issues?q=is%3Aopen+${searchQuery}`"
|
||||
target="_blank"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -48,7 +65,7 @@ useEventListener(document, 'mousemove', (mouseEvent) => {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bird-icon {
|
||||
.animal-icon {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
color: var(--vp-c-neutral);
|
||||
@@ -56,12 +73,12 @@ useEventListener(document, 'mousemove', (mouseEvent) => {
|
||||
margin-top: 72px;
|
||||
}
|
||||
|
||||
.bird-icon.flip {
|
||||
.animal-icon.flip {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.bird-icon {
|
||||
.animal-icon {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user