mirror of
https://github.com/lucide-icons/lucide.git
synced 2025-12-17 20:07:41 +01:00
Compare commits
281 Commits
lucide-vue
...
0.424.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
16
.cspell/custom-words.txt
Normal file
16
.cspell/custom-words.txt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Custom words
|
||||||
|
fullscreen
|
||||||
|
gamepad
|
||||||
|
gantt
|
||||||
|
kanban
|
||||||
|
pilcrow
|
||||||
|
squircle
|
||||||
|
strikethrough
|
||||||
|
touchpad
|
||||||
|
ungroup
|
||||||
|
pilcrow
|
||||||
|
|
||||||
|
# Brands
|
||||||
|
codepen
|
||||||
|
codesandbox
|
||||||
|
dribbble
|
||||||
@@ -6,6 +6,5 @@ tests
|
|||||||
node_modules
|
node_modules
|
||||||
.eslintrc.js
|
.eslintrc.js
|
||||||
docs/images
|
docs/images
|
||||||
docs/guide/basics/examples
|
docs/**/examples/
|
||||||
docs/guide/advanced/examples
|
|
||||||
packages/lucide-react/dynamicIconImports.js
|
packages/lucide-react/dynamicIconImports.js
|
||||||
|
|||||||
17
.eslintrc.js
17
.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 = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
@@ -15,7 +15,9 @@ module.exports = {
|
|||||||
'no-use-before-define': 'off',
|
'no-use-before-define': 'off',
|
||||||
'import/no-extraneous-dependencies': [
|
'import/no-extraneous-dependencies': [
|
||||||
'error',
|
'error',
|
||||||
{ devDependencies: ['**/*.test.js', '**/*.spec.js', './scripts/**'] },
|
{
|
||||||
|
devDependencies: ['**/*.test.js', '**/*.spec.js', '**/scripts/**'],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'import/extensions': [
|
'import/extensions': [
|
||||||
'error',
|
'error',
|
||||||
@@ -43,11 +45,14 @@ module.exports = {
|
|||||||
'@html-eslint/no-inline-styles': 'error',
|
'@html-eslint/no-inline-styles': 'error',
|
||||||
'@html-eslint/require-attrs': [
|
'@html-eslint/require-attrs': [
|
||||||
'error',
|
'error',
|
||||||
...Object.entries(DEFAULT_ATTRS)
|
...Object.entries(DEFAULT_ATTRS).map(([attr, value]) => ({
|
||||||
.map(([attr, value]) => ({ tag: 'svg', attr, value: String(value) }))
|
tag: 'svg',
|
||||||
|
attr,
|
||||||
|
value: String(value),
|
||||||
|
})),
|
||||||
],
|
],
|
||||||
'@html-eslint/indent': ['error', 2],
|
'@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': [
|
'@html-eslint/no-extra-spacing-attrs': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
@@ -64,7 +69,7 @@ module.exports = {
|
|||||||
'@html-eslint/element-newline': 'error',
|
'@html-eslint/element-newline': 'error',
|
||||||
'@html-eslint/no-trailing-spaces': 'error',
|
'@html-eslint/no-trailing-spaces': 'error',
|
||||||
'@html-eslint/quotes': '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
|
placeholder: e.g. 0.289.1
|
||||||
validations:
|
validations:
|
||||||
required: true
|
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
|
- type: checkboxes
|
||||||
id: browsers
|
id: browsers
|
||||||
attributes:
|
attributes:
|
||||||
@@ -59,6 +69,9 @@ body:
|
|||||||
- label: Windows
|
- label: Windows
|
||||||
- label: Linux
|
- label: Linux
|
||||||
- label: macOS
|
- label: macOS
|
||||||
|
- label: ChromeOS
|
||||||
|
- label: iOS
|
||||||
|
- label: Android
|
||||||
- label: Other/not relevant
|
- label: Other/not relevant
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ body:
|
|||||||
- label: Windows
|
- label: Windows
|
||||||
- label: Linux
|
- label: Linux
|
||||||
- label: macOS
|
- label: macOS
|
||||||
|
- label: ChromeOS
|
||||||
|
- label: iOS
|
||||||
|
- label: Android
|
||||||
- label: Other/not relevant
|
- label: Other/not relevant
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
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"
|
name: 'Build and Test'
|
||||||
description: "Builds and test a package"
|
description: 'Builds and test a package'
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
name:
|
name:
|
||||||
@@ -7,7 +7,7 @@ inputs:
|
|||||||
required: true
|
required: true
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: 'composite'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@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"
|
name: 'Check icons'
|
||||||
description: "Cross-checks icon and category references in JSON descriptors"
|
description: 'Cross-checks icon and category references in JSON descriptors'
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
name:
|
name:
|
||||||
@@ -7,7 +7,7 @@ inputs:
|
|||||||
required: true
|
required: true
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: 'composite'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
|
|||||||
30
.github/labeler.yml
vendored
30
.github/labeler.yml
vendored
@@ -1,92 +1,92 @@
|
|||||||
# For changed dependencies
|
# For changed dependencies
|
||||||
📦 dependencies:
|
📦 dependencies:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- pnpm-lock.yaml
|
- pnpm-lock.yaml
|
||||||
|
|
||||||
# For changes in documentation
|
# For changes in documentation
|
||||||
📖 documentation:
|
📖 documentation:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- docs/*.md
|
- docs/*.md
|
||||||
- docs/**/*.md
|
- docs/**/*.md
|
||||||
|
|
||||||
# For changes in the site, but not markdown files
|
# For changes in the site, but not markdown files
|
||||||
🌍 site:
|
🌍 site:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
|
|
||||||
# For changes in the metadata
|
# For changes in the metadata
|
||||||
🫧 metadata:
|
🫧 metadata:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- 'icons/*.json'
|
- 'icons/*.json'
|
||||||
- categories/*
|
- categories/*
|
||||||
|
|
||||||
# For changes or added icons
|
# For changes or added icons
|
||||||
🎨 icon:
|
🎨 icon:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- 'icons/*.svg'
|
- 'icons/*.svg'
|
||||||
|
|
||||||
# For changes in the lucide package
|
# For changes in the lucide package
|
||||||
🧳 lucide package:
|
🧳 lucide package:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- 'packages/lucide/*'
|
- 'packages/lucide/*'
|
||||||
|
|
||||||
# For changes in the lucide React package
|
# For changes in the lucide React package
|
||||||
⚛️ react package:
|
⚛️ react package:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- 'packages/lucide-react/*'
|
- 'packages/lucide-react/*'
|
||||||
|
|
||||||
# For changes in the lucide React Native package
|
# For changes in the lucide React Native package
|
||||||
⚛️ react native package:
|
⚛️ react native package:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- 'packages/lucide-react-native/*'
|
- 'packages/lucide-react-native/*'
|
||||||
|
|
||||||
# For changes in the lucide vue packages
|
# For changes in the lucide vue packages
|
||||||
💎 vue package:
|
💎 vue package:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- 'packages/lucide-vue/*'
|
- 'packages/lucide-vue/*'
|
||||||
- 'packages/lucide-vue-next/*'
|
- 'packages/lucide-vue-next/*'
|
||||||
|
|
||||||
# For changes in the lucide angular package
|
# For changes in the lucide angular package
|
||||||
🅰️ angular package:
|
🅰️ angular package:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- 'packages/lucide-angular/*'
|
- 'packages/lucide-angular/*'
|
||||||
|
|
||||||
# For changes in the lucide preact package
|
# For changes in the lucide preact package
|
||||||
⚛️ preact package:
|
⚛️ preact package:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- 'packages/lucide-preact/*'
|
- 'packages/lucide-preact/*'
|
||||||
|
|
||||||
# For changes in the lucide svelte package
|
# For changes in the lucide svelte package
|
||||||
🧣 svelte package:
|
🧣 svelte package:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- 'packages/lucide-svelte/*'
|
- 'packages/lucide-svelte/*'
|
||||||
|
|
||||||
# For changes in the lucide solid package
|
# For changes in the lucide solid package
|
||||||
🪝 solid package:
|
🪝 solid package:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- 'packages/lucide-solid/*'
|
- 'packages/lucide-solid/*'
|
||||||
|
|
||||||
# For changes in the lucide static package
|
# For changes in the lucide static package
|
||||||
🪨 static package:
|
🪨 static package:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- 'packages/lucide-static/*'
|
- 'packages/lucide-static/*'
|
||||||
|
|
||||||
# For changes in the lucide flutter package
|
# For changes in the lucide flutter package
|
||||||
🏹 flutter package:
|
🏹 flutter package:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- 'packages/lucide-flutter/*'
|
- '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 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 look consistent with the icon set in size, optical volume and density.
|
||||||
- [ ] I've made sure that the icons are visually centered.
|
- [ ] 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! -->
|
## Before Submitting <!-- For every PR! -->
|
||||||
<!-- All of these requirements must be fulfilled. -->
|
<!-- All of these requirements must be fulfilled. -->
|
||||||
|
|||||||
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
|
name: Close stale issues and PR
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "45 1 * * *"
|
- cron: '45 1 * * *'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@v9
|
||||||
with:
|
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-message: This PR was closed because it has been stalled for 5 days with no activity.
|
||||||
close-pr-label: 🧶 stale
|
close-pr-label: 🧶 stale
|
||||||
days-before-stale: 30
|
days-before-stale: 30
|
||||||
|
days-before-issue-stale: -1
|
||||||
days-before-close: -1
|
days-before-close: -1
|
||||||
|
|||||||
4
.github/workflows/labeler.yml
vendored
4
.github/workflows/labeler.yml
vendored
@@ -1,6 +1,6 @@
|
|||||||
name: "Pull Request Labeler"
|
name: 'Pull Request Labeler'
|
||||||
on:
|
on:
|
||||||
- pull_request_target
|
- pull_request_target
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
triage:
|
triage:
|
||||||
|
|||||||
17
.github/workflows/lucide-angular.yml
vendored
17
.github/workflows/lucide-angular.yml
vendored
@@ -8,7 +8,7 @@ on:
|
|||||||
- pnpm-lock.yaml
|
- pnpm-lock.yaml
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lucide-angular:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -26,5 +26,20 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm --filter lucide-angular build
|
run: pnpm --filter lucide-angular build
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
- uses: actions/setup-node@v3.8.1
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm --filter lucide-angular test
|
run: pnpm --filter lucide-angular test
|
||||||
|
|||||||
2
.github/workflows/lucide-font.yml
vendored
2
.github/workflows/lucide-font.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
|||||||
- name: Create font in ./lucide-font
|
- name: Create font in ./lucide-font
|
||||||
run: pnpm build:font
|
run: pnpm build:font
|
||||||
|
|
||||||
- name: "Upload to Artifacts"
|
- name: 'Upload to Artifacts'
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: lucide-font
|
name: lucide-font
|
||||||
|
|||||||
3
.github/workflows/lucide-preact.yml
vendored
3
.github/workflows/lucide-preact.yml
vendored
@@ -24,8 +24,5 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm --filter lucide-preact build
|
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm --filter lucide-preact test
|
run: pnpm --filter lucide-preact test
|
||||||
|
|||||||
3
.github/workflows/lucide-react-native.yml
vendored
3
.github/workflows/lucide-react-native.yml
vendored
@@ -24,8 +24,5 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm --filter lucide-react-native build
|
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm --filter lucide-react-native test
|
run: pnpm --filter lucide-react-native test
|
||||||
|
|||||||
17
.github/workflows/lucide-react.yml
vendored
17
.github/workflows/lucide-react.yml
vendored
@@ -10,7 +10,7 @@ on:
|
|||||||
- pnpm-lock.yaml
|
- pnpm-lock.yaml
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lucide-react:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -28,5 +28,20 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm --filter lucide-react build
|
run: pnpm --filter lucide-react build
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
- uses: actions/setup-node@v3.8.1
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm --filter lucide-react test
|
run: pnpm --filter lucide-react test
|
||||||
|
|||||||
17
.github/workflows/lucide-solid.yml
vendored
17
.github/workflows/lucide-solid.yml
vendored
@@ -9,7 +9,7 @@ on:
|
|||||||
- pnpm-lock.yaml
|
- pnpm-lock.yaml
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lucide-solid:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -27,5 +27,20 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm --filter lucide-solid build
|
run: pnpm --filter lucide-solid build
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
- uses: actions/setup-node@v3.8.1
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm --filter lucide-solid test
|
run: pnpm --filter lucide-solid test
|
||||||
|
|||||||
17
.github/workflows/lucide-svelte.yml
vendored
17
.github/workflows/lucide-svelte.yml
vendored
@@ -9,7 +9,7 @@ on:
|
|||||||
- pnpm-lock.yaml
|
- pnpm-lock.yaml
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lucide-svelte:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -27,5 +27,20 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm --filter lucide-svelte build
|
run: pnpm --filter lucide-svelte build
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
- uses: actions/setup-node@v3.8.1
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm --filter lucide-svelte test
|
run: pnpm --filter lucide-svelte test
|
||||||
|
|||||||
17
.github/workflows/lucide-vue-next.yml
vendored
17
.github/workflows/lucide-vue-next.yml
vendored
@@ -9,7 +9,7 @@ on:
|
|||||||
- pnpm-lock.yaml
|
- pnpm-lock.yaml
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lucide-vue-next:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -27,5 +27,20 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm --filter lucide-vue-next build
|
run: pnpm --filter lucide-vue-next build
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
- uses: actions/setup-node@v3.8.1
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm --filter lucide-vue-next test
|
run: pnpm --filter lucide-vue-next test
|
||||||
|
|||||||
17
.github/workflows/lucide-vue.yml
vendored
17
.github/workflows/lucide-vue.yml
vendored
@@ -9,7 +9,7 @@ on:
|
|||||||
- pnpm-lock.yaml
|
- pnpm-lock.yaml
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lucide-vue:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -27,5 +27,20 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm --filter lucide-vue build
|
run: pnpm --filter lucide-vue build
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
- uses: actions/setup-node@v3.8.1
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm --filter lucide-vue test
|
run: pnpm --filter lucide-vue test
|
||||||
|
|||||||
17
.github/workflows/lucide.yml
vendored
17
.github/workflows/lucide.yml
vendored
@@ -9,7 +9,7 @@ on:
|
|||||||
- pnpm-lock.yaml
|
- pnpm-lock.yaml
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lucide:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -27,5 +27,20 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm --filter lucide build
|
run: pnpm --filter lucide build
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
- uses: actions/setup-node@v3.8.1
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm --filter lucide 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:
|
on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
paths:
|
paths:
|
||||||
- 'icons/*.svg'
|
- 'icons/*'
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- fix-icon-preview
|
- 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
|
# 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
|
# 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:
|
generate-changed-icons-comment:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -94,6 +104,10 @@ jobs:
|
|||||||
comment-author: 'github-actions[bot]'
|
comment-author: 'github-actions[bot]'
|
||||||
body-includes: Added or changed icons
|
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
|
- name: Generate comment markup
|
||||||
run: node ./scripts/generateChangedIconsCommentMarkup.mjs >> comment-markup.md
|
run: node ./scripts/generateChangedIconsCommentMarkup.mjs >> comment-markup.md
|
||||||
id: comment-markup
|
id: comment-markup
|
||||||
|
|||||||
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -41,7 +41,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
package: [
|
package:
|
||||||
|
[
|
||||||
'lucide',
|
'lucide',
|
||||||
'lucide-react',
|
'lucide-react',
|
||||||
'lucide-react-native',
|
'lucide-react-native',
|
||||||
@@ -136,7 +137,7 @@ jobs:
|
|||||||
- name: Create font in ./lucide-font
|
- name: Create font in ./lucide-font
|
||||||
run: pnpm build:font
|
run: pnpm build:font
|
||||||
|
|
||||||
- name: "Upload to Artifacts"
|
- name: 'Upload to Artifacts'
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: lucide-font
|
name: lucide-font
|
||||||
@@ -145,10 +146,7 @@ jobs:
|
|||||||
post-release:
|
post-release:
|
||||||
if: github.repository == 'lucide-icons/lucide'
|
if: github.repository == 'lucide-icons/lucide'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [
|
needs: [pre-release, lucide-font]
|
||||||
pre-release,
|
|
||||||
lucide-font,
|
|
||||||
]
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -34,6 +34,7 @@ docs/.vitepress/data/iconNodes
|
|||||||
docs/.vitepress/data/iconMetaData.ts
|
docs/.vitepress/data/iconMetaData.ts
|
||||||
docs/.vitepress/data/releaseMetaData.json
|
docs/.vitepress/data/releaseMetaData.json
|
||||||
docs/.vitepress/data/releaseMetaData
|
docs/.vitepress/data/releaseMetaData
|
||||||
|
docs/.vitepress/data/categoriesData.json
|
||||||
docs/.vitepress/data/iconDetails
|
docs/.vitepress/data/iconDetails
|
||||||
docs/.vitepress/data/relatedIcons.json
|
docs/.vitepress/data/relatedIcons.json
|
||||||
docs/.vercel
|
docs/.vercel
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
|
|
||||||
|
# docs examples
|
||||||
|
docs/**/examples/
|
||||||
|
docs/.vitepress/.temp
|
||||||
|
docs/.vitepress/cache
|
||||||
|
docs/.vitepress/data
|
||||||
|
docs/.nitro
|
||||||
|
|
||||||
# lucide-angular
|
# lucide-angular
|
||||||
packages/lucide-angular/.angular/cache
|
packages/lucide-angular/.angular/cache
|
||||||
|
|
||||||
|
|||||||
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@@ -1,13 +1,6 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": ["devs", "preact", "Preact"],
|
||||||
"devs",
|
|
||||||
"preact",
|
|
||||||
"Preact"
|
|
||||||
],
|
|
||||||
"eslint.enable": true,
|
"eslint.enable": true,
|
||||||
"eslint.validate": [
|
"eslint.validate": ["javascript", "svg"],
|
||||||
"javascript",
|
|
||||||
"svg"
|
|
||||||
],
|
|
||||||
"svg.preview.background": "transparent"
|
"svg.preview.background": "transparent"
|
||||||
}
|
}
|
||||||
|
|||||||
2
.vscode/svg.code-snippets
vendored
2
.vscode/svg.code-snippets
vendored
@@ -49,7 +49,7 @@
|
|||||||
"circle",
|
"circle",
|
||||||
"<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": {
|
"Ellipse": {
|
||||||
"scope": "xml",
|
"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)
|
#### [Figma Guide](https://lucide.dev/docs/figma-guide)
|
||||||
|
|
||||||
|
#### [Affinity Designer Guide](https://lucide.dev/guide/design/affinity-designer-guide)
|
||||||
|
|
||||||
### Submitting Multiple Icons
|
### 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.
|
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.
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -1,4 +1,11 @@
|
|||||||
<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">
|
<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://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.npmjs.com/package/lucide"><img src="https://img.shields.io/npm/v/lucide" alt="npm package"></a>
|
||||||
@@ -274,9 +281,12 @@ Thank you to all the people who contributed to Lucide!
|
|||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
|
|
||||||
<a href="https://vercel.com?utm_source=lucide&utm_campaign=oss">
|
<a href="https://vercel.com?utm_source=lucide&utm_campaign=oss">
|
||||||
<img src="docs/public/vercel.svg" alt="Powered by Vercel" width="200" />
|
<img src="docs/public/vercel.svg" alt="Powered by Vercel" width="200" />
|
||||||
</a>
|
</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>
|
<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 backer 🍺
|
||||||
|
|
||||||
|
<a href="https://www.scipress.io?utm_source=lucide"><img src="docs/public/sponsors/scipress.svg" width="180" alt="Scipress sponsor badge" /></a>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../category.schema.json",
|
"$schema": "../category.schema.json",
|
||||||
"title": "Coding & development",
|
"title": "Coding & development",
|
||||||
"icon": "code-2"
|
"icon": "code-xml"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../category.schema.json",
|
"$schema": "../category.schema.json",
|
||||||
"title": "Home",
|
"title": "Home",
|
||||||
"icon": "home"
|
"icon": "house"
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../category.schema.json",
|
"$schema": "../category.schema.json",
|
||||||
"title": "Multimedia",
|
"title": "Multimedia",
|
||||||
"icon": "play-circle"
|
"icon": "circle-play"
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../category.schema.json",
|
"$schema": "../category.schema.json",
|
||||||
"title": "Notifications",
|
"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) => {
|
export default eventHandler((event) => {
|
||||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
|
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
|
||||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||||
|
|
||||||
return Object.fromEntries(
|
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 data = pathData.at(-1).slice(0, -4);
|
||||||
const [name] = pathData;
|
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 = [];
|
const children = [];
|
||||||
|
|
||||||
@@ -30,20 +33,29 @@ export default eventHandler((event) => {
|
|||||||
|
|
||||||
const LucideIcon = createLucideIcon(backdropName, iconNode);
|
const LucideIcon = createLucideIcon(backdropName, iconNode);
|
||||||
const svg = renderToStaticMarkup(createElement(LucideIcon));
|
const svg = renderToStaticMarkup(createElement(LucideIcon));
|
||||||
const backdropString = svg.replace(/<svg[^>]*>|<\/svg>/g, '');
|
const backdropString = svg.replaceAll('\n', '').replace(/<svg[^>]*>|<\/svg>/g, '');
|
||||||
|
|
||||||
children.push(
|
children.push(
|
||||||
createElement(Backdrop, {
|
createElement(Backdrop, {
|
||||||
backdropString,
|
backdropString,
|
||||||
src,
|
src,
|
||||||
color: name in iconNodes ? 'red' : '#777',
|
color: name in iconNodes ? 'red' : '#777',
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
if (name in iconNodes) {
|
||||||
|
children.push(
|
||||||
|
createElement(Backdrop, {
|
||||||
|
backdropString: src,
|
||||||
|
src: backdropString,
|
||||||
|
color: 'lime',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const svg = Buffer.from(
|
const svg = Buffer.from(
|
||||||
// We can't use jsx here, is not supported here by nitro.
|
// 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');
|
).toString('utf8');
|
||||||
|
|
||||||
defaultContentType(event, 'image/svg+xml');
|
defaultContentType(event, 'image/svg+xml');
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default eventHandler(async (event) => {
|
|||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
`
|
`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const resvg = new Resvg(svg, { background: '#000' });
|
const resvg = new Resvg(svg, { background: '#000' });
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { eventHandler, setResponseHeader, defaultContentType } from 'h3'
|
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
|
||||||
import { renderToString } from 'react-dom/server'
|
import { renderToString } from 'react-dom/server';
|
||||||
import { createElement } from 'react'
|
import { createElement } from 'react';
|
||||||
import SvgPreview from '../../../lib/SvgPreview/index.tsx';
|
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';
|
import { parseSync } from 'svgson';
|
||||||
|
|
||||||
export default eventHandler((event) => {
|
export default eventHandler((event) => {
|
||||||
const { params } = event.context
|
const { params } = event.context;
|
||||||
|
|
||||||
const [strokeWidth, svgData] = params.data.split('/');
|
const [strokeWidth, svgData] = params.data.split('/');
|
||||||
const data = svgData.slice(0, -4);
|
const data = svgData.slice(0, -4);
|
||||||
@@ -16,8 +16,8 @@ export default eventHandler((event) => {
|
|||||||
const Icon = createLucideIcon(
|
const Icon = createLucideIcon(
|
||||||
'icon',
|
'icon',
|
||||||
parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`).children.map(
|
parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`).children.map(
|
||||||
({ name, attributes }) => [name, attributes]
|
({ name, attributes }) => [name, attributes],
|
||||||
) as IconNode
|
) as IconNode,
|
||||||
);
|
);
|
||||||
|
|
||||||
const svg = Buffer.from(
|
const svg = Buffer.from(
|
||||||
@@ -33,12 +33,12 @@ export default eventHandler((event) => {
|
|||||||
@media screen and (prefers-color-scheme: dark) {
|
@media screen and (prefers-color-scheme: dark) {
|
||||||
svg { stroke: #fff; fill: transparent !important; }
|
svg { stroke: #fff; fill: transparent !important; }
|
||||||
}
|
}
|
||||||
</style>`
|
</style>`,
|
||||||
)
|
),
|
||||||
).toString('utf8');
|
).toString('utf8');
|
||||||
|
|
||||||
defaultContentType(event, 'image/svg+xml')
|
defaultContentType(event, 'image/svg+xml');
|
||||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000')
|
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
|
||||||
|
|
||||||
return svg
|
return svg;
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
import { eventHandler, getQuery, setResponseHeader } from 'h3'
|
import { eventHandler, getQuery, setResponseHeader } from 'h3';
|
||||||
import iconNodes from '../../data/iconNodes'
|
import iconNodes from '../../data/iconNodes';
|
||||||
import { IconNodeWithKeys } from '../../theme/types'
|
import { IconNodeWithKeys } from '../../theme/types';
|
||||||
|
|
||||||
export default eventHandler((event) => {
|
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, 'Cache-Control', 'public, max-age=86400');
|
||||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||||
|
|
||||||
if (withUniqueKeys) {
|
if (withUniqueKeys) {
|
||||||
return iconNodes
|
return iconNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
|
return Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
|
||||||
if (withUniqueKeys) {
|
if (withUniqueKeys) {
|
||||||
return [name, iconNode]
|
return [name, iconNode];
|
||||||
}
|
}
|
||||||
|
|
||||||
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs}]) => {
|
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
|
||||||
return [name, 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 { eventHandler, getQuery, setResponseHeader, createError } from 'h3';
|
||||||
import iconNodes from '../../data/iconNodes'
|
import iconNodes from '../../data/iconNodes';
|
||||||
import createLucideIcon from 'lucide-react/src/createLucideIcon'
|
import createLucideIcon from 'lucide-react/src/createLucideIcon';
|
||||||
import { renderToString } from 'react-dom/server'
|
import { renderToString } from 'react-dom/server';
|
||||||
import { createElement } from 'react'
|
import { createElement } from 'react';
|
||||||
|
|
||||||
export default eventHandler((event) => {
|
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) {
|
if (iconNode == null) {
|
||||||
const error = createError({
|
const error = createError({
|
||||||
statusCode: 404,
|
statusCode: 404,
|
||||||
message: `Icon "${params.iconName}" not found`,
|
message: `Icon "${params.iconName}" not found`,
|
||||||
})
|
});
|
||||||
|
|
||||||
return sendError(event, error)
|
return sendError(event, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const width = getQuery(event).width || undefined
|
const width = getQuery(event).width || undefined;
|
||||||
const height = getQuery(event).height || undefined
|
const height = getQuery(event).height || undefined;
|
||||||
const color = getQuery(event).color || undefined
|
const color = getQuery(event).color || undefined;
|
||||||
const strokeWidth = getQuery(event).strokeWidth || undefined
|
const strokeWidth = getQuery(event).strokeWidth || undefined;
|
||||||
|
|
||||||
const LucideIcon = createLucideIcon(params.iconName, iconNode)
|
const LucideIcon = createLucideIcon(params.iconName, iconNode);
|
||||||
|
|
||||||
const svg = Buffer.from(
|
const svg = Buffer.from(
|
||||||
renderToString(
|
renderToString(
|
||||||
@@ -32,14 +32,13 @@ export default eventHandler((event) => {
|
|||||||
height,
|
height,
|
||||||
color: color ? `#${color}` : undefined,
|
color: color ? `#${color}` : undefined,
|
||||||
strokeWidth,
|
strokeWidth,
|
||||||
}
|
}),
|
||||||
))
|
),
|
||||||
).toString('utf8');
|
).toString('utf8');
|
||||||
|
|
||||||
defaultContentType(event, 'image/svg+xml')
|
defaultContentType(event, 'image/svg+xml');
|
||||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000')
|
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
|
||||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
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) => {
|
export default eventHandler((event) => {
|
||||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
|
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
|
||||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||||
|
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(Object.entries(iconMetaData).map(([name, { tags }]) => [name, tags]));
|
||||||
Object.entries(iconMetaData).map(([name, { tags }]) => [ name, tags ])
|
});
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export default eventHandler(() => {
|
export default eventHandler(() => {
|
||||||
return { nitro: 'Is Awesome! asda' }
|
return { nitro: 'Is Awesome! asda' };
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { fileURLToPath, URL } from 'node:url'
|
import { fileURLToPath, URL } from 'node:url';
|
||||||
import { defineConfig } from 'vitepress'
|
import { defineConfig } from 'vitepress';
|
||||||
import sidebar from './sidebar';
|
import sidebar from './sidebar';
|
||||||
|
|
||||||
const title = "Lucide";
|
const title = 'Lucide';
|
||||||
const socialTitle = "Lucide Icons";
|
const socialTitle = 'Lucide Icons';
|
||||||
const description = "Beautiful & consistent icon toolkit made by the community."
|
const description = 'Beautiful & consistent icon toolkit made by the community.';
|
||||||
|
|
||||||
// https://vitepress.dev/reference/site-config
|
// https://vitepress.dev/reference/site-config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@@ -19,86 +19,135 @@ export default defineConfig({
|
|||||||
{
|
{
|
||||||
find: /^.*\/VPIconAlignLeft\.vue$/,
|
find: /^.*\/VPIconAlignLeft\.vue$/,
|
||||||
replacement: fileURLToPath(
|
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$/,
|
find: /^.*\/VPFooter\.vue$/,
|
||||||
replacement: fileURLToPath(
|
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: [
|
head: [
|
||||||
[ 'script', {
|
[
|
||||||
|
'script',
|
||||||
|
{
|
||||||
src: 'https://analytics.lucide.dev/js/script.js',
|
src: 'https://analytics.lucide.dev/js/script.js',
|
||||||
'data-domain': 'lucide.dev',
|
'data-domain': 'lucide.dev',
|
||||||
defer: ''
|
defer: '',
|
||||||
}],
|
},
|
||||||
[ 'meta', {
|
],
|
||||||
property:"og:locale",
|
[
|
||||||
content:"en_US"
|
'meta',
|
||||||
}],
|
{
|
||||||
[ 'meta', {
|
property: 'og:locale',
|
||||||
property:"og:type",
|
content: 'en_US',
|
||||||
content:"website"
|
},
|
||||||
}],
|
],
|
||||||
[ 'meta', {
|
[
|
||||||
property:"og:site_name",
|
'meta',
|
||||||
|
{
|
||||||
|
property: 'og:type',
|
||||||
|
content: 'website',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'meta',
|
||||||
|
{
|
||||||
|
property: 'og:site_name',
|
||||||
content: title,
|
content: title,
|
||||||
}],
|
},
|
||||||
[ 'meta', {
|
],
|
||||||
property:"og:title",
|
[
|
||||||
|
'meta',
|
||||||
|
{
|
||||||
|
property: 'og:title',
|
||||||
content: socialTitle,
|
content: socialTitle,
|
||||||
}],
|
},
|
||||||
[ 'meta', {
|
],
|
||||||
property:"og:description",
|
[
|
||||||
content: description
|
'meta',
|
||||||
}],
|
{
|
||||||
[ 'meta', {
|
property: 'og:description',
|
||||||
property:"og:url",
|
content: description,
|
||||||
content:"https://lucide.dev"
|
},
|
||||||
}],
|
],
|
||||||
[ 'meta', {
|
[
|
||||||
property:"og:image",
|
'meta',
|
||||||
content: "https://lucide.dev/og.png"
|
{
|
||||||
}],
|
property: 'og:url',
|
||||||
[ 'meta', {
|
content: 'https://lucide.dev',
|
||||||
property:"og:image:width",
|
},
|
||||||
content:"1200"
|
],
|
||||||
}],
|
[
|
||||||
[ 'meta', {
|
'meta',
|
||||||
property:"og:image:height",
|
{
|
||||||
content:"630"
|
property: 'og:image',
|
||||||
}],
|
content: 'https://lucide.dev/og.png',
|
||||||
[ 'meta', {
|
},
|
||||||
property:"og:image:type",
|
],
|
||||||
content:"image/png"
|
[
|
||||||
}],
|
'meta',
|
||||||
[ 'meta', {
|
{
|
||||||
property:"twitter:card",
|
property: 'og:image:width',
|
||||||
content:"summary_large_image"
|
content: '1200',
|
||||||
}],
|
},
|
||||||
[ 'meta', {
|
],
|
||||||
property:"twitter:title",
|
[
|
||||||
|
'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,
|
content: socialTitle,
|
||||||
}],
|
},
|
||||||
[ 'meta', {
|
],
|
||||||
property:"twitter:description",
|
[
|
||||||
content: description
|
'meta',
|
||||||
}],
|
{
|
||||||
[ 'meta', {
|
property: 'twitter:description',
|
||||||
property:"twitter:image",
|
content: description,
|
||||||
content:"https://lucide.dev/og.png"
|
},
|
||||||
}],
|
],
|
||||||
|
[
|
||||||
|
'meta',
|
||||||
|
{
|
||||||
|
property: 'twitter:image',
|
||||||
|
content: 'https://lucide.dev/og.png',
|
||||||
|
},
|
||||||
|
],
|
||||||
],
|
],
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
// https://vitepress.dev/reference/default-theme-config
|
// https://vitepress.dev/reference/default-theme-config
|
||||||
logo: {
|
logo: {
|
||||||
light: '/logo.light.svg',
|
light: '/logo.light.svg',
|
||||||
dark: '/logo.dark.svg'
|
dark: '/logo.dark.svg',
|
||||||
},
|
},
|
||||||
nav: [
|
nav: [
|
||||||
{ text: 'Icons', link: '/icons/' },
|
{ text: 'Icons', link: '/icons/' },
|
||||||
@@ -110,21 +159,21 @@ export default defineConfig({
|
|||||||
sidebar,
|
sidebar,
|
||||||
socialLinks: [
|
socialLinks: [
|
||||||
{ icon: 'github', link: 'https://github.com/lucide-icons/lucide' },
|
{ icon: 'github', link: 'https://github.com/lucide-icons/lucide' },
|
||||||
{ icon: 'discord', link: 'https://discord.gg/EH6nSts' }
|
{ icon: 'discord', link: 'https://discord.gg/EH6nSts' },
|
||||||
],
|
],
|
||||||
footer: {
|
footer: {
|
||||||
message: 'Released under the ISC License.',
|
message: 'Released under the ISC License.',
|
||||||
copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`
|
copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`,
|
||||||
},
|
},
|
||||||
editLink: {
|
editLink: {
|
||||||
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path'
|
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path',
|
||||||
},
|
},
|
||||||
carbonAds: {
|
carbonAds: {
|
||||||
code: 'CWYIC53U',
|
code: 'CWYIC53U',
|
||||||
placement: 'lucidedev'
|
placement: 'lucidedev',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
sitemap: {
|
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,160 @@
|
|||||||
"order": 0,
|
"order": 0,
|
||||||
"icon": "js",
|
"icon": "js",
|
||||||
"shields": [
|
"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": {
|
"lucide-react": {
|
||||||
"order": 1,
|
"order": 1,
|
||||||
"icon": "react",
|
"icon": "react",
|
||||||
"shields": [
|
"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" }
|
"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": {
|
"lucide-vue": {
|
||||||
"order": 2,
|
"order": 2,
|
||||||
"icon": "vue",
|
"icon": "vue",
|
||||||
"shields": [
|
"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-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"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lucide-vue-next": {
|
"lucide-vue-next": {
|
||||||
"order": 3,
|
"order": 3,
|
||||||
"icon": "vue-next",
|
"icon": "vue-next",
|
||||||
"shields": [
|
"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": {
|
"lucide-svelte": {
|
||||||
"order": 4,
|
"order": 4,
|
||||||
"icon": "svelte",
|
"icon": "svelte",
|
||||||
"shields": [
|
"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": {
|
"lucide-solid": {
|
||||||
"order": 4,
|
"order": 4,
|
||||||
"icon": "solid",
|
"icon": "solid",
|
||||||
"shields": [
|
"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" }
|
"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": {
|
"lucide-preact": {
|
||||||
"order": 5,
|
"order": 5,
|
||||||
"icon": "preact",
|
"icon": "preact",
|
||||||
"shields": [
|
"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-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-react-native": {
|
"lucide-react-native": {
|
||||||
"order": 6,
|
"order": 6,
|
||||||
"icon": "react-native",
|
"icon": "react-native",
|
||||||
"shields": [
|
"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": {
|
"lucide-angular": {
|
||||||
"order": 7,
|
"order": 7,
|
||||||
"icon": "angular",
|
"icon": "angular",
|
||||||
"shields": [
|
"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-static": {
|
"lucide-static": {
|
||||||
"order": 8,
|
"order": 8,
|
||||||
"icon": "svg",
|
"icon": "svg",
|
||||||
"shields": [
|
"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" }
|
"alt": "npm",
|
||||||
]
|
"src": "https://img.shields.io/npm/v/lucide-static",
|
||||||
|
"href": "https://www.npmjs.com/package/lucide-static"
|
||||||
},
|
},
|
||||||
"lucide-flutter": {
|
{
|
||||||
"order": 9,
|
"alt": "npm",
|
||||||
"icon": "flutter",
|
"src": "https://img.shields.io/npm/dw/lucide-static",
|
||||||
"shields": [
|
"href": "https://www.npmjs.com/package/lucide-static"
|
||||||
{ "alt": "flutter", "src": "https://img.shields.io/pub/v/lucide_icons", "href": "https://img.shields.io/pub/v/lucide_icons" }
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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://github.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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -7,51 +7,80 @@ interface BackdropProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Backdrop = ({ src, color = 'red', backdropString }: BackdropProps): JSX.Element => {
|
const Backdrop = ({ src, color = 'red', backdropString }: BackdropProps): JSX.Element => {
|
||||||
|
const id = React.useId();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<defs xmlns="http://www.w3.org/2000/svg">
|
<defs xmlns="http://www.w3.org/2000/svg">
|
||||||
<pattern
|
<pattern
|
||||||
id="pattern"
|
id={`pattern-${id}`}
|
||||||
width=".1"
|
width=".1"
|
||||||
height=".1"
|
height=".1"
|
||||||
patternUnits="userSpaceOnUse"
|
patternUnits="userSpaceOnUse"
|
||||||
patternTransform="rotate(45 50 50)"
|
patternTransform="rotate(45 50 50)"
|
||||||
>
|
>
|
||||||
<line stroke={color} strokeWidth={0.1} y2={1} />
|
<line
|
||||||
<line stroke={color} strokeWidth={0.1} y2={1} />
|
stroke={color}
|
||||||
|
strokeWidth={0.1}
|
||||||
|
y2={1}
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth={0.1}
|
||||||
|
y2={1}
|
||||||
|
/>
|
||||||
</pattern>
|
</pattern>
|
||||||
</defs>
|
</defs>
|
||||||
<mask id="svg-preview-backdrop-mask-outline" maskUnits="userSpaceOnUse">
|
<mask
|
||||||
<g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} />
|
id={`svg-preview-backdrop-mask-${id}`}
|
||||||
<g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} />
|
maskUnits="userSpaceOnUse"
|
||||||
</mask>
|
>
|
||||||
<mask id="svg-preview-backdrop-mask-fill" maskUnits="userSpaceOnUse">
|
<g
|
||||||
<g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} />
|
stroke="#fff"
|
||||||
<g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} />
|
dangerouslySetInnerHTML={{ __html: backdropString }}
|
||||||
<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>
|
|
||||||
<rect
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
fill="url(#pattern)"
|
|
||||||
stroke="none"
|
|
||||||
mask={'url(#svg-preview-backdrop-mask-fill)'}
|
|
||||||
/>
|
/>
|
||||||
|
<g dangerouslySetInnerHTML={{ __html: src }} />
|
||||||
|
</mask>
|
||||||
|
<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 mask={`url(#svg-preview-backdrop-mask-${id})`}>
|
||||||
<rect
|
<rect
|
||||||
x="0"
|
x="0"
|
||||||
y="0"
|
y="0"
|
||||||
width="24"
|
width="24"
|
||||||
height="24"
|
height="24"
|
||||||
fill={color}
|
|
||||||
opacity={0.5}
|
opacity={0.5}
|
||||||
|
fill={`url(#pattern-${id})`}
|
||||||
stroke="none"
|
stroke="none"
|
||||||
mask={'url(#svg-preview-backdrop-mask-fill)'}
|
|
||||||
/>
|
/>
|
||||||
|
<g
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth={2.25}
|
||||||
|
opacity={0.75}
|
||||||
|
dangerouslySetInnerHTML={{ __html: src }}
|
||||||
|
/>
|
||||||
|
<g
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth={2.25}
|
||||||
|
opacity={0.75}
|
||||||
|
mask={`url(#svg-preview-backdrop-mask-outline-${id})`}
|
||||||
|
dangerouslySetInnerHTML={{ __html: backdropString }}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ const Grid = ({
|
|||||||
strokeWidth: number;
|
strokeWidth: number;
|
||||||
radius: number;
|
radius: number;
|
||||||
} & PathProps<'stroke', 'strokeWidth'>) => (
|
} & PathProps<'stroke', 'strokeWidth'>) => (
|
||||||
<g className="svg-preview-grid-group" strokeLinecap="butt" {...props}>
|
<g
|
||||||
|
className="svg-preview-grid-group"
|
||||||
|
strokeLinecap="butt"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
<rect
|
<rect
|
||||||
className="svg-preview-grid-rect"
|
className="svg-preview-grid-rect"
|
||||||
width={24 - props.strokeWidth}
|
width={24 - props.strokeWidth}
|
||||||
@@ -21,11 +25,29 @@ const Grid = ({
|
|||||||
fill={fill}
|
fill={fill}
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
|
strokeDasharray={'0 0.1 ' + '0.1 0.15 '.repeat(11) + '0 0.15'}
|
||||||
|
strokeWidth={0.1}
|
||||||
d={
|
d={
|
||||||
props.d ||
|
props.d ||
|
||||||
new Array(Math.floor(24 - 1))
|
new Array(Math.floor(24 - 1))
|
||||||
.fill(null)
|
.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${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
|
||||||
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
|
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
|
||||||
])
|
])
|
||||||
@@ -44,15 +66,21 @@ const Shadow = ({
|
|||||||
paths: Path[];
|
paths: Path[];
|
||||||
} & PathProps<'stroke' | 'strokeWidth' | 'strokeOpacity', 'd'>) => {
|
} & PathProps<'stroke' | 'strokeWidth' | 'strokeOpacity', 'd'>) => {
|
||||||
const groupedPaths = Object.entries(
|
const groupedPaths = Object.entries(
|
||||||
paths.reduce((groups, val) => {
|
paths.reduce(
|
||||||
|
(groups, val) => {
|
||||||
const key = val.c.id;
|
const key = val.c.id;
|
||||||
groups[key] = [...(groups[key] || []), val];
|
groups[key] = [...(groups[key] || []), val];
|
||||||
return groups;
|
return groups;
|
||||||
}, {} as Record<number, Path[]>)
|
},
|
||||||
|
{} as Record<number, Path[]>,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<g className="svg-preview-shadow-mask-group" {...props}>
|
<g
|
||||||
|
className="svg-preview-shadow-mask-group"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
{groupedPaths.map(([id, paths]) => (
|
{groupedPaths.map(([id, paths]) => (
|
||||||
<mask
|
<mask
|
||||||
id={`svg-preview-shadow-mask-${id}`}
|
id={`svg-preview-shadow-mask-${id}`}
|
||||||
@@ -61,7 +89,15 @@ const Shadow = ({
|
|||||||
strokeWidth={props.strokeWidth}
|
strokeWidth={props.strokeWidth}
|
||||||
stroke="#000"
|
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
|
<path
|
||||||
d={paths
|
d={paths
|
||||||
.flatMap(({ prev, next }) => [
|
.flatMap(({ prev, next }) => [
|
||||||
@@ -74,9 +110,16 @@ const Shadow = ({
|
|||||||
</mask>
|
</mask>
|
||||||
))}
|
))}
|
||||||
</g>
|
</g>
|
||||||
<g className="svg-preview-shadow-group" {...props}>
|
<g
|
||||||
|
className="svg-preview-shadow-group"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
{paths.map(({ d, c: { id } }, i) => (
|
{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
|
<path
|
||||||
d={paths
|
d={paths
|
||||||
@@ -94,9 +137,16 @@ const ColoredPath = ({
|
|||||||
paths,
|
paths,
|
||||||
...props
|
...props
|
||||||
}: { paths: Path[]; colors: string[] } & PathProps<never, 'd' | 'stroke'>) => (
|
}: { 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) => (
|
{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>
|
</g>
|
||||||
);
|
);
|
||||||
@@ -138,7 +188,15 @@ const ControlPath = ({
|
|||||||
key={i}
|
key={i}
|
||||||
maskUnits="userSpaceOnUse"
|
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${prev.x} ${prev.y}h.01`} />
|
||||||
<path d={`M${next.x} ${next.y}h.01`} />
|
<path d={`M${next.x} ${next.y}h.01`} />
|
||||||
</mask>
|
</mask>
|
||||||
@@ -146,7 +204,10 @@ const ControlPath = ({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</g>
|
</g>
|
||||||
<g className="svg-preview-control-path-group" {...props}>
|
<g
|
||||||
|
className="svg-preview-control-path-group"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
{controlPaths.map(({ d, showMarker }, i) => (
|
{controlPaths.map(({ d, showMarker }, i) => (
|
||||||
<path
|
<path
|
||||||
key={i}
|
key={i}
|
||||||
@@ -155,18 +216,33 @@ const ControlPath = ({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</g>
|
</g>
|
||||||
<g className="svg-preview-control-path-marker-group" {...props}>
|
<g
|
||||||
|
className="svg-preview-control-path-marker-group"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
d={controlPaths
|
d={controlPaths
|
||||||
.flatMap(({ prev, next, showMarker }) =>
|
.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('')}
|
.join('')}
|
||||||
/>
|
/>
|
||||||
{controlPaths.map(({ d, prev, next, startMarker, endMarker }, i) => (
|
{controlPaths.map(({ d, prev, next, startMarker, endMarker }, i) => (
|
||||||
<React.Fragment key={i}>
|
<React.Fragment key={i}>
|
||||||
{startMarker && <circle cx={prev.x} cy={prev.y} r={pointSize / 2} />}
|
{startMarker && (
|
||||||
{endMarker && <circle cx={next.x} cy={next.y} r={pointSize / 2} />}
|
<circle
|
||||||
|
cx={prev.x}
|
||||||
|
cy={prev.y}
|
||||||
|
r={pointSize / 2}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{endMarker && (
|
||||||
|
<circle
|
||||||
|
cx={next.x}
|
||||||
|
cy={next.y}
|
||||||
|
r={pointSize / 2}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</g>
|
</g>
|
||||||
@@ -182,15 +258,16 @@ const Radii = ({
|
|||||||
any
|
any
|
||||||
>) => {
|
>) => {
|
||||||
return (
|
return (
|
||||||
<g className="svg-preview-radii-group" {...props}>
|
<g
|
||||||
|
className="svg-preview-radii-group"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
{paths.map(
|
{paths.map(
|
||||||
({ c, prev, next, circle }, i) =>
|
({ c, prev, next, circle }, i) =>
|
||||||
circle && (
|
circle && (
|
||||||
<React.Fragment key={i}>
|
<React.Fragment key={i}>
|
||||||
{c.name !== "circle" && (
|
{c.name !== 'circle' && (
|
||||||
<path
|
<path d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`} />
|
||||||
d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<circle
|
<circle
|
||||||
cy={circle.y}
|
cy={circle.y}
|
||||||
@@ -200,7 +277,7 @@ const Radii = ({
|
|||||||
stroke={
|
stroke={
|
||||||
(Math.round(circle.x * 100) / 100) % 1 !== 0 ||
|
(Math.round(circle.x * 100) / 100) % 1 !== 0 ||
|
||||||
(Math.round(circle.y * 100) / 100) % 1 !== 0
|
(Math.round(circle.y * 100) / 100) % 1 !== 0
|
||||||
? "red"
|
? 'red'
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -208,11 +285,7 @@ const Radii = ({
|
|||||||
cy={circle.y}
|
cy={circle.y}
|
||||||
cx={circle.x}
|
cx={circle.x}
|
||||||
r={circle.r}
|
r={circle.r}
|
||||||
stroke={
|
stroke={(Math.round(circle.r * 1000) / 1000) % 1 !== 0 ? 'red' : undefined}
|
||||||
(Math.round(circle.r * 1000) / 1000) % 1 !== 0
|
|
||||||
? "red"
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
),
|
),
|
||||||
@@ -228,15 +301,29 @@ const Handles = ({
|
|||||||
'strokeWidth' | 'stroke' | 'strokeDasharray' | 'strokeOpacity',
|
'strokeWidth' | 'stroke' | 'strokeDasharray' | 'strokeOpacity',
|
||||||
any
|
any
|
||||||
>) => {
|
>) => {
|
||||||
console.log(paths);
|
|
||||||
return (
|
return (
|
||||||
<g className="svg-preview-handles-group" {...props}>
|
<g
|
||||||
|
className="svg-preview-handles-group"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
{paths.map(({ c, prev, next, cp1, cp2 }) => (
|
{paths.map(({ c, prev, next, cp1, cp2 }) => (
|
||||||
<>
|
<>
|
||||||
{cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />}
|
{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 && <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>
|
</g>
|
||||||
@@ -280,9 +367,27 @@ const SvgPreview = React.forwardRef<
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<style>{darkModeCss}</style>
|
<style>{darkModeCss}</style>
|
||||||
{showGrid && <Grid strokeWidth={0.1} stroke="#777" strokeOpacity={0.3} radius={1} />}
|
{showGrid && (
|
||||||
<Shadow paths={paths} strokeWidth={4} stroke="#777" radius={1} strokeOpacity={0.15} />
|
<Grid
|
||||||
<Handles paths={paths} strokeWidth={0.12} stroke="#777" strokeOpacity={0.6} />
|
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
|
<ColoredPath
|
||||||
paths={paths}
|
paths={paths}
|
||||||
colors={[
|
colors={[
|
||||||
@@ -307,8 +412,19 @@ const SvgPreview = React.forwardRef<
|
|||||||
stroke="#777"
|
stroke="#777"
|
||||||
strokeOpacity={0.3}
|
strokeOpacity={0.3}
|
||||||
/>
|
/>
|
||||||
<ControlPath radius={1} paths={paths} pointSize={1} stroke="#fff" strokeWidth={0.125} />
|
<ControlPath
|
||||||
<Handles paths={paths} strokeWidth={0.12} stroke="#FFF" strokeOpacity={0.3} />
|
radius={1}
|
||||||
|
paths={paths}
|
||||||
|
pointSize={1}
|
||||||
|
stroke="#fff"
|
||||||
|
strokeWidth={0.125}
|
||||||
|
/>
|
||||||
|
<Handles
|
||||||
|
paths={paths}
|
||||||
|
strokeWidth={0.12}
|
||||||
|
stroke="#FFF"
|
||||||
|
strokeOpacity={0.3}
|
||||||
|
/>
|
||||||
{children}
|
{children}
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export type Path = {
|
|||||||
|
|
||||||
export type PathProps<
|
export type PathProps<
|
||||||
RequiredProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
|
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>> &
|
> = Required<Pick<React.SVGProps<SVGElement & SVGRectElement & SVGCircleElement>, RequiredProps>> &
|
||||||
Omit<
|
Omit<
|
||||||
React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>,
|
React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>,
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const getCommands = (src: string) =>
|
|||||||
getNodes(src)
|
getNodes(src)
|
||||||
.map(convertToPathNode)
|
.map(convertToPathNode)
|
||||||
.flatMap(({ d, name }, idx) =>
|
.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) => {
|
export const getPaths = (src: string) => {
|
||||||
@@ -60,10 +60,10 @@ export const getPaths = (src: string) => {
|
|||||||
let prev: Point | undefined = undefined;
|
let prev: Point | undefined = undefined;
|
||||||
let start: Point | undefined = undefined;
|
let start: Point | undefined = undefined;
|
||||||
const addPath = (
|
const addPath = (
|
||||||
c: typeof commands[number],
|
c: (typeof commands)[number],
|
||||||
next: Point,
|
next: Point,
|
||||||
d?: string,
|
d?: string,
|
||||||
extras?: { circle?: Path['circle']; cp1?: Path['cp1']; cp2?: Path['cp2'] }
|
extras?: { circle?: Path['circle']; cp1?: Path['cp1']; cp2?: Path['cp2'] },
|
||||||
) => {
|
) => {
|
||||||
assert(prev);
|
assert(prev);
|
||||||
paths.push({
|
paths.push({
|
||||||
@@ -153,7 +153,7 @@ export const getPaths = (src: string) => {
|
|||||||
{
|
{
|
||||||
cp1: { x: prev.x - reflectedCp1.x, y: prev.y - reflectedCp1.y },
|
cp1: { x: prev.x - reflectedCp1.x, y: prev.y - reflectedCp1.y },
|
||||||
cp2: { x: c.x2, y: c.y2 },
|
cp2: { x: c.x2, y: c.y2 },
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -169,7 +169,7 @@ export const getPaths = (src: string) => {
|
|||||||
assert(prev);
|
assert(prev);
|
||||||
const backTrackCP = (
|
const backTrackCP = (
|
||||||
index: number,
|
index: number,
|
||||||
currentPoint: { x: number; y: number }
|
currentPoint: { x: number; y: number },
|
||||||
): { x: number; y: number } => {
|
): { x: number; y: number } => {
|
||||||
const previousCommand = commands[index - 1];
|
const previousCommand = commands[index - 1];
|
||||||
if (!previousCommand) {
|
if (!previousCommand) {
|
||||||
@@ -211,7 +211,7 @@ export const getPaths = (src: string) => {
|
|||||||
{
|
{
|
||||||
cp1: { x: prevCP.x, y: prevCP.y },
|
cp1: { x: prevCP.x, y: prevCP.y },
|
||||||
cp2: { x: prevCP.x, y: prevCP.y },
|
cp2: { x: prevCP.x, y: prevCP.y },
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -226,13 +226,13 @@ export const getPaths = (src: string) => {
|
|||||||
c.lArcFlag,
|
c.lArcFlag,
|
||||||
c.sweepFlag,
|
c.sweepFlag,
|
||||||
c.x,
|
c.x,
|
||||||
c.y
|
c.y,
|
||||||
);
|
);
|
||||||
addPath(
|
addPath(
|
||||||
c,
|
c,
|
||||||
c,
|
c,
|
||||||
`M ${prev.x} ${prev.y} A${c.rX} ${c.rY} ${c.xRot} ${c.lArcFlag} ${c.sweepFlag} ${c.x} ${c.y}`,
|
`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;
|
break;
|
||||||
}
|
}
|
||||||
@@ -253,7 +253,7 @@ export const arcEllipseCenter = (
|
|||||||
fa: number,
|
fa: number,
|
||||||
fs: number,
|
fs: number,
|
||||||
x2: number,
|
x2: number,
|
||||||
y2: number
|
y2: number,
|
||||||
) => {
|
) => {
|
||||||
const phi = (a * Math.PI) / 180;
|
const phi = (a * Math.PI) / 180;
|
||||||
|
|
||||||
@@ -280,7 +280,7 @@ export const arcEllipseCenter = (
|
|||||||
sign *
|
sign *
|
||||||
Math.sqrt(
|
Math.sqrt(
|
||||||
Math.max(rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p, 0) /
|
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];
|
const V2 = [(rx * y1p) / ry, (-ry * x1p) / rx];
|
||||||
|
|||||||
@@ -1,28 +1,34 @@
|
|||||||
import fs from "fs";
|
import fs from 'fs';
|
||||||
import path from "path";
|
import path from 'path';
|
||||||
import {Category, IconEntity} from "../theme/types";
|
import { Category, IconEntity } from '../theme/types';
|
||||||
|
|
||||||
const directory = path.join(process.cwd(), "../categories");
|
const directory = path.join(process.cwd(), '../categories');
|
||||||
|
|
||||||
export function getAllCategoryFiles(): Category[] {
|
export function getAllCategoryFiles(): Category[] {
|
||||||
const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json');
|
const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json');
|
||||||
|
|
||||||
return fileNames.map((fileName) => {
|
return fileNames.map((fileName) => {
|
||||||
const name = path.basename(fileName, '.json')
|
const name = path.basename(fileName, '.json');
|
||||||
const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8')
|
const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8');
|
||||||
|
|
||||||
const parsedFileContent = JSON.parse(fileContent)
|
const parsedFileContent = JSON.parse(fileContent);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
title: parsedFileContent.title,
|
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) => ({
|
return categories.map((category) => ({
|
||||||
...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 {
|
import { bundledLanguages, type ThemeRegistration } from 'shikiji';
|
||||||
bundledLanguages,
|
import { getHighlighter } from 'shikiji';
|
||||||
type ThemeRegistration
|
|
||||||
} from 'shikiji'
|
|
||||||
import {
|
|
||||||
getHighlighter,
|
|
||||||
} from 'shikiji'
|
|
||||||
|
|
||||||
|
|
||||||
type CodeExampleType = {
|
type CodeExampleType = {
|
||||||
title: string,
|
title: string;
|
||||||
language: string,
|
language: string;
|
||||||
code: string,
|
code: string;
|
||||||
}[]
|
}[];
|
||||||
|
|
||||||
const getIconCodes = (): CodeExampleType => {
|
const getIconCodes = (): CodeExampleType => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
language: 'html',
|
language: 'js',
|
||||||
title: 'HTML',
|
title: 'Vanilla',
|
||||||
code: `<i data-lucide="Name"></i>`
|
code: `\
|
||||||
|
import { createIcons, icons } from 'lucide';
|
||||||
|
|
||||||
|
createIcons({ icons });
|
||||||
|
|
||||||
|
document.body.append('<i data-lucide="$Name"></i>');\
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
language: 'tsx',
|
language: 'tsx',
|
||||||
title: 'React',
|
title: 'React',
|
||||||
code: `import { PascalCase } from 'lucide-react';
|
code: `import { $PascalCase } from 'lucide-react';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
<PascalCase />
|
<$PascalCase />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,11 +38,11 @@ export default App;
|
|||||||
language: 'vue',
|
language: 'vue',
|
||||||
title: 'Vue',
|
title: 'Vue',
|
||||||
code: `<script setup>
|
code: `<script setup>
|
||||||
import { PascalCase } from 'lucide-vue-next';
|
import { $PascalCase } from 'lucide-vue-next';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<PascalCase />
|
<$PascalCase />
|
||||||
</template>
|
</template>
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
@@ -50,20 +50,20 @@ export default App;
|
|||||||
language: 'svelte',
|
language: 'svelte',
|
||||||
title: 'Svelte',
|
title: 'Svelte',
|
||||||
code: `<script>
|
code: `<script>
|
||||||
import { PascalCase } from 'lucide-svelte';
|
import { $PascalCase } from 'lucide-svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PascalCase />
|
<$PascalCase />
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
language: 'tsx',
|
language: 'tsx',
|
||||||
title: 'Preact',
|
title: 'Preact',
|
||||||
code: `import { PascalCase } from 'lucide-preact';
|
code: `import { $PascalCase } from 'lucide-preact';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
<PascalCase />
|
<$PascalCase />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -73,11 +73,11 @@ export default App;
|
|||||||
{
|
{
|
||||||
language: 'tsx',
|
language: 'tsx',
|
||||||
title: 'Solid',
|
title: 'Solid',
|
||||||
code: `import { PascalCase } from 'lucide-solid';
|
code: `import { $PascalCase } from 'lucide-solid';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
<PascalCase />
|
<$PascalCase />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -88,16 +88,16 @@ export default App;
|
|||||||
language: 'tsx',
|
language: 'tsx',
|
||||||
title: 'Angular',
|
title: 'Angular',
|
||||||
code: `// app.module.ts
|
code: `// app.module.ts
|
||||||
import { LucideAngularModule, PascalCase } from 'lucide-angular';
|
import { LucideAngularModule, $PascalCase } from 'lucide-angular';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
LucideAngularModule.pick({ PascalCase })
|
LucideAngularModule.pick({ $PascalCase })
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
// app.component.html
|
// 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');
|
@import ('~lucide-static/font/Lucide.css');
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="icon-Name"></div>
|
<div class="icon-$Name"></div>
|
||||||
`,
|
`,
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ThemeOptions =
|
export type ThemeOptions =
|
||||||
| ThemeRegistration
|
| ThemeRegistration
|
||||||
| { light: ThemeRegistration; dark: ThemeRegistration }
|
| { light: ThemeRegistration; dark: ThemeRegistration };
|
||||||
|
|
||||||
const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
||||||
const highlighter = await getHighlighter({
|
const highlighter = await getHighlighter({
|
||||||
themes: ['github-light', 'github-dark'],
|
themes: ['github-light', 'github-dark'],
|
||||||
langs: Object.keys(bundledLanguages)
|
langs: Object.keys(bundledLanguages),
|
||||||
})
|
});
|
||||||
|
|
||||||
const highlightedCode = highlighter.codeToHtml(code, {
|
const highlightedCode = highlighter
|
||||||
|
.codeToHtml(code, {
|
||||||
lang,
|
lang,
|
||||||
themes: {
|
themes: {
|
||||||
light: 'github-light',
|
light: 'github-light',
|
||||||
dark: 'github-dark'
|
dark: 'github-dark',
|
||||||
},
|
},
|
||||||
defaultColor: false
|
defaultColor: false,
|
||||||
}).replace('shiki-themes', 'shiki-themes vp-code')
|
})
|
||||||
|
.replace('shiki-themes', 'shiki-themes vp-code');
|
||||||
|
|
||||||
return `<div class="language-${lang} ${active ? 'active' : ''}">
|
return `<div class="language-${lang} ${active ? 'active' : ''}">
|
||||||
<button title="Copy Code" class="copy"></button>
|
<button title="Copy Code" class="copy"></button>
|
||||||
<span class="lang">${lang}</span>
|
<span class="lang">${lang}</span>
|
||||||
${highlightedCode}
|
${highlightedCode}
|
||||||
</div>`
|
</div>`;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export default async function createCodeExamples() {
|
export default async function createCodeExamples() {
|
||||||
const codes = getIconCodes();
|
const codes = getIconCodes();
|
||||||
@@ -153,7 +154,7 @@ export default async function createCodeExamples() {
|
|||||||
language: language,
|
language: language,
|
||||||
code: codeString,
|
code: codeString,
|
||||||
};
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
return Promise.all(codeExamplePromises);
|
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 { promises as fs, constants } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import yaml from 'js-yaml'
|
|
||||||
import { PackageItem } from '../theme/types';
|
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 fetchPackages = async (): Promise<PackageItem[]> => {
|
||||||
const docsDir = path.resolve(process.cwd(), '../packages');
|
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 packageJsons = await Promise.all(
|
||||||
const filePath = path.resolve(directory, filename)
|
fileNames.map(async ({ filename, directory }) => {
|
||||||
|
const filePath = path.resolve(directory, filename);
|
||||||
const fileStat = await fs.lstat(filePath);
|
const fileStat = await fs.lstat(filePath);
|
||||||
|
|
||||||
if(!fileStat.isDirectory()) return null;
|
if (!fileStat.isDirectory()) return null;
|
||||||
|
|
||||||
const jsonFilePath = path.resolve(filePath, 'package.json')
|
const jsonFilePath = path.resolve(filePath, 'package.json');
|
||||||
if (await fileExist(jsonFilePath)) {
|
if (await fileExist(jsonFilePath)) {
|
||||||
return JSON.parse(
|
return JSON.parse(await fs.readFile(jsonFilePath, 'utf-8'));
|
||||||
await fs.readFile(jsonFilePath, 'utf-8')
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ymlFilePath = path.resolve(filePath, 'pubspec.yaml')
|
return null;
|
||||||
if(await fileExist(ymlFilePath)) {
|
}),
|
||||||
return yaml.load(
|
|
||||||
await fs.readFile(ymlFilePath, 'utf-8')
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
return packageJsons;
|
||||||
}))
|
};
|
||||||
|
|
||||||
return packageJsons
|
|
||||||
}
|
|
||||||
|
|
||||||
export default fetchPackages;
|
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[]) {
|
async function generateZip(icons: IconContent[]) {
|
||||||
const JSZip = (await import('jszip')).default
|
const JSZip = (await import('jszip')).default;
|
||||||
|
|
||||||
const zip = new JSZip();
|
const zip = new JSZip();
|
||||||
|
|
||||||
const addingZipPromises = icons.map(([name, src]) =>
|
const addingZipPromises = icons.map(([name, src]) => zip.file(`${name}.svg`, src));
|
||||||
zip.file(`${name}.svg`, src),
|
|
||||||
);
|
|
||||||
|
|
||||||
await Promise.all(addingZipPromises)
|
await Promise.all(addingZipPromises);
|
||||||
|
|
||||||
return zip.generateAsync({ type: 'blob' });
|
return zip.generateAsync({ type: 'blob' });
|
||||||
}
|
}
|
||||||
|
|
||||||
export default generateZip
|
export default generateZip;
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import { createLucideIcon } from "lucide-react/src/lucide-react"
|
import { createLucideIcon } from 'lucide-react/src/lucide-react';
|
||||||
import { type LucideProps, type IconNode } from "lucide-react/src/createLucideIcon"
|
import { type LucideProps, type IconNode } from 'lucide-react/src/createLucideIcon';
|
||||||
import { IconEntity } from "../theme/types"
|
import { IconEntity } from '../theme/types';
|
||||||
import { renderToStaticMarkup } from 'react-dom/server';
|
import { renderToStaticMarkup } from 'react-dom/server';
|
||||||
import { IconContent } from "./generateZip";
|
import { IconContent } from './generateZip';
|
||||||
|
|
||||||
const getFallbackZip = (icons: IconEntity[], params: LucideProps) => {
|
const getFallbackZip = (icons: IconEntity[], params: LucideProps) => {
|
||||||
return icons
|
return icons.map<IconContent>((icon) => {
|
||||||
.map<IconContent>((icon) => {
|
const Icon = createLucideIcon(icon.name, icon.iconNode as IconNode);
|
||||||
const Icon = createLucideIcon(icon.name, icon.iconNode as IconNode)
|
const src = renderToStaticMarkup(<Icon {...params} />);
|
||||||
const src = renderToStaticMarkup(<Icon {...params} />)
|
return [icon.name, src];
|
||||||
return [icon.name, src]
|
});
|
||||||
})
|
};
|
||||||
}
|
|
||||||
|
|
||||||
|
export default getFallbackZip;
|
||||||
export default getFallbackZip
|
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
import fs from "fs";
|
import fs from 'fs';
|
||||||
import path from "path";
|
import path from 'path';
|
||||||
import { IconNodeWithKeys } from "../theme/types";
|
import { IconNodeWithKeys } from '../theme/types';
|
||||||
import iconNodes from '../data/iconNodes'
|
import iconNodes from '../data/iconNodes';
|
||||||
import releaseMeta from "../data/releaseMetaData.json";
|
import releaseMeta from '../data/releaseMetaData.json';
|
||||||
|
|
||||||
const DATE_OF_FORK = '2020-06-08T16:39:52+0100';
|
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 {
|
export interface GetDataOptions {
|
||||||
withChildKeys?: boolean
|
withChildKeys?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getData(name: string) {
|
export async function getData(name: string) {
|
||||||
const jsonPath = path.join(directory, `${name}.json`);
|
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 { tags, categories, contributors } = JSON.parse(jsonContent);
|
||||||
|
|
||||||
const iconNode = iconNodes[name]
|
const iconNode = iconNodes[name];
|
||||||
|
|
||||||
const releaseData = releaseMeta?.[name] ?? {
|
const releaseData = releaseMeta?.[name] ?? {
|
||||||
"createdRelease": {
|
createdRelease: {
|
||||||
"version": "0.0.0",
|
version: '0.0.0',
|
||||||
"date": DATE_OF_FORK
|
date: DATE_OF_FORK,
|
||||||
},
|
},
|
||||||
"changedRelease": {
|
changedRelease: {
|
||||||
"version": "0.0.0",
|
version: '0.0.0',
|
||||||
"date": DATE_OF_FORK
|
date: DATE_OF_FORK,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
@@ -36,11 +36,11 @@ export async function getData(name: string) {
|
|||||||
categories,
|
categories,
|
||||||
iconNode,
|
iconNode,
|
||||||
contributors,
|
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);
|
const names = Object.keys(iconNodes);
|
||||||
|
|
||||||
return Promise.all(names.map((name) => getData(name)));
|
return Promise.all(names.map((name) => getData(name)));
|
||||||
|
|||||||
@@ -1,50 +1,50 @@
|
|||||||
import { DefaultTheme, UserConfig } from "vitepress"
|
import { DefaultTheme, UserConfig } from 'vitepress';
|
||||||
|
|
||||||
const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
|
const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
|
||||||
'guide':[
|
guide: [
|
||||||
{
|
{
|
||||||
text: 'Introduction',
|
text: 'Introduction',
|
||||||
items: [
|
items: [
|
||||||
{ text: 'What is lucide?', link: '/guide/' },
|
{ text: 'What is lucide?', link: '/guide/' },
|
||||||
{ text: 'Installation', link: '/guide/installation' },
|
{ text: 'Installation', link: '/guide/installation' },
|
||||||
{ text: 'Comparison', link: '/guide/comparison' }
|
{ text: 'Comparison', link: '/guide/comparison' },
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Basics',
|
text: 'Basics',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
text: 'Color',
|
text: 'Color',
|
||||||
link: '/guide/basics/color'
|
link: '/guide/basics/color',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Sizing',
|
text: 'Sizing',
|
||||||
link: '/guide/basics/sizing'
|
link: '/guide/basics/sizing',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Stroke width',
|
text: 'Stroke width',
|
||||||
link: '/guide/basics/stroke-width'
|
link: '/guide/basics/stroke-width',
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
// TODO: Add this section
|
// TODO: Add this section
|
||||||
{
|
{
|
||||||
text: 'Advanced',
|
text: 'Advanced',
|
||||||
items: [
|
items: [
|
||||||
// {
|
{
|
||||||
// text: 'Accessibility',
|
text: 'Accessibility',
|
||||||
// link: '/guide/advanced/accessibility'
|
link: '/guide/advanced/accessibility',
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
text: 'Global styling',
|
text: 'Global styling',
|
||||||
link: '/guide/advanced/global-styling'
|
link: '/guide/advanced/global-styling',
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// text: 'Animations',
|
// text: 'Animations',
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
text: 'Filled icons',
|
text: 'Filled icons',
|
||||||
link: '/guide/advanced/filled-icons'
|
link: '/guide/advanced/filled-icons',
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// text: 'Combining icons',
|
// text: 'Combining icons',
|
||||||
@@ -55,75 +55,77 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
|
|||||||
// {
|
// {
|
||||||
// text: 'Auto importing'
|
// text: 'Auto importing'
|
||||||
// },
|
// },
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Packages',
|
text: 'Packages',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
text: 'Lucide',
|
text: 'Lucide',
|
||||||
link: '/guide/packages/lucide'
|
link: '/guide/packages/lucide',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Lucide React',
|
text: 'Lucide React',
|
||||||
link: '/guide/packages/lucide-react'
|
link: '/guide/packages/lucide-react',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Lucide React Native',
|
text: 'Lucide React Native',
|
||||||
link: '/guide/packages/lucide-react-native'
|
link: '/guide/packages/lucide-react-native',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Lucide Vue',
|
text: 'Lucide Vue',
|
||||||
link: '/guide/packages/lucide-vue-next'
|
link: '/guide/packages/lucide-vue-next',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Lucide Svelte',
|
text: 'Lucide Svelte',
|
||||||
link: '/guide/packages/lucide-svelte'
|
link: '/guide/packages/lucide-svelte',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Lucide Solid',
|
text: 'Lucide Solid',
|
||||||
link: '/guide/packages/lucide-solid'
|
link: '/guide/packages/lucide-solid',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Lucide Preact',
|
text: 'Lucide Preact',
|
||||||
link: '/guide/packages/lucide-preact'
|
link: '/guide/packages/lucide-preact',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Lucide Angular',
|
text: 'Lucide Angular',
|
||||||
link: '/guide/packages/lucide-angular'
|
link: '/guide/packages/lucide-angular',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Lucide Static',
|
text: 'Lucide Static',
|
||||||
link: '/guide/packages/lucide-static'
|
link: '/guide/packages/lucide-static',
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Contributing',
|
text: 'Contributing',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
text: 'Icon Design Principles',
|
text: 'Icon Design Principles',
|
||||||
link: '/guide/design/icon-design-guide'
|
link: '/guide/design/icon-design-guide',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Designing in Illustrator',
|
text: 'Designing in Illustrator',
|
||||||
link: '/guide/design/illustrator-guide'
|
link: '/guide/design/illustrator-guide',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Designing in InkScape',
|
text: 'Designing in InkScape',
|
||||||
link: '/guide/design/inkscape-guide'
|
link: '/guide/design/inkscape-guide',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Designing in Figma',
|
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
|
// This should be here to keep the sidebar shown on the icons page
|
||||||
'icons': [
|
icons: [{ text: '', link: '/' }],
|
||||||
{ text: '', link: '/' },
|
};
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
export default sidebar
|
export default sidebar;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import { useRouter } from 'vitepress';
|
import { useRouter } from 'vitepress';
|
||||||
|
|
||||||
const { go } = useRouter()
|
const { go } = useRouter()
|
||||||
@@ -8,7 +7,16 @@ const props = defineProps<{
|
|||||||
href?: string
|
href?: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const isExternal = computed(() => props.href?.startsWith('http') ?? false)
|
||||||
|
|
||||||
const component = computed(() => props.href ? 'a' : 'div')
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -16,7 +24,9 @@ const component = computed(() => props.href ? 'a' : 'div')
|
|||||||
:is="component"
|
:is="component"
|
||||||
:href="href"
|
:href="href"
|
||||||
class="badge"
|
class="badge"
|
||||||
@click="props?.href ? go(href) : undefined"
|
:target="target"
|
||||||
|
:rel="rel"
|
||||||
|
@click="onClick"
|
||||||
>
|
>
|
||||||
<slot/>
|
<slot/>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="grid">
|
<div class="card-grid-flex">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style>
|
||||||
.grid {
|
.card-grid-flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
@@ -15,20 +15,20 @@
|
|||||||
margin: -8px;
|
margin: -8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid > * {
|
.card-grid-flex > * {
|
||||||
flex-basis: 100%;
|
flex-basis: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 960px) {
|
@media (min-width: 960px) {
|
||||||
.grid > * {
|
.card-grid-flex > * {
|
||||||
flex-basis: 50%;
|
flex-basis: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1280px) {
|
@media (min-width: 1280px) {
|
||||||
.grid > * {
|
.card-grid-flex > * {
|
||||||
flex-basis: 33.33%;
|
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-size: 32px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 32px;
|
margin-bottom: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
margin-bottom: 96px;
|
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>
|
</style>
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
export default {
|
export default {
|
||||||
async load() {
|
async load() {
|
||||||
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest').then(res => {
|
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest')
|
||||||
|
.then((res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const releaseData = res.json() as Promise<{ tag_name: string }>
|
const releaseData = res.json() as Promise<{ tag_name: string }>;
|
||||||
|
|
||||||
return releaseData
|
return releaseData;
|
||||||
}
|
}
|
||||||
return null
|
return null;
|
||||||
}).then(res => res.tag_name)
|
})
|
||||||
|
.then((res) => res.tag_name);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
version
|
version,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import { data } from './HomeHeroBefore.data'
|
|||||||
<HomeContainer class="container">
|
<HomeContainer class="container">
|
||||||
<Badge
|
<Badge
|
||||||
:href="`https://github.com/lucide-icons/lucide/releases/tag/${data.version}`"
|
:href="`https://github.com/lucide-icons/lucide/releases/tag/${data.version}`"
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
>v{{ data.version }}</Badge>
|
>v{{ data.version }}</Badge>
|
||||||
</HomeContainer>
|
</HomeContainer>
|
||||||
</template>
|
</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 {
|
export default {
|
||||||
async load() {
|
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 {
|
return {
|
||||||
icons: randomIcons,
|
icons: randomIcons,
|
||||||
iconsCount: icons.length,
|
iconsCount: icons.length,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ function insert() {
|
|||||||
const replaceIndex = random(0, 48)
|
const replaceIndex = random(0, 48)
|
||||||
const newIcon = getRandomNewIcon()
|
const newIcon = getRandomNewIcon()
|
||||||
|
|
||||||
// items.value.splice(replaceIndex, 0, newIcon);
|
|
||||||
|
|
||||||
items.value[replaceIndex] = newIcon
|
items.value[replaceIndex] = newIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +74,6 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.card-wrapper {
|
.card-wrapper {
|
||||||
/* padding: 0 24px; */
|
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
margin-top: 48px;
|
margin-top: 48px;
|
||||||
@@ -87,13 +84,10 @@ onBeforeUnmount(() => {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height:100%;
|
height:100%;
|
||||||
/* box-shadow: var(--vp-shadow-2); */
|
|
||||||
max-height: 220px;
|
max-height: 220px;
|
||||||
max-width: 560px;
|
max-width: 560px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
/* max-height: 240px; */
|
|
||||||
/* margin-top: 96px; */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-grid {
|
.card-grid {
|
||||||
@@ -107,7 +101,6 @@ onBeforeUnmount(() => {
|
|||||||
max-width: 512px;
|
max-width: 512px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
/* white-space: nowrap; */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-enter-active {
|
.list-enter-active {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export default {
|
|||||||
logo: '/framework-logos/flutter.svg',
|
logo: '/framework-logos/flutter.svg',
|
||||||
label: 'Lucide documentation for Flutter',
|
label: 'Lucide documentation for Flutter',
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import HomeContainer from './HomeContainer.vue'
|
import HomeContainer from './HomeContainer.vue'
|
||||||
|
import HomeSectionTitle from './HomeSectionTitle.vue'
|
||||||
import { useRouter } from 'vitepress';
|
import { useRouter } from 'vitepress';
|
||||||
import { data } from './HomePackagesSection.data'
|
import { data } from './HomePackagesSection.data'
|
||||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||||
@@ -9,7 +10,7 @@ const { go } = useRouter()
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<HomeContainer>
|
<HomeContainer>
|
||||||
<h2 class="section-title">Available For:</h2>
|
<HomeSectionTitle>Available For:</HomeSectionTitle>
|
||||||
<div class="packages-list">
|
<div class="packages-list">
|
||||||
<a
|
<a
|
||||||
v-for="{ name, logo } in data.packages"
|
v-for="{ name, logo } in data.packages"
|
||||||
@@ -34,14 +35,6 @@ const { go } = useRouter()
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<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 {
|
.packages-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
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>
|
||||||
91
docs/.vitepress/theme/components/home/TeamMemberCard.vue
Normal file
91
docs/.vitepress/theme/components/home/TeamMemberCard.vue
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<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;
|
||||||
|
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 { getAllData } from '../../../lib/icons';
|
||||||
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
|
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
|
||||||
import iconsMetaData from '../../../data/iconMetaData'
|
import iconsMetaData from '../../../data/iconMetaData';
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async load() {
|
async load() {
|
||||||
let categories = getAllCategoryFiles()
|
let categories = getAllCategoryFiles();
|
||||||
|
|
||||||
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData))
|
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
categories,
|
categories,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
import { useData } from 'vitepress'
|
import { useData } from 'vitepress'
|
||||||
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
|
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
|
||||||
import { isActive } from 'vitepress/dist/client/shared'
|
import { isActive } from 'vitepress/dist/client/shared'
|
||||||
import { useActiveAnchor } from '../../composables/useActiveAnchor'
|
import { useActiveAnchor } from '../../composables/useActiveAnchor'
|
||||||
import { data } from './CategoryList.data'
|
import { data } from './CategoryList.data'
|
||||||
import CategoryListItem from './CategoryListItem.vue'
|
import CategoryListItem from './CategoryListItem.vue'
|
||||||
|
import SidebarTitle from './SidebarTitle.vue'
|
||||||
|
import { useCategoryView } from '../../composables/useCategoryView'
|
||||||
|
|
||||||
const { page } = useData()
|
const { page } = useData()
|
||||||
|
const { categoryCounts } = useCategoryView();
|
||||||
|
|
||||||
const categoriesIsActive = computed(() => {
|
const categoriesIsActive = computed(() => {
|
||||||
return isActive(page.value.relativePath, '/icons/categories');
|
return isActive(page.value.relativePath, '/icons/categories');
|
||||||
@@ -25,22 +28,32 @@ const headers = computed(() => {
|
|||||||
level: 2,
|
level: 2,
|
||||||
link: `${linkPrefix}#${name}`,
|
link: `${linkPrefix}#${name}`,
|
||||||
title,
|
title,
|
||||||
iconCount
|
iconCount: categoryCounts.value[name] ?? iconCount,
|
||||||
|
name
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
const container = ref()
|
const container = ref()
|
||||||
const marker = ref()
|
const marker = ref()
|
||||||
|
|
||||||
useActiveAnchor(container, marker)
|
const { setActiveLinkDebounced } = useActiveAnchor(container, marker)
|
||||||
|
|
||||||
|
watch(headers, () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setActiveLinkDebounced()
|
||||||
|
}, 200)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="category-list" ref="container">
|
<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
|
All
|
||||||
</VPLink>
|
</VPLink>
|
||||||
<VPLink class="sidebar-title" href="/icons/categories" :class="{ 'active': categoriesIsActive } ">
|
<VPLink class="sidebar-link sidebar-text" href="/icons/categories" :class="{ 'active': categoriesIsActive } ">
|
||||||
Categories
|
Categories
|
||||||
</VPLink>
|
</VPLink>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@@ -53,17 +66,20 @@ useActiveAnchor(container, marker)
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.sidebar-title {
|
.sidebar-text {
|
||||||
font-weight: 500;
|
|
||||||
color: var(--vp-c-text-2);
|
|
||||||
margin-bottom: 6px;
|
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
display: block;
|
display: block;
|
||||||
transition: color 0.25s;
|
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);
|
color: var(--vp-c-brand);
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ interface Header {
|
|||||||
slug: string;
|
slug: string;
|
||||||
iconCount: number;
|
iconCount: number;
|
||||||
link: string;
|
link: string;
|
||||||
|
name: string;
|
||||||
children: Header[];
|
children: Header[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,36 +15,35 @@ type MenuItem = Omit<Header, 'slug' | 'children'> & {
|
|||||||
children?: MenuItem[];
|
children?: MenuItem[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
headers: MenuItem[];
|
headers: MenuItem[];
|
||||||
root?: boolean;
|
root?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { selectedCategory } = useCategoryView();
|
const { selectedCategory } = useCategoryView();
|
||||||
|
|
||||||
function onClick(event: Event) {
|
function onClick(categoryName: string) {
|
||||||
const target =
|
selectedCategory.value = categoryName;
|
||||||
(event.target as HTMLElement).nodeName === 'span'
|
|
||||||
? (event.target as HTMLElement).parentNode
|
|
||||||
: (event.target as HTMLElement);
|
|
||||||
const href = (target as HTMLAnchorElement)?.href;
|
|
||||||
|
|
||||||
if (href) {
|
const heading = document.querySelector<HTMLAnchorElement>(categoryName);
|
||||||
const id = '#' + href.split('#')[1];
|
|
||||||
const decodedId = decodeURIComponent(id);
|
|
||||||
|
|
||||||
selectedCategory.value = decodedId.replace('#', '');
|
|
||||||
|
|
||||||
const heading = document.querySelector<HTMLAnchorElement>(decodedId);
|
|
||||||
heading?.focus();
|
heading?.focus();
|
||||||
}
|
|
||||||
|
window.history.pushState({}, '', `/icons/categories#${categoryName}`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ul :class="root ? 'root' : 'nested'">
|
<ul :class="root ? 'root' : 'nested'">
|
||||||
<li v-for="{ children, link, title, iconCount } in headers">
|
<li v-for="{ children, link, title, iconCount, name } in headers">
|
||||||
<a class="outline-link" :href="link" @click="onClick" :title="title">
|
<a
|
||||||
|
class="outline-link"
|
||||||
|
:href="link"
|
||||||
|
@click="onClick(name)"
|
||||||
|
:title="title"
|
||||||
|
:class="{
|
||||||
|
inactive: iconCount === 0,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<span>
|
<span>
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</span>
|
</span>
|
||||||
@@ -83,6 +83,10 @@ function onClick(event: Event) {
|
|||||||
transition: color 0.25s;
|
transition: color 0.25s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.outline-link.inactive {
|
||||||
|
color: var(--vp-c-text-4);
|
||||||
|
}
|
||||||
|
|
||||||
.outline-link.nested {
|
.outline-link.nested {
|
||||||
padding-left: 13px;
|
padding-left: 13px;
|
||||||
}
|
}
|
||||||
@@ -93,4 +97,8 @@ function onClick(event: Event) {
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.outline-link.inactive .icon-count {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -39,6 +39,12 @@ function copyJSX() {
|
|||||||
navigator.clipboard.writeText(code)
|
navigator.clipboard.writeText(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyComponentName() {
|
||||||
|
const code = componentName.value
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(code)
|
||||||
|
}
|
||||||
|
|
||||||
function copyVue() {
|
function copyVue() {
|
||||||
let attrs = ['']
|
let attrs = ['']
|
||||||
|
|
||||||
@@ -101,6 +107,7 @@ function copyAngular() {
|
|||||||
:popoverPosition="popoverPosition"
|
:popoverPosition="popoverPosition"
|
||||||
:options="[
|
:options="[
|
||||||
{ text: 'Copy JSX' , onClick: copyJSX },
|
{ text: 'Copy JSX' , onClick: copyJSX },
|
||||||
|
{ text: 'Copy Component Name' , onClick: copyComponentName },
|
||||||
{ text: 'Copy Vue' , onClick: copyVue },
|
{ text: 'Copy Vue' , onClick: copyVue },
|
||||||
{ text: 'Copy Svelte' , onClick: copyJSX },
|
{ text: 'Copy Svelte' , onClick: copyJSX },
|
||||||
{ text: 'Copy Angular' , onClick: copyAngular },
|
{ text: 'Copy Angular' , onClick: copyAngular },
|
||||||
|
|||||||
@@ -11,15 +11,32 @@ import IconInfo from './IconInfo.vue';
|
|||||||
import Badge from '../base/Badge.vue';
|
import Badge from '../base/Badge.vue';
|
||||||
import { computedAsync } from '@vueuse/core';
|
import { computedAsync } from '@vueuse/core';
|
||||||
import { satisfies } from 'semver';
|
import { satisfies } from 'semver';
|
||||||
|
import { useExternalLibs } from '../../composables/useExternalLibs';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
iconName: string
|
iconName: string | null
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const { externalIconNodes } = useExternalLibs()
|
||||||
|
|
||||||
|
const { go } = useRouter()
|
||||||
|
|
||||||
const icon = computedAsync<IconEntity | null>(async () => {
|
const icon = computedAsync<IconEntity | null>(async () => {
|
||||||
if (props.iconName) {
|
if (props.iconName) {
|
||||||
|
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
|
return (await import(`../../../data/iconDetails/${props.iconName}.ts`)).default as IconEntity
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (!props.iconName.includes(':')) {
|
||||||
|
go(`/icons/${props.iconName}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
}, null)
|
}, null)
|
||||||
|
|
||||||
@@ -36,8 +53,6 @@ function onClose() {
|
|||||||
emit('close')
|
emit('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
const { go } = useRouter()
|
|
||||||
|
|
||||||
const CloseIcon = createLucideIcon('Close', x)
|
const CloseIcon = createLucideIcon('Close', x)
|
||||||
const Expand = createLucideIcon('Expand', expand)
|
const Expand = createLucideIcon('Expand', expand)
|
||||||
</script>
|
</script>
|
||||||
@@ -51,10 +66,8 @@ const Expand = createLucideIcon('Expand', expand)
|
|||||||
v-if="icon.createdRelease"
|
v-if="icon.createdRelease"
|
||||||
class="version"
|
class="version"
|
||||||
:href="releaseTagLink(icon.createdRelease.version)"
|
:href="releaseTagLink(icon.createdRelease.version)"
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
>v{{ icon.createdRelease.version }}</Badge>
|
>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" />
|
<component :is="Expand" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton @click="onClose">
|
<IconButton @click="onClose">
|
||||||
@@ -144,11 +157,11 @@ const Expand = createLucideIcon('Expand', expand)
|
|||||||
}
|
}
|
||||||
|
|
||||||
.drawer-enter-active {
|
.drawer-enter-active {
|
||||||
transition: all 0.2s cubic-bezier(.21,.8,.46,.9);
|
transition: opacity 0.5s, transform 0.25s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer-leave-active {
|
.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,
|
.drawer-enter-from,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import IconItem from './IconItem.vue'
|
|||||||
|
|
||||||
const emit = defineEmits(['setActiveIcon'])
|
const emit = defineEmits(['setActiveIcon'])
|
||||||
|
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
icons: IconEntity[]
|
icons: IconEntity[]
|
||||||
activeIcon?: string
|
activeIcon?: string
|
||||||
overlayMode?: boolean
|
overlayMode?: boolean
|
||||||
@@ -25,8 +25,10 @@ function setActiveIcon(name: string) {
|
|||||||
:key="icon.name"
|
:key="icon.name"
|
||||||
>
|
>
|
||||||
<IconItem
|
<IconItem
|
||||||
v-bind="icon"
|
:iconNode="icon.iconNode"
|
||||||
@setActiveIcon="setActiveIcon(icon.name)"
|
:name="icon.name"
|
||||||
|
:externalLibrary="icon.externalLibrary"
|
||||||
|
@setActiveIcon="setActiveIcon"
|
||||||
:active="activeIcon === icon.name"
|
:active="activeIcon === icon.name"
|
||||||
customizable
|
customizable
|
||||||
:overlayMode="overlayMode"
|
:overlayMode="overlayMode"
|
||||||
@@ -40,7 +42,6 @@ function setActiveIcon(name: string) {
|
|||||||
.icons {
|
.icons {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
|
||||||
/* padding: 32px 32px 96px; */
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import CopyCodeButton from './CopyCodeButton.vue';
|
|||||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||||
import {useData, useRouter} from 'vitepress';
|
import {useData, useRouter} from 'vitepress';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||||
|
import { diamond } from '../../../data/iconNodes'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
icon: IconEntity
|
icon: IconEntity
|
||||||
@@ -20,13 +22,21 @@ const tags = computed(() => {
|
|||||||
if (!props.icon || !props?.icon?.tags) return []
|
if (!props.icon || !props?.icon?.tags) return []
|
||||||
return props.icon.tags.join(' • ')
|
return props.icon.tags.join(' • ')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const DiamondIcon = createLucideIcon('Diamond', diamond)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="icon-info">
|
<div class="icon-info">
|
||||||
|
<div class="icon-name-wrapper">
|
||||||
<IconDetailName class="icon-name">
|
<IconDetailName class="icon-name">
|
||||||
{{ icon.name }}
|
{{ icon.name }}
|
||||||
</IconDetailName>
|
</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">
|
<div class="tags-scroller" v-if="tags.length">
|
||||||
<p class="icon-tags horizontal-scroller">
|
<p class="icon-tags horizontal-scroller">
|
||||||
{{ tags }}
|
{{ tags }}
|
||||||
@@ -44,10 +54,10 @@ const tags = computed(() => {
|
|||||||
|
|
||||||
<div class="group buttons">
|
<div class="group buttons">
|
||||||
<VPButton
|
<VPButton
|
||||||
v-if="!page?.relativePath?.startsWith?.(`icons/${icon.name}`)"
|
v-if="!page?.relativePath?.startsWith?.(icon.externalLibrary ? `icons/${icon.externalLibrary}/${icon.name}`: `icons/${icon.name}`)"
|
||||||
:href="`/icons/${icon.name}`"
|
:href="icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}`: `/icons/${icon.name}`"
|
||||||
text="See in action"
|
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"/>
|
<CopySVGButton :name="icon.name" :popoverPosition="popoverPosition"/>
|
||||||
<CopyCodeButton :name="icon.name" :popoverPosition="popoverPosition"/>
|
<CopyCodeButton :name="icon.name" :popoverPosition="popoverPosition"/>
|
||||||
@@ -67,9 +77,27 @@ const tags = computed(() => {
|
|||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
.icon-name {
|
.icon-name {
|
||||||
|
margin-right: -36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-name-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
margin-bottom: 4px;
|
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 {
|
.icon-tags {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: var(--vp-c-text-2);
|
color: var(--vp-c-text-2);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useRouter } from 'vitepress';
|
|||||||
import getSVGIcon from '../../utils/getSVGIcon';
|
import getSVGIcon from '../../utils/getSVGIcon';
|
||||||
import useConfetti from '../../composables/useConfetti';
|
import useConfetti from '../../composables/useConfetti';
|
||||||
import Tooltip from '../base/Tooltip.vue';
|
import Tooltip from '../base/Tooltip.vue';
|
||||||
|
import { diamond } from '../../../data/iconNodes'
|
||||||
|
|
||||||
const downloadText = 'Download!'
|
const downloadText = 'Download!'
|
||||||
const copiedText = 'Copied!'
|
const copiedText = 'Copied!'
|
||||||
@@ -16,6 +17,7 @@ const props = defineProps<{
|
|||||||
name: string;
|
name: string;
|
||||||
iconNode: IconNode;
|
iconNode: IconNode;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
|
externalLibrary?: string;
|
||||||
customizable?: boolean;
|
customizable?: boolean;
|
||||||
overlayMode?: boolean
|
overlayMode?: boolean
|
||||||
hideIcon?: boolean
|
hideIcon?: boolean
|
||||||
@@ -33,8 +35,9 @@ const icon = computed(() => {
|
|||||||
return createLucideIcon(props.name, props.iconNode)
|
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) {
|
if (event.shiftKey) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const svgString = getSVGIcon(event.target.firstChild, {
|
const svgString = getSVGIcon(event.target.firstChild, {
|
||||||
@@ -50,14 +53,16 @@ async function navigateToIcon(event) {
|
|||||||
|
|
||||||
if(props.overlayMode && showOverlay.value) {
|
if(props.overlayMode && showOverlay.value) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
window.history.pushState({}, '', `/icons/${props.name}`)
|
|
||||||
emit('setActiveIcon', props.name)
|
window.history.pushState({}, '', props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`)
|
||||||
}
|
emit('setActiveIcon', props.externalLibrary ? `${props.externalLibrary}:${props.name}`: props.name)
|
||||||
else {
|
} else {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
go(`/icons/${props.name}`)
|
go(props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DiamondIcon = createLucideIcon('Diamond', diamond)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -67,7 +72,7 @@ async function navigateToIcon(event) {
|
|||||||
@click="navigateToIcon"
|
@click="navigateToIcon"
|
||||||
:class="{ active, animate }"
|
:class="{ active, animate }"
|
||||||
:aria-label="name"
|
:aria-label="name"
|
||||||
:href="`/icons/${props.name}`"
|
|
||||||
:data-confetti-text="confettiText"
|
:data-confetti-text="confettiText"
|
||||||
ref="ref"
|
ref="ref"
|
||||||
>
|
>
|
||||||
@@ -81,6 +86,13 @@ async function navigateToIcon(event) {
|
|||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
</KeepAlive>
|
</KeepAlive>
|
||||||
|
<div
|
||||||
|
v-if="externalLibrary"
|
||||||
|
class="floating-diamond"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<DiamondIcon fill="currentColor" :size="8"/>
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</template>
|
</template>
|
||||||
@@ -89,6 +101,7 @@ async function navigateToIcon(event) {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.icon-button {
|
.icon-button {
|
||||||
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -105,6 +118,13 @@ async function navigateToIcon(event) {
|
|||||||
color: var(--vp-c-text-1);
|
color: var(--vp-c-text-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.floating-diamond {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 4px;
|
||||||
|
color: var(--vp-c-brand);
|
||||||
|
}
|
||||||
|
|
||||||
.confetti-button:before,
|
.confetti-button:before,
|
||||||
.confetti-button:after {
|
.confetti-button:after {
|
||||||
z-index: 100;
|
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">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
|
||||||
import { Category } from '../../types';
|
|
||||||
import IconGrid from './IconGrid.vue'
|
import IconGrid from './IconGrid.vue'
|
||||||
import { vIntersectionObserver } from '@vueuse/components'
|
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
activeIconName: string
|
activeIconName: string | null
|
||||||
category: Category
|
categoryRow: CategoryRow
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|
||||||
const emit = defineEmits(['setActiveIcon'])
|
const emit = defineEmits(['setActiveIcon'])
|
||||||
|
|
||||||
const showIcons = ref(false)
|
|
||||||
|
|
||||||
// Added intersection observer to improve performance
|
|
||||||
const onIntersectionObserver: IntersectionObserverCallback = ([{ isIntersecting }]) => {
|
|
||||||
showIcons.value = isIntersecting
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section
|
<h2
|
||||||
class="category"
|
v-if="categoryRow.type === 'category'"
|
||||||
:key="category.name"
|
class="title"
|
||||||
:id="category.name"
|
|
||||||
v-intersection-observer="onIntersectionObserver"
|
|
||||||
>
|
>
|
||||||
<h2 class="title" >
|
<a class="header-anchor" :href="`#${categoryRow.name}`" :aria-label="`Permalink to "${categoryRow.title}"`">​</a>
|
||||||
<a class="header-anchor" :href="`#${category.name}`" :aria-label="`Permalink to "${category.title}"`">​</a>
|
{{ categoryRow.title }}
|
||||||
{{ category.title }}
|
|
||||||
</h2>
|
</h2>
|
||||||
<IconGrid
|
<IconGrid
|
||||||
|
v-else-if="categoryRow.type === 'icons'"
|
||||||
:activeIcon="activeIconName"
|
:activeIcon="activeIconName"
|
||||||
:icons="category.icons"
|
:icons="categoryRow.icons"
|
||||||
@setActiveIcon="$event => $emit('setActiveIcon', $event)"
|
@setActiveIcon="$event => $emit('setActiveIcon', $event)"
|
||||||
overlayMode
|
overlayMode
|
||||||
:hideIcons="!showIcons"
|
|
||||||
/>
|
/>
|
||||||
</section>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.title {
|
.title {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 8px;
|
||||||
font-size: 19px;
|
font-size: 19px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
padding-top: 86px;
|
padding: 24px 0 8px;
|
||||||
/* scroll-padding-top: 240px; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.category {
|
|
||||||
margin-bottom: calc(-86px + 32px);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<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 type { IconEntity, Category } from '../../types';
|
||||||
import useSearch from '../../composables/useSearch';
|
import useSearch from '../../composables/useSearch';
|
||||||
import InputSearch from '../base/InputSearch.vue';
|
import InputSearch from '../base/InputSearch.vue';
|
||||||
@@ -8,6 +8,14 @@ import StickyBar from './StickyBar.vue';
|
|||||||
import IconsCategory from './IconsCategory.vue';
|
import IconsCategory from './IconsCategory.vue';
|
||||||
import useFetchTags from '../../composables/useFetchTags';
|
import useFetchTags from '../../composables/useFetchTags';
|
||||||
import useFetchCategories from '../../composables/useFetchCategories';
|
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<{
|
const props = defineProps<{
|
||||||
icons: IconEntity[];
|
icons: IconEntity[];
|
||||||
@@ -17,7 +25,6 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const activeIconName = ref(null);
|
const activeIconName = ref(null);
|
||||||
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
||||||
|
|
||||||
const isSearching = computed(() => !!searchQuery.value);
|
const isSearching = computed(() => !!searchQuery.value);
|
||||||
|
|
||||||
function setActiveIconName(name: string) {
|
function setActiveIconName(name: string) {
|
||||||
@@ -27,6 +34,13 @@ function setActiveIconName(name: string) {
|
|||||||
const { execute: fetchTags, data: tags } = useFetchTags();
|
const { execute: fetchTags, data: tags } = useFetchTags();
|
||||||
const { execute: fetchCategories, data: categoriesMap } = useFetchCategories();
|
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(() => {
|
const mappedIcons = computed(() => {
|
||||||
if (tags.value == null) {
|
if (tags.value == null) {
|
||||||
return props.icons;
|
return props.icons;
|
||||||
@@ -43,9 +57,10 @@ const mappedIcons = computed(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const searchResults = useSearch(searchQuery, mappedIcons, [
|
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
|
||||||
{ name: 'name', weight: 2 },
|
{ name: 'name', weight: 3 },
|
||||||
{ name: 'tags', weight: 1 },
|
{ name: 'aliases', weight: 3 },
|
||||||
|
{ name: 'tags', weight: 2 },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const categories = computed(() => {
|
const categories = computed(() => {
|
||||||
@@ -54,7 +69,7 @@ const categories = computed(() => {
|
|||||||
return props.categories
|
return props.categories
|
||||||
.map(({ name, title }) => {
|
.map(({ name, title }) => {
|
||||||
const categoryIcons = props.icons.filter((icon) => {
|
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);
|
return iconCategories?.includes(name);
|
||||||
});
|
});
|
||||||
@@ -71,9 +86,43 @@ const categories = computed(() => {
|
|||||||
icons: searchedCategoryIcons,
|
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() {
|
function onFocusSearchInput() {
|
||||||
if (tags.value == null) {
|
if (tags.value == null) {
|
||||||
fetchTags();
|
fetchTags();
|
||||||
@@ -84,12 +133,24 @@ function onFocusSearchInput() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
||||||
|
|
||||||
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<StickyBar class="search-bar category-search">
|
<div ref="overviewEl" class="overview-container">
|
||||||
|
<StickyBar class="category-search">
|
||||||
<InputSearch
|
<InputSearch
|
||||||
:placeholder="`Search ${icons.length} icons ...`"
|
:placeholder="`Search ${icons.length} icons ...`"
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
@@ -98,19 +159,28 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
|
|||||||
@focus="onFocusSearchInput"
|
@focus="onFocusSearchInput"
|
||||||
/>
|
/>
|
||||||
</StickyBar>
|
</StickyBar>
|
||||||
<NoResults v-if="categories.length === 0" :searchQuery="searchQuery" @clear="searchQuery = ''" />
|
<NoResults
|
||||||
|
v-if="categories.length === 0"
|
||||||
|
:searchQuery="searchQuery"
|
||||||
|
@clear="searchQuery = ''"
|
||||||
|
/>
|
||||||
|
<div v-bind="wrapperProps">
|
||||||
<IconsCategory
|
<IconsCategory
|
||||||
v-for="category in categories"
|
v-for="{ index, data } in list"
|
||||||
:key="category.name"
|
:categoryRow="data"
|
||||||
:category="category"
|
|
||||||
:activeIconName="activeIconName"
|
:activeIconName="activeIconName"
|
||||||
@setActiveIcon="setActiveIconName"
|
@setActiveIcon="setActiveIconName"
|
||||||
|
:key="index"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<IconDetailOverlay
|
<IconDetailOverlay
|
||||||
v-if="activeIconName != null"
|
v-if="activeIconName != null"
|
||||||
:iconName="activeIconName"
|
:iconName="activeIconName"
|
||||||
@close="setActiveIconName('')"
|
@close="handleCloseDrawer"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<CarbonAdOverlay :drawerOpen="!!activeIconName" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -119,6 +189,21 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search-bar.category-search {
|
.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>
|
</style>
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
<script setup lang="ts">
|
<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 type { IconEntity } from '../../types';
|
||||||
import { useMediaQuery, useOffsetPagination } from '@vueuse/core';
|
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
||||||
import IconGrid from './IconGrid.vue';
|
import IconGrid from './IconGrid.vue';
|
||||||
import InputSearch from '../base/InputSearch.vue';
|
import InputSearch from '../base/InputSearch.vue';
|
||||||
import useSearch from '../../composables/useSearch';
|
import useSearch from '../../composables/useSearch';
|
||||||
import EndOfPage from '../base/EndOfPage.vue';
|
|
||||||
import useSearchInput from '../../composables/useSearchInput';
|
import useSearchInput from '../../composables/useSearchInput';
|
||||||
import StickyBar from './StickyBar.vue';
|
import StickyBar from './StickyBar.vue';
|
||||||
import useFetchTags from '../../composables/useFetchTags';
|
import useFetchTags from '../../composables/useFetchTags';
|
||||||
import useFetchCategories from '../../composables/useFetchCategories';
|
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<{
|
const props = defineProps<{
|
||||||
icons: IconEntity[];
|
icons: IconEntity[];
|
||||||
@@ -17,32 +21,16 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const activeIconName = ref(null);
|
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: fetchTags, data: tags } = useFetchTags();
|
||||||
const { execute: fetchCategories, data: categories } = useFetchCategories();
|
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(() => {
|
const mappedIcons = computed(() => {
|
||||||
if (tags.value == null) {
|
if (tags.value == null) {
|
||||||
return props.icons;
|
return props.icons;
|
||||||
@@ -63,26 +51,33 @@ const mappedIcons = computed(() => {
|
|||||||
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
||||||
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
|
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
|
||||||
{ name: 'name', weight: 3 },
|
{ name: 'name', weight: 3 },
|
||||||
|
{ name: 'aliases', weight: 3 },
|
||||||
{ name: 'tags', weight: 2 },
|
{ name: 'tags', weight: 2 },
|
||||||
{ name: 'categories', weight: 1 },
|
{ name: 'categories', weight: 1 },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { next, currentPage } = useOffsetPagination({ pageSize });
|
const chunkedIcons = computed(() => {
|
||||||
|
return chunkArray(searchResults.value, columnSize.value);
|
||||||
const paginatedIcons = computed(() => {
|
|
||||||
const end = pageSize.value * currentPage.value;
|
|
||||||
|
|
||||||
return searchResults.value.slice(0, end);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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) {
|
function setActiveIconName(name: string) {
|
||||||
activeIconName.value = name;
|
activeIconName.value = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(searchQueryDebounced, (searchString) => {
|
|
||||||
currentPage.value = 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
function onFocusSearchInput() {
|
function onFocusSearchInput() {
|
||||||
if (tags.value == null) {
|
if (tags.value == null) {
|
||||||
fetchTags();
|
fetchTags();
|
||||||
@@ -95,9 +90,20 @@ function onFocusSearchInput() {
|
|||||||
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
||||||
|
|
||||||
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
|
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
|
||||||
|
|
||||||
|
watch(searchQueryDebounced, () => {
|
||||||
|
scrollTo(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleCloseDrawer() {
|
||||||
|
setActiveIconName('');
|
||||||
|
|
||||||
|
window.history.pushState({}, '', '/icons/');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div ref="overviewEl" class="overview-container">
|
||||||
<StickyBar>
|
<StickyBar>
|
||||||
<InputSearch
|
<InputSearch
|
||||||
:placeholder="`Search ${icons.length} icons ...`"
|
:placeholder="`Search ${icons.length} icons ...`"
|
||||||
@@ -108,30 +114,33 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
|
|||||||
/>
|
/>
|
||||||
</StickyBar>
|
</StickyBar>
|
||||||
<NoResults
|
<NoResults
|
||||||
v-if="paginatedIcons.length === 0"
|
v-if="list.length === 0"
|
||||||
:searchQuery="searchQuery"
|
:searchQuery="searchQuery"
|
||||||
@clear="searchQuery = ''"
|
@clear="searchQuery = ''"
|
||||||
/>
|
/>
|
||||||
|
<div v-bind="wrapperProps" class="icon">
|
||||||
<IconGrid
|
<IconGrid
|
||||||
|
v-for="{ index, data: icons } in list"
|
||||||
|
:key="index"
|
||||||
overlayMode
|
overlayMode
|
||||||
|
:icons="icons"
|
||||||
:activeIcon="activeIconName"
|
:activeIcon="activeIconName"
|
||||||
:icons="paginatedIcons"
|
|
||||||
@setActiveIcon="setActiveIconName"
|
@setActiveIcon="setActiveIconName"
|
||||||
/>
|
/>
|
||||||
<EndOfPage @end-of-page="next" class="bottom-page" />
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<IconDetailOverlay
|
<IconDetailOverlay
|
||||||
v-if="activeIconName != null"
|
|
||||||
:iconName="activeIconName"
|
:iconName="activeIconName"
|
||||||
@close="setActiveIconName('')"
|
@close="handleCloseDrawer"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<CarbonAdOverlay :drawerOpen="!!activeIconName" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.icons {
|
.icons {
|
||||||
display: grid;
|
margin-bottom: 8px;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
|
|
||||||
gap: 8px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
@@ -142,7 +151,7 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-page {
|
.overview-container {
|
||||||
height: 288px;
|
padding-bottom: 288px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref} from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import {bird} from '../../../data/iconNodes'
|
import { bird, squirrel, rabbit } from '../../../data/iconNodes'
|
||||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
||||||
import {useEventListener} from '@vueuse/core'
|
import {useEventListener} from '@vueuse/core'
|
||||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
|
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
|
||||||
|
import { IconNode } from '../../types'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
searchQuery: string
|
searchQuery: string
|
||||||
@@ -11,29 +12,45 @@ defineProps<{
|
|||||||
|
|
||||||
defineEmits(['clear'])
|
defineEmits(['clear'])
|
||||||
|
|
||||||
const birdIcon = ref<HTMLElement>()
|
const animalIcon = ref<HTMLElement>()
|
||||||
const Bird = createLucideIcon('bird', bird)
|
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)
|
const flip = ref(false)
|
||||||
|
|
||||||
useEventListener(document, 'mousemove', (mouseEvent) => {
|
onMounted(() => {
|
||||||
const {width, height, x, y} = birdIcon.value.getBoundingClientRect()
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="no-results">
|
<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">
|
<h2 class="no-results-text">
|
||||||
No icons found for '{{ searchQuery }}'
|
No icons found for '{{ searchQuery }}'
|
||||||
</h2>
|
</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>
|
<span class="text-divider">or</span>
|
||||||
<VPButton text="Search on Github issues"
|
<VPButton
|
||||||
|
text="Search on Github issues"
|
||||||
theme="alt"
|
theme="alt"
|
||||||
:href="`https://github.com/lucide-icons/lucide/issues?q=is%3Aopen+${searchQuery}`"
|
:href="`https://github.com/lucide-icons/lucide/issues?q=is%3Aopen+${searchQuery}`"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -48,7 +65,7 @@ useEventListener(document, 'mousemove', (mouseEvent) => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bird-icon {
|
.animal-icon {
|
||||||
width: 160px;
|
width: 160px;
|
||||||
height: 160px;
|
height: 160px;
|
||||||
color: var(--vp-c-neutral);
|
color: var(--vp-c-neutral);
|
||||||
@@ -56,12 +73,12 @@ useEventListener(document, 'mousemove', (mouseEvent) => {
|
|||||||
margin-top: 72px;
|
margin-top: 72px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bird-icon.flip {
|
.animal-icon.flip {
|
||||||
transform: rotateY(180deg);
|
transform: rotateY(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 960px) {
|
@media (min-width: 960px) {
|
||||||
.bird-icon {
|
.animal-icon {
|
||||||
width: 240px;
|
width: 240px;
|
||||||
height: 240px;
|
height: 240px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Checkbox from '../base/Checkbox.vue'
|
||||||
|
import SidebarTitle from './SidebarTitle.vue'
|
||||||
|
import { useExternalLibs } from '../../composables/useExternalLibs';
|
||||||
|
import { ExternalLibs } from '../../types';
|
||||||
|
|
||||||
|
interface ExternalLibrary {
|
||||||
|
name: string;
|
||||||
|
value: ExternalLibs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const externalLibraries: ExternalLibrary[] = [
|
||||||
|
{
|
||||||
|
name: 'Lab',
|
||||||
|
value: 'lab'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { selectedLibs } = useExternalLibs();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="external-library-select">
|
||||||
|
<SidebarTitle>
|
||||||
|
Include external libs
|
||||||
|
</SidebarTitle>
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
v-for="library in externalLibraries"
|
||||||
|
:key="library.name"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
:label="library.name"
|
||||||
|
:id="library.name"
|
||||||
|
v-model="selectedLibs"
|
||||||
|
:value="library.value"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.external-library-select {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
19
docs/.vitepress/theme/components/icons/SidebarTitle.vue
Normal file
19
docs/.vitepress/theme/components/icons/SidebarTitle.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<h2 class="sidebar-title sidebar-text">
|
||||||
|
<slot />
|
||||||
|
</h2>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.sidebar-title {
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
}
|
||||||
|
.sidebar-text {
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
display: block;
|
||||||
|
transition: color 0.25s;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
.confetti-button:before,
|
.confetti-button:before,
|
||||||
.confetti-button:after {
|
.confetti-button:after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: "";
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
width: 140%;
|
width: 140%;
|
||||||
max-width: 160px;
|
max-width: 160px;
|
||||||
@@ -41,8 +41,16 @@
|
|||||||
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
||||||
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
||||||
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%);
|
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%);
|
||||||
background-size: 10% 10%, 20% 20%, 15% 15%, 20% 20%, 18% 18%, 10% 10%, 15% 15%,
|
background-size:
|
||||||
10% 10%, 18% 18%;
|
10% 10%,
|
||||||
|
20% 20%,
|
||||||
|
15% 15%,
|
||||||
|
20% 20%,
|
||||||
|
18% 18%,
|
||||||
|
10% 10%,
|
||||||
|
15% 15%,
|
||||||
|
10% 10%,
|
||||||
|
18% 18%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confetti-button:after {
|
.confetti-button:after {
|
||||||
@@ -55,7 +63,14 @@
|
|||||||
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
||||||
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
||||||
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%);
|
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%);
|
||||||
background-size: 15% 15%, 20% 20%, 18% 18%, 20% 20%, 15% 15%, 10% 10%, 20% 20%;
|
background-size:
|
||||||
|
15% 15%,
|
||||||
|
20% 20%,
|
||||||
|
18% 18%,
|
||||||
|
20% 20%,
|
||||||
|
15% 15%,
|
||||||
|
10% 10%,
|
||||||
|
20% 20%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confetti-button.animate:before {
|
.confetti-button.animate:before {
|
||||||
@@ -70,35 +85,89 @@
|
|||||||
@keyframes topBubbles {
|
@keyframes topBubbles {
|
||||||
0% {
|
0% {
|
||||||
color: rgb(var(--text-color) / 0);
|
color: rgb(var(--text-color) / 0);
|
||||||
background-position: 5% 90%, 10% 90%, 10% 90%, 15% 90%, 25% 90%, 25% 90%,
|
background-position:
|
||||||
40% 90%, 55% 90%, 70% 90%;
|
5% 90%,
|
||||||
|
10% 90%,
|
||||||
|
10% 90%,
|
||||||
|
15% 90%,
|
||||||
|
25% 90%,
|
||||||
|
25% 90%,
|
||||||
|
40% 90%,
|
||||||
|
55% 90%,
|
||||||
|
70% 90%;
|
||||||
}
|
}
|
||||||
30% {
|
30% {
|
||||||
color: rgb(var(--text-color) / 1);
|
color: rgb(var(--text-color) / 1);
|
||||||
}
|
}
|
||||||
50% {
|
50% {
|
||||||
background-position: 0% 80%, 0% 20%, 10% 40%, 20% 0%, 30% 30%, 22% 50%,
|
background-position:
|
||||||
50% 50%, 65% 20%, 90% 30%;
|
0% 80%,
|
||||||
|
0% 20%,
|
||||||
|
10% 40%,
|
||||||
|
20% 0%,
|
||||||
|
30% 30%,
|
||||||
|
22% 50%,
|
||||||
|
50% 50%,
|
||||||
|
65% 20%,
|
||||||
|
90% 30%;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
background-position: 0% 70%, 0% 10%, 10% 30%, 20% -10%, 30% 20%, 22% 40%,
|
background-position:
|
||||||
50% 40%, 65% 10%, 90% 20%;
|
0% 70%,
|
||||||
background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%;
|
0% 10%,
|
||||||
|
10% 30%,
|
||||||
|
20% -10%,
|
||||||
|
30% 20%,
|
||||||
|
22% 40%,
|
||||||
|
50% 40%,
|
||||||
|
65% 10%,
|
||||||
|
90% 20%;
|
||||||
|
background-size:
|
||||||
|
0% 0%,
|
||||||
|
0% 0%,
|
||||||
|
0% 0%,
|
||||||
|
0% 0%,
|
||||||
|
0% 0%,
|
||||||
|
0% 0%;
|
||||||
color: rgb(var(--text-color) / 0);
|
color: rgb(var(--text-color) / 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@keyframes bottomBubbles {
|
@keyframes bottomBubbles {
|
||||||
0% {
|
0% {
|
||||||
background-position: 10% -10%, 30% 10%, 55% -10%, 70% -10%, 85% -10%,
|
background-position:
|
||||||
70% -10%, 70% 0%;
|
10% -10%,
|
||||||
|
30% 10%,
|
||||||
|
55% -10%,
|
||||||
|
70% -10%,
|
||||||
|
85% -10%,
|
||||||
|
70% -10%,
|
||||||
|
70% 0%;
|
||||||
}
|
}
|
||||||
50% {
|
50% {
|
||||||
background-position: 0% 80%, 20% 80%, 45% 60%, 60% 100%, 75% 70%, 95% 60%,
|
background-position:
|
||||||
|
0% 80%,
|
||||||
|
20% 80%,
|
||||||
|
45% 60%,
|
||||||
|
60% 100%,
|
||||||
|
75% 70%,
|
||||||
|
95% 60%,
|
||||||
105% 0%;
|
105% 0%;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
background-position: 0% 90%, 20% 90%, 45% 70%, 60% 110%, 75% 80%, 95% 70%,
|
background-position:
|
||||||
|
0% 90%,
|
||||||
|
20% 90%,
|
||||||
|
45% 70%,
|
||||||
|
60% 110%,
|
||||||
|
75% 80%,
|
||||||
|
95% 70%,
|
||||||
110% 10%;
|
110% 10%;
|
||||||
background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%;
|
background-size:
|
||||||
|
0% 0%,
|
||||||
|
0% 0%,
|
||||||
|
0% 0%,
|
||||||
|
0% 0%,
|
||||||
|
0% 0%,
|
||||||
|
0% 0%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const links = computed(() => [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Github',
|
text: 'Github',
|
||||||
href: `${githubLink.value}/issues`
|
href: `${githubLink.value}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Issues',
|
text: 'Issues',
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
import packageData from '../../../data/packageData.json';
|
import packageData from '../../../data/packageData.json';
|
||||||
import thirdPartyPackages from '../../../data/packageData.thirdParty.json';
|
import thirdPartyPackages from '../../../data/packageData.thirdParty.json';
|
||||||
import fetchPackages from "../../../lib/fetchPackages";
|
import fetchPackages from '../../../lib/fetchPackages';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async load() {
|
async load() {
|
||||||
const packages = await fetchPackages();
|
const packages = await fetchPackages();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
packages: packages
|
packages: packages
|
||||||
.filter(p => p.name in packageData)
|
.filter((p) => p?.name != null && p.name in packageData)
|
||||||
.map((pData) => ({
|
.map((pData) => ({
|
||||||
...pData,
|
...pData,
|
||||||
...packageData[pData.name],
|
...packageData[pData.name],
|
||||||
documentation: `/guide/packages/${pData.name}`,
|
documentation: `/guide/packages/${pData.name}`,
|
||||||
source: `https://github.com/lucide-icons/lucide/tree/main/packages/${pData.name}`,
|
source: `https://github.com/lucide-icons/lucide/tree/main/packages/${pData.name}`,
|
||||||
icon: `/framework-logos/${packageData[pData.name].icon}.svg`,
|
icon: `/framework-logos/${packageData[pData.name].icon}.svg`,
|
||||||
})).sort((a, b) => a.order - b.order),
|
}))
|
||||||
|
.sort((a, b) => a.order - b.order),
|
||||||
thirdPartyPackages,
|
thirdPartyPackages,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {data} from './PackageList.data'
|
import { data } from './PackageList.data'
|
||||||
import GridSection from '../base/GridSection.vue'
|
import GridSection from '../base/GridSection.vue'
|
||||||
import PackageListItem from "./PackageListItem.vue";</script>
|
import PackageListItem from "./PackageListItem.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<GridSection
|
<GridSection
|
||||||
@@ -38,33 +39,4 @@ import PackageListItem from "./PackageListItem.vue";</script>
|
|||||||
.package-group {
|
.package-group {
|
||||||
margin-bottom: 96px;
|
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>
|
</style>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const props = defineProps<{
|
|||||||
<img v-if="packageData.iconDark" :src="packageData.iconDark" alt="" class="package-icon dark" :class="packageData.iconClass" />
|
<img v-if="packageData.iconDark" :src="packageData.iconDark" alt="" class="package-icon dark" :class="packageData.iconClass" />
|
||||||
</div>
|
</div>
|
||||||
<div class="package-title">
|
<div class="package-title">
|
||||||
<h2 class="title">{{ props.packageData.name }}</h2>
|
<h2 class="title">{{ props.packageData?.name }}</h2>
|
||||||
<a v-for="shield in props.packageData.shields" :href="shield.href" class="package-shield" rel="noreferrer noopener">
|
<a v-for="shield in props.packageData.shields" :href="shield.href" class="package-shield" rel="noreferrer noopener">
|
||||||
<img :src="shield.src" :alt="shield.href" />
|
<img :src="shield.src" :alt="shield.href" />
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -40,33 +40,4 @@ import ShowcaseListItem from "./ShowcaseListItem.vue";
|
|||||||
.package-group {
|
.package-group {
|
||||||
margin-bottom: 96px;
|
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>
|
</style>
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import { onMounted, onUpdated, onUnmounted } from 'vue';
|
import { onMounted, onUpdated, onUnmounted } from 'vue';
|
||||||
import { throttleAndDebounce } from 'vitepress/dist/client/theme-default/support/utils'
|
import { throttleAndDebounce } from 'vitepress/dist/client/theme-default/support/utils';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This file is compied and adjusted from vitepress/dist/client/theme-default/composables/useActiveAnchor.ts
|
* This file is compied and adjusted from vitepress/dist/client/theme-default/composables/useActiveAnchor.ts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function useActiveAnchor(container, marker) {
|
export function useActiveAnchor(container, marker) {
|
||||||
const onScroll = throttleAndDebounce(setActiveLink, 100);
|
const setActiveLinkDebounced = throttleAndDebounce(setActiveLink, 100);
|
||||||
let prevActiveLink = null;
|
let prevActiveLink = null;
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
requestAnimationFrame(setActiveLink);
|
requestAnimationFrame(setActiveLink);
|
||||||
window.addEventListener('scroll', onScroll);
|
window.addEventListener('scroll', setActiveLinkDebounced);
|
||||||
});
|
});
|
||||||
onUpdated(() => {
|
onUpdated(() => {
|
||||||
// sidebar update means a route change
|
// sidebar update means a route change
|
||||||
activateLink(location.hash);
|
activateLink(location.hash);
|
||||||
});
|
});
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('scroll', onScroll);
|
window.removeEventListener('scroll', setActiveLinkDebounced);
|
||||||
});
|
});
|
||||||
function setActiveLink() {
|
function setActiveLink() {
|
||||||
const links = [].slice.call(container.value.querySelectorAll('.outline-link'));
|
const links = [].slice.call(container.value.querySelectorAll('.outline-link'));
|
||||||
@@ -59,15 +59,18 @@ export function useActiveAnchor(container, marker) {
|
|||||||
activeLink.classList.add('active');
|
activeLink.classList.add('active');
|
||||||
marker.value.style.top = activeLink.offsetTop + 5 + 'px';
|
marker.value.style.top = activeLink.offsetTop + 5 + 'px';
|
||||||
marker.value.style.opacity = '1';
|
marker.value.style.opacity = '1';
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
marker.value.style.top = '33px';
|
marker.value.style.top = '33px';
|
||||||
marker.value.style.opacity = '0';
|
marker.value.style.opacity = '0';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
setActiveLinkDebounced,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const PAGE_OFFSET = 64;
|
const PAGE_OFFSET = 128;
|
||||||
|
|
||||||
function getAnchorTop(anchor) {
|
function getAnchorTop(anchor) {
|
||||||
return anchor.parentElement.offsetTop - PAGE_OFFSET;
|
return anchor.parentElement.offsetTop - PAGE_OFFSET;
|
||||||
@@ -75,7 +78,7 @@ function getAnchorTop(anchor) {
|
|||||||
function isAnchorActive(index, anchor, nextAnchor) {
|
function isAnchorActive(index, anchor, nextAnchor) {
|
||||||
const scrollTop = window.scrollY;
|
const scrollTop = window.scrollY;
|
||||||
if (index === 0 && scrollTop === 0) {
|
if (index === 0 && scrollTop === 0) {
|
||||||
return [true, null];
|
return [true, anchor.hash];
|
||||||
}
|
}
|
||||||
if (scrollTop < getAnchorTop(anchor)) {
|
if (scrollTop < getAnchorTop(anchor)) {
|
||||||
return [false, null];
|
return [false, null];
|
||||||
|
|||||||
@@ -1,25 +1,38 @@
|
|||||||
import {
|
import { useRoute } from 'vitepress';
|
||||||
ref, inject, Ref
|
import { ref, inject, Ref, onMounted, watch } from 'vue';
|
||||||
} from 'vue';
|
|
||||||
|
|
||||||
export const CATEGORY_VIEW_CONTEXT = Symbol('categoryView');
|
export const CATEGORY_VIEW_CONTEXT = Symbol('categoryView');
|
||||||
|
|
||||||
interface CategoryViewContext {
|
interface CategoryViewContext {
|
||||||
selectedCategory: Ref<string>
|
selectedCategory: Ref<string>;
|
||||||
categoryCounts: Ref<Record<string, number>>
|
categoryCounts: Ref<Record<string, number>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const categoryViewContext = {
|
export const categoryViewContext = {
|
||||||
selectedCategory: ref(''),
|
selectedCategory: ref(),
|
||||||
categoryCounts: ref({}),
|
categoryCounts: ref({}),
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useCategoryView(): CategoryViewContext {
|
export function useCategoryView(): CategoryViewContext {
|
||||||
const context = inject<CategoryViewContext>(CATEGORY_VIEW_CONTEXT);
|
const context = inject<CategoryViewContext>(CATEGORY_VIEW_CONTEXT);
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error('useCategoryView must be used with categoryView context');
|
throw new Error('useCategoryView must be used with categoryView context');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (window.location.hash) {
|
||||||
|
context.selectedCategory.value = decodeURIComponent(window.location.hash.slice(1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(route, (currentRoute) => {
|
||||||
|
if (currentRoute.path !== '/icons/categories') {
|
||||||
|
context.selectedCategory.value = '';
|
||||||
|
context.categoryCounts.value = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { ref } from "vue";
|
import { ref } from 'vue';
|
||||||
|
|
||||||
export default function useConfetti() {
|
export default function useConfetti() {
|
||||||
const animate = ref(false)
|
const animate = ref(false);
|
||||||
const confettiText = ref('confetti!')
|
const confettiText = ref('confetti!');
|
||||||
|
|
||||||
function confetti() {
|
function confetti() {
|
||||||
animate.value = true;
|
animate.value = true;
|
||||||
@@ -15,6 +15,6 @@ export default function useConfetti() {
|
|||||||
return {
|
return {
|
||||||
animate,
|
animate,
|
||||||
confetti,
|
confetti,
|
||||||
confettiText
|
confettiText,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
57
docs/.vitepress/theme/composables/useExternalLibs.ts
Normal file
57
docs/.vitepress/theme/composables/useExternalLibs.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { ref, inject, Ref, watch } from 'vue';
|
||||||
|
import { ExternalLibs, IconEntity } from '../types';
|
||||||
|
|
||||||
|
export const EXTERNAL_LIBS_CONTEXT = Symbol('externalLibs');
|
||||||
|
|
||||||
|
type ExternalIconNodes = Partial<Record<ExternalLibs, IconEntity[]>>;
|
||||||
|
|
||||||
|
interface ExternalLibContext {
|
||||||
|
selectedLibs: Ref<[ExternalLibs]>;
|
||||||
|
externalIconNodes: Ref<ExternalIconNodes>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const externalLibContext = {
|
||||||
|
selectedLibs: ref([]),
|
||||||
|
externalIconNodes: ref({}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const externalLibIconNodesAPI = {
|
||||||
|
lab: 'https://lab.lucide.dev/api/icon-details',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useExternalLibs(): ExternalLibContext {
|
||||||
|
const context = inject<ExternalLibContext>(EXTERNAL_LIBS_CONTEXT);
|
||||||
|
|
||||||
|
watch(context?.selectedLibs, async (selectedLibs) => {
|
||||||
|
const savedIconNodes = { ...context?.externalIconNodes.value };
|
||||||
|
const newExternalIconNodes: ExternalIconNodes = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const lib of selectedLibs) {
|
||||||
|
if (savedIconNodes[lib]) {
|
||||||
|
newExternalIconNodes[lib] = savedIconNodes[lib];
|
||||||
|
} else {
|
||||||
|
const response = await fetch(externalLibIconNodesAPI[lib]);
|
||||||
|
const iconNodes = await response.json();
|
||||||
|
|
||||||
|
if (iconNodes) {
|
||||||
|
newExternalIconNodes[lib] = Object.values(iconNodes).map((iconEntity: IconEntity) => ({
|
||||||
|
...iconEntity,
|
||||||
|
externalLibrary: lib,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.externalIconNodes.value = newExternalIconNodes;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useExternalLibs must be used with externalLibs context');
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user