mirror of
https://github.com/lucide-icons/lucide.git
synced 2025-12-18 20:09:22 +01:00
Compare commits
319 Commits
0.300.0
...
pr-commit-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
224b4e60c2 | ||
|
|
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 | ||
|
|
06372db723 | ||
|
|
114fb08556 | ||
|
|
3bad7f6ced | ||
|
|
e450afe408 | ||
|
|
6fcfc820ca | ||
|
|
55f264bea6 | ||
|
|
675158df16 | ||
|
|
042393a931 | ||
|
|
551635003c | ||
|
|
5c7119f6d1 | ||
|
|
7bfdb2f54a | ||
|
|
e3abcdbcc6 | ||
|
|
62d350140e | ||
|
|
ad90ee7582 | ||
|
|
b0e825cb57 | ||
|
|
d4cbe899fb | ||
|
|
be3ccc8ece | ||
|
|
e56b09d400 | ||
|
|
7e936c8803 | ||
|
|
c9ffa4033f | ||
|
|
32a6187d69 | ||
|
|
2bf20db4bf | ||
|
|
4473df5ad5 | ||
|
|
bc5efd7f0d | ||
|
|
05f209cb04 | ||
|
|
ef89510a47 | ||
|
|
b72cea6399 | ||
|
|
d2152b3cdd | ||
|
|
ea43df6431 | ||
|
|
4042a366e8 | ||
|
|
5e0ba7e897 | ||
|
|
6b4f575992 | ||
|
|
92784de9c5 | ||
|
|
314ee3b255 | ||
|
|
ee7add11bf | ||
|
|
001d0a51fb | ||
|
|
fd4308e755 | ||
|
|
59893119b5 | ||
|
|
faff6a8269 | ||
|
|
3fe74beeb0 | ||
|
|
f8aafd63b9 | ||
|
|
f0422f415c | ||
|
|
14a196d89c | ||
|
|
87a89b30da | ||
|
|
3180910b23 | ||
|
|
46a4041cc7 | ||
|
|
e3166c65df | ||
|
|
d9d7e245a3 | ||
|
|
e19a9cc43a | ||
|
|
5ecfd61397 | ||
|
|
5366782da0 | ||
|
|
262f9e8865 | ||
|
|
7af26002a0 | ||
|
|
e71a776acc | ||
|
|
0a69fb9e52 |
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,5 +6,5 @@ tests
|
||||
node_modules
|
||||
.eslintrc.js
|
||||
docs/images
|
||||
docs/guide/basics/examples
|
||||
docs/**/examples/
|
||||
packages/lucide-react/dynamicIconImports.js
|
||||
|
||||
32
.eslintrc.js
32
.eslintrc.js
@@ -1,4 +1,4 @@
|
||||
const DEFAULT_ATTRS = require('./scripts/render/default-attrs.json');
|
||||
const DEFAULT_ATTRS = require('./tools/build-icons/render/default-attrs.json');
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
@@ -7,23 +7,17 @@ module.exports = {
|
||||
node: true,
|
||||
},
|
||||
extends: ['airbnb-base', 'prettier'],
|
||||
plugins: ['import', 'prettier', '@html-eslint'],
|
||||
plugins: ['import', '@html-eslint'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'no-param-reassign': 'off',
|
||||
'no-shadow': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
printWidth: 100
|
||||
},
|
||||
],
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{ devDependencies: ['**/*.test.js', '**/*.spec.js', './scripts/**'] },
|
||||
{
|
||||
devDependencies: ['**/*.test.js', '**/*.spec.js', '**/scripts/**'],
|
||||
},
|
||||
],
|
||||
'import/extensions': [
|
||||
'error',
|
||||
@@ -37,7 +31,7 @@ module.exports = {
|
||||
},
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./site/tsconfig.json', './packages/*/tsconfig.json'],
|
||||
project: ['./docs/tsconfig.json', './packages/*/tsconfig.json'],
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
@@ -46,17 +40,19 @@ module.exports = {
|
||||
files: ['./icons/*.svg'],
|
||||
parser: '@html-eslint/parser',
|
||||
rules: {
|
||||
'prettier/prettier': 'off',
|
||||
'@html-eslint/require-doctype': 'off',
|
||||
'@html-eslint/no-duplicate-attrs': 'error',
|
||||
'@html-eslint/no-inline-styles': 'error',
|
||||
'@html-eslint/require-attrs': [
|
||||
'error',
|
||||
...Object.entries(DEFAULT_ATTRS)
|
||||
.map(([attr, value]) => ({ tag: 'svg', attr, value: String(value) }))
|
||||
'error',
|
||||
...Object.entries(DEFAULT_ATTRS).map(([attr, value]) => ({
|
||||
tag: 'svg',
|
||||
attr,
|
||||
value: String(value),
|
||||
})),
|
||||
],
|
||||
'@html-eslint/indent': ['error', 2],
|
||||
"@html-eslint/no-multiple-empty-lines": ["error", { "max": 0 }],
|
||||
'@html-eslint/no-multiple-empty-lines': ['error', { max: 0 }],
|
||||
'@html-eslint/no-extra-spacing-attrs': [
|
||||
'error',
|
||||
{
|
||||
@@ -73,7 +69,7 @@ module.exports = {
|
||||
'@html-eslint/element-newline': 'error',
|
||||
'@html-eslint/no-trailing-spaces': 'error',
|
||||
'@html-eslint/quotes': 'error',
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
13
.github/ISSUE_TEMPLATE/02_bug_report.yml
vendored
13
.github/ISSUE_TEMPLATE/02_bug_report.yml
vendored
@@ -35,6 +35,16 @@ body:
|
||||
placeholder: e.g. 0.289.1
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: can-reproduce-in-latest-version
|
||||
attributes:
|
||||
label: Can you reproduce this in the latest version?
|
||||
description: i.e. after running `npm install lucide-react@latest`
|
||||
options:
|
||||
- label: 'Yes'
|
||||
- label: 'No'
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
id: browsers
|
||||
attributes:
|
||||
@@ -59,6 +69,9 @@ body:
|
||||
- label: Windows
|
||||
- label: Linux
|
||||
- label: macOS
|
||||
- label: ChromeOS
|
||||
- label: iOS
|
||||
- label: Android
|
||||
- label: Other/not relevant
|
||||
- type: textarea
|
||||
id: description
|
||||
|
||||
@@ -30,6 +30,9 @@ body:
|
||||
- label: Windows
|
||||
- label: Linux
|
||||
- label: macOS
|
||||
- label: ChromeOS
|
||||
- label: iOS
|
||||
- label: Android
|
||||
- label: Other/not relevant
|
||||
- type: textarea
|
||||
id: description
|
||||
|
||||
14
.github/actions/build-and-test.yml
vendored
14
.github/actions/build-and-test.yml
vendored
@@ -1,5 +1,5 @@
|
||||
name: "Build and Test"
|
||||
description: "Builds and test a package"
|
||||
name: 'Build and Test'
|
||||
description: 'Builds and test a package'
|
||||
|
||||
inputs:
|
||||
name:
|
||||
@@ -7,18 +7,18 @@ inputs:
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- uses: pnpm/action-setup@v2.0.1
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 7
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
|
||||
14
.github/actions/check-icons.yml
vendored
14
.github/actions/check-icons.yml
vendored
@@ -1,5 +1,5 @@
|
||||
name: "Check icons"
|
||||
description: "Cross-checks icon and category references in JSON descriptors"
|
||||
name: 'Check icons'
|
||||
description: 'Cross-checks icon and category references in JSON descriptors'
|
||||
|
||||
inputs:
|
||||
name:
|
||||
@@ -7,18 +7,18 @@ inputs:
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- uses: pnpm/action-setup@v2.0.1
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 7
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
|
||||
66
.github/labeler.yml
vendored
66
.github/labeler.yml
vendored
@@ -1,62 +1,92 @@
|
||||
# For changed dependencies
|
||||
📦 dependencies:
|
||||
- pnpm-lock.yaml
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- pnpm-lock.yaml
|
||||
|
||||
# For changes in documentation
|
||||
📖 documentation:
|
||||
- docs/*.md
|
||||
- docs/**/*.md
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- docs/*.md
|
||||
- docs/**/*.md
|
||||
|
||||
# For changes in the site, but not markdown files
|
||||
🌍 site:
|
||||
- 'docs/**'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'docs/**'
|
||||
|
||||
# For changes in the metadata
|
||||
🫧 metadata:
|
||||
- 'icons/*.json'
|
||||
- categories/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'icons/*.json'
|
||||
- categories/*
|
||||
|
||||
# For changes or added icons
|
||||
🎨 icon:
|
||||
- 'icons/*.svg'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'icons/*.svg'
|
||||
|
||||
# For changes in the lucide package
|
||||
🧳 lucide package:
|
||||
- 'packages/lucide/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide/*'
|
||||
|
||||
# For changes in the lucide React package
|
||||
⚛️ react package:
|
||||
- 'packages/lucide-react/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-react/*'
|
||||
|
||||
# For changes in the lucide React Native package
|
||||
⚛️ react native package:
|
||||
- 'packages/lucide-react-native/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-react-native/*'
|
||||
|
||||
# For changes in the lucide vue packages
|
||||
💎 vue package:
|
||||
- 'packages/lucide-vue/*'
|
||||
- 'packages/lucide-vue-next/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-vue/*'
|
||||
- 'packages/lucide-vue-next/*'
|
||||
|
||||
# For changes in the lucide angular package
|
||||
🅰️ angular package:
|
||||
- 'packages/lucide-angular/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-angular/*'
|
||||
|
||||
# For changes in the lucide preact package
|
||||
⚛️ preact package:
|
||||
- 'packages/lucide-preact/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-preact/*'
|
||||
|
||||
# For changes in the lucide svelte package
|
||||
🧣 svelte package:
|
||||
- 'packages/lucide-svelte/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-svelte/*'
|
||||
|
||||
# For changes in the lucide solid package
|
||||
🪝 solid package:
|
||||
- 'packages/lucide-solid/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-solid/*'
|
||||
|
||||
# For changes in the lucide static package
|
||||
🪨 static package:
|
||||
- 'packages/lucide-static/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-static/*'
|
||||
|
||||
# For changes in the lucide flutter package
|
||||
🏹 flutter package:
|
||||
- 'packages/lucide-flutter/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-flutter/*'
|
||||
|
||||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -47,7 +47,7 @@
|
||||
- [ ] I've made sure that the icons look sharp on low DPI displays.
|
||||
- [ ] I've made sure that the icons look consistent with the icon set in size, optical volume and density.
|
||||
- [ ] I've made sure that the icons are visually centered.
|
||||
- [ ] I've correctly optimized all icons to two points of precision.
|
||||
- [ ] I've correctly optimized all icons to three points of precision.
|
||||
|
||||
## Before Submitting <!-- For every PR! -->
|
||||
<!-- All of these requirements must be fulfilled. -->
|
||||
|
||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -15,11 +15,11 @@ jobs:
|
||||
VERSION: ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v3.8.1
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
10
.github/workflows/close-stale-prs.yml
vendored
10
.github/workflows/close-stale-prs.yml
vendored
@@ -1,18 +1,20 @@
|
||||
name: Close stale issues and PR
|
||||
on:
|
||||
schedule:
|
||||
- cron: "45 1 * * *"
|
||||
- cron: '45 1 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-pr-message: This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 5 days.
|
||||
close-pr-message: This PR was closed because it has been stalled for 5 days with no activity.
|
||||
close-pr-label: 🧶 stale
|
||||
days-before-stale: 30
|
||||
days-before-close: 5
|
||||
days-before-pr-close: -1
|
||||
days-before-issue-stale: -1
|
||||
days-before-close: -1
|
||||
|
||||
6
.github/workflows/labeler.yml
vendored
6
.github/workflows/labeler.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: "Pull Request Labeler"
|
||||
name: 'Pull Request Labeler'
|
||||
on:
|
||||
- pull_request_target
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
@@ -9,4 +9,4 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v4
|
||||
- uses: actions/labeler@v5
|
||||
|
||||
8
.github/workflows/linting.yml
vendored
8
.github/workflows/linting.yml
vendored
@@ -2,18 +2,18 @@ name: Linting
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- icons/**
|
||||
branches:
|
||||
- '**'
|
||||
|
||||
jobs:
|
||||
linting:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v3.8.1
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
23
.github/workflows/lucide-angular.yml
vendored
23
.github/workflows/lucide-angular.yml
vendored
@@ -8,7 +8,25 @@ on:
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
lucide-angular:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-angular build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -23,8 +41,5 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-angular build
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-angular test
|
||||
|
||||
8
.github/workflows/lucide-font.yml
vendored
8
.github/workflows/lucide-font.yml
vendored
@@ -11,11 +11,11 @@ jobs:
|
||||
lucide-font:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v3.8.1
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
@@ -29,8 +29,8 @@ jobs:
|
||||
- name: Create font in ./lucide-font
|
||||
run: pnpm build:font
|
||||
|
||||
- name: "Upload to Artifacts"
|
||||
uses: actions/upload-artifact@v1
|
||||
- name: 'Upload to Artifacts'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lucide-font
|
||||
path: lucide-font
|
||||
|
||||
7
.github/workflows/lucide-preact.yml
vendored
7
.github/workflows/lucide-preact.yml
vendored
@@ -12,11 +12,11 @@ jobs:
|
||||
lucide-preact:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v3.8.1
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
@@ -24,8 +24,5 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-preact build
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-preact test
|
||||
|
||||
7
.github/workflows/lucide-react-native.yml
vendored
7
.github/workflows/lucide-react-native.yml
vendored
@@ -12,11 +12,11 @@ jobs:
|
||||
lucide-react-native:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v3.8.1
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
@@ -24,8 +24,5 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-react-native build
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-react-native test
|
||||
|
||||
23
.github/workflows/lucide-react.yml
vendored
23
.github/workflows/lucide-react.yml
vendored
@@ -10,7 +10,25 @@ on:
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
lucide-react:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-react build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -25,8 +43,5 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-react build
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-react test
|
||||
|
||||
23
.github/workflows/lucide-solid.yml
vendored
23
.github/workflows/lucide-solid.yml
vendored
@@ -9,7 +9,25 @@ on:
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
lucide-solid:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-solid build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -24,8 +42,5 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-solid build
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-solid test
|
||||
|
||||
4
.github/workflows/lucide-static.yml
vendored
4
.github/workflows/lucide-static.yml
vendored
@@ -11,11 +11,11 @@ jobs:
|
||||
lucide-static:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v3.8.1
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
23
.github/workflows/lucide-svelte.yml
vendored
23
.github/workflows/lucide-svelte.yml
vendored
@@ -9,7 +9,25 @@ on:
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
lucide-svelte:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-svelte build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -24,8 +42,5 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-svelte build
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-svelte test
|
||||
|
||||
23
.github/workflows/lucide-vue-next.yml
vendored
23
.github/workflows/lucide-vue-next.yml
vendored
@@ -9,7 +9,25 @@ on:
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
lucide-vue-next:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-vue-next build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -24,8 +42,5 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-vue-next build
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-vue-next test
|
||||
|
||||
23
.github/workflows/lucide-vue.yml
vendored
23
.github/workflows/lucide-vue.yml
vendored
@@ -9,7 +9,25 @@ on:
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
lucide-vue:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-vue build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -24,8 +42,5 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-vue build
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-vue test
|
||||
|
||||
23
.github/workflows/lucide.yml
vendored
23
.github/workflows/lucide.yml
vendored
@@ -9,7 +9,25 @@ on:
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
lucide:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -24,8 +42,5 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide build
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide test
|
||||
|
||||
16
.github/workflows/pull-request-title.yml
vendored
Normal file
16
.github/workflows/pull-request-title.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Validate PR title
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
semantic-pull-request:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
216
.github/workflows/pull-request.yml
vendored
216
.github/workflows/pull-request.yml
vendored
@@ -3,27 +3,48 @@ name: Add Changed Icons comment
|
||||
on:
|
||||
pull_request_target:
|
||||
paths:
|
||||
- 'icons/*.svg'
|
||||
- 'icons/*'
|
||||
branches:
|
||||
- main
|
||||
- fix-icon-preview
|
||||
|
||||
jobs:
|
||||
lint-filenames:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: refs/pull/${{ github.event.pull_request.number }}/merge
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v41
|
||||
with:
|
||||
files: icons/*
|
||||
- name: Generate annotations
|
||||
run: node ./scripts/lintFilenames.mjs
|
||||
env:
|
||||
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
|
||||
lint-contributors:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: refs/pull/${{ github.event.pull_request.number }}/merge
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v35
|
||||
uses: tj-actions/changed-files@v41
|
||||
with:
|
||||
files: icons/*
|
||||
- uses: actions/setup-node@v3.8.1
|
||||
- uses: actions/setup-node@v4
|
||||
- name: Install simple-git (safer and faster than installing all deps)
|
||||
run: npm install simple-git
|
||||
- name: Generate annotations
|
||||
@@ -46,184 +67,57 @@ jobs:
|
||||
# Example for the previous substitution
|
||||
# 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
|
||||
# - name: Fail if contributors have changed
|
||||
# run: git diff --exit-code -- icons/*.json
|
||||
|
||||
generate-changed-icons-comment-data:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
lint-aliases:
|
||||
name: Check Uniqueness of Aliases
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
generate-1px-stroke-width: ${{ steps.generate-1px-stroke-width.outputs.body }}
|
||||
generate-2px-stroke-width: ${{ steps.generate-2px-stroke-width.outputs.body }}
|
||||
generate-3px-stroke-width: ${{ steps.generate-3px-stroke-width.outputs.body }}
|
||||
generate-24px-dpi-preview: ${{ steps.generate-24px-dpi-preview.outputs.body }}
|
||||
generate-cohesion-check-random: ${{ steps.generate-cohesion-check-random.outputs.body }}
|
||||
generate-cohesion-check-squares: ${{ steps.generate-cohesion-check-squares.outputs.body }}
|
||||
generate-x-rays: ${{ steps.generate-x-rays.outputs.body }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check Uniqueness of Aliases
|
||||
run: "! cat <(printf \"%s\\n\" icons/*.json | while read -r name; do basename \"$name\" .json; done) <(jq -cr 'select(.aliases) | .aliases[] | if type==\"string\" then . else .name end' icons/*.json) | sort | uniq -c | grep -ve '^\\s*1 '"
|
||||
|
||||
generate-changed-icons-comment:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: refs/pull/${{ github.event.pull_request.number }}/merge
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v35
|
||||
uses: tj-actions/changed-files@v41
|
||||
with:
|
||||
files: icons/*.svg
|
||||
- name: Generate 24px dpi preview
|
||||
id: generate-24px-dpi-preview
|
||||
env:
|
||||
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
run: |
|
||||
delimiter="$(openssl rand -hex 8)"
|
||||
echo "body<<$delimiter" >> $GITHUB_OUTPUT
|
||||
while IFS= read -r file; do
|
||||
cat "$file" | # get file content
|
||||
tr '\n' ' ' | # remove line breaks
|
||||
sed -e 's/<svg[^>]*>/<svg>/g' | # remove attributes from svg element
|
||||
base64 -w 0 | # encode svg
|
||||
sed "s|.*|<img title=\"$file\" alt=\"$file\" src=\"https://lucide.dev/api/gh-icon/dpi/24/&.svg\"/> |"
|
||||
done <<< "$CHANGED_FILES" | tr '\n' ' ' >> $GITHUB_OUTPUT
|
||||
echo >> $GITHUB_OUTPUT
|
||||
echo "$delimiter" >> $GITHUB_OUTPUT
|
||||
- name: Generate cohesion check random
|
||||
id: generate-cohesion-check-random
|
||||
env:
|
||||
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
run: |
|
||||
delimiter="$(openssl rand -hex 8)"
|
||||
echo "body<<$delimiter" >> $GITHUB_OUTPUT
|
||||
for file in $(printf "%s\\n" icons/*.svg | shuf | head -n$(awk -F' ' '{print NF}' <<< '${{ steps.changed-files.outputs.all_changed_files }}')); do
|
||||
cat "$file" | # get file content
|
||||
tr '\n' ' ' | # remove line breaks
|
||||
sed -e 's/<svg[^>]*>/<svg>/g' | # remove attributes from svg element
|
||||
base64 -w 0 | # encode svg
|
||||
sed "s|.*|<img title=\"$file\" alt=\"$file\" src=\"https://lucide.dev/api/gh-icon/stroke-width/2/&.svg\"/> |"
|
||||
done <<< "$CHANGED_FILES" | tr '\n' ' ' >> $GITHUB_OUTPUT
|
||||
echo >> $GITHUB_OUTPUT
|
||||
echo "$delimiter" >> $GITHUB_OUTPUT
|
||||
- name: Generate cohesion check squares
|
||||
id: generate-cohesion-check-squares
|
||||
env:
|
||||
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
run: |
|
||||
delimiter="$(openssl rand -hex 8)"
|
||||
echo "body<<$delimiter" >> $GITHUB_OUTPUT
|
||||
for file in $(printf "%s\\n" icons/*square*.svg | shuf | head -n$(awk -F' ' '{print NF}' <<< '${{ steps.changed-files.outputs.all_changed_files }}')); do
|
||||
cat "$file" | # get file content
|
||||
tr '\n' ' ' | # remove line breaks
|
||||
sed -e 's/<svg[^>]*>/<svg>/g' | # remove attributes from svg element
|
||||
base64 -w 0 | # encode svg
|
||||
sed "s|.*|<img title=\"$file\" alt=\"$file\" src=\"https://lucide.dev/api/gh-icon/stroke-width/2/&.svg\"/> |"
|
||||
done <<< "$CHANGED_FILES" | tr '\n' ' ' >> $GITHUB_OUTPUT
|
||||
echo >> $GITHUB_OUTPUT
|
||||
echo "$delimiter" >> $GITHUB_OUTPUT
|
||||
- name: Generate 1px stroke-width
|
||||
id: generate-1px-stroke-width
|
||||
env:
|
||||
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
run: |
|
||||
delimiter="$(openssl rand -hex 8)"
|
||||
echo "body<<$delimiter" >> $GITHUB_OUTPUT
|
||||
while IFS= read -r file; do
|
||||
cat "$file" | # get file content
|
||||
tr '\n' ' ' | # remove line breaks
|
||||
sed -e 's/<svg[^>]*>/<svg>/g' | # remove attributes from svg element
|
||||
base64 -w 0 | # encode svg
|
||||
sed "s|.*|<img title=\"$file\" alt=\"$file\" src=\"https://lucide.dev/api/gh-icon/stroke-width/1/&.svg\"/> |"
|
||||
done <<< "$CHANGED_FILES" | tr '\n' ' ' >> $GITHUB_OUTPUT
|
||||
echo >> $GITHUB_OUTPUT
|
||||
echo "$delimiter" >> $GITHUB_OUTPUT
|
||||
- name: Generate 2px stroke-width
|
||||
id: generate-2px-stroke-width
|
||||
env:
|
||||
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
run: |
|
||||
delimiter="$(openssl rand -hex 8)"
|
||||
echo "body<<$delimiter" >> $GITHUB_OUTPUT
|
||||
while IFS= read -r file; do
|
||||
cat "$file" | # get file content
|
||||
tr '\n' ' ' | # remove line breaks
|
||||
sed -e 's/<svg[^>]*>/<svg>/g' | # remove attributes from svg element
|
||||
base64 -w 0 | # encode svg
|
||||
sed "s|.*|<img title=\"$file\" alt=\"$file\" src=\"https://lucide.dev/api/gh-icon/stroke-width/2/&.svg\"/> |"
|
||||
done <<< "$CHANGED_FILES" | tr '\n' ' ' >> $GITHUB_OUTPUT
|
||||
echo >> $GITHUB_OUTPUT
|
||||
echo "$delimiter" >> $GITHUB_OUTPUT
|
||||
- name: Generate 3px stroke-width
|
||||
id: generate-3px-stroke-width
|
||||
run: |
|
||||
delimiter="$(openssl rand -hex 8)"
|
||||
echo "body<<$delimiter" >> $GITHUB_OUTPUT
|
||||
while IFS= read -r file; do
|
||||
cat "$file" | # get file content
|
||||
tr '\n' ' ' | # remove line breaks
|
||||
sed -e 's/<svg[^>]*>/<svg>/g' | # remove attributes from svg element
|
||||
base64 -w 0 | # encode svg
|
||||
sed "s|.*|<img title=\"$file\" alt=\"$file\" src=\"https://lucide.dev/api/gh-icon/stroke-width/3/&.svg\"/> |"
|
||||
done <<< "$CHANGED_FILES" | tr '\n' ' ' >> $GITHUB_OUTPUT
|
||||
echo >> $GITHUB_OUTPUT
|
||||
echo "$delimiter" >> $GITHUB_OUTPUT
|
||||
- name: Generate X-rays
|
||||
id: generate-x-rays
|
||||
env:
|
||||
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
run: |
|
||||
delimiter="$(openssl rand -hex 8)"
|
||||
echo "body<<$delimiter" >> $GITHUB_OUTPUT
|
||||
while IFS= read -r file; do
|
||||
cat "$file" | # get file content
|
||||
tr '\n' ' ' | # remove line breaks
|
||||
sed -e 's/<svg[^>]*>/<svg>/g' | # remove attributes from svg element
|
||||
base64 -w 0 | # encode svg
|
||||
sed "s|.*|<img width=\"400\" title=\"$file\" alt=\"$file\" src=\"https://lucide.dev/api/gh-icon/$(basename ${file//\.svg/})/&.svg\"/> |"
|
||||
done <<< "$CHANGED_FILES" | tr '\n' ' ' >> $GITHUB_OUTPUT
|
||||
echo >> $GITHUB_OUTPUT
|
||||
echo "$delimiter" >> $GITHUB_OUTPUT
|
||||
|
||||
add-changed-icons-comment:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
needs: [generate-changed-icons-comment-data]
|
||||
steps:
|
||||
- name: Find Comment
|
||||
uses: peter-evans/find-comment@v2
|
||||
id: fc
|
||||
id: pr-comment
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: Added or changed icons
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- name: Install svgson for code preview (safer and faster than installing all deps)
|
||||
run: npm install svgson
|
||||
|
||||
- name: Generate comment markup
|
||||
run: node ./scripts/generateChangedIconsCommentMarkup.mjs >> comment-markup.md
|
||||
id: comment-markup
|
||||
env:
|
||||
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
|
||||
- name: Create or update comment
|
||||
uses: peter-evans/create-or-update-comment@v2
|
||||
uses: peter-evans/create-or-update-comment@v3
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
comment-id: ${{ steps.pr-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
### Added or changed icons
|
||||
${{ needs.generate-changed-icons-comment-data.outputs.generate-2px-stroke-width }}<br/>
|
||||
<details>
|
||||
<summary>Preview cohesion</summary>
|
||||
${{ needs.generate-changed-icons-comment-data.outputs.generate-cohesion-check-squares }}<br/>
|
||||
${{ needs.generate-changed-icons-comment-data.outputs.generate-2px-stroke-width }}<br/>
|
||||
${{ needs.generate-changed-icons-comment-data.outputs.generate-cohesion-check-random }}<br/>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Preview stroke widths</summary>
|
||||
${{ needs.generate-changed-icons-comment-data.outputs.generate-1px-stroke-width }}<br/>
|
||||
${{ needs.generate-changed-icons-comment-data.outputs.generate-2px-stroke-width }}<br/>
|
||||
${{ needs.generate-changed-icons-comment-data.outputs.generate-3px-stroke-width }}<br/>
|
||||
</details>
|
||||
<details>
|
||||
<summary>DPI Preview (24px)</summary>
|
||||
${{ needs.generate-changed-icons-comment-data.outputs.generate-24px-dpi-preview }}
|
||||
</details>
|
||||
<details>
|
||||
<summary>Icon X-rays</summary>
|
||||
${{ needs.generate-changed-icons-comment-data.outputs.generate-x-rays }}
|
||||
</details>
|
||||
body-path: ./comment-markup.md
|
||||
edit-mode: replace
|
||||
|
||||
50
.github/workflows/release.yml
vendored
50
.github/workflows/release.yml
vendored
@@ -41,23 +41,24 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package: [
|
||||
'lucide',
|
||||
'lucide-react',
|
||||
'lucide-react-native',
|
||||
'lucide-vue',
|
||||
'lucide-vue-next',
|
||||
'lucide-angular',
|
||||
'lucide-preact',
|
||||
'lucide-solid',
|
||||
'lucide-svelte',
|
||||
]
|
||||
package:
|
||||
[
|
||||
'lucide',
|
||||
'lucide-react',
|
||||
'lucide-react-native',
|
||||
'lucide-vue',
|
||||
'lucide-vue-next',
|
||||
'lucide-angular',
|
||||
'lucide-preact',
|
||||
'lucide-solid',
|
||||
'lucide-svelte',
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
@@ -85,12 +86,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [pre-release, lucide-font]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/download-artifact@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
@@ -118,11 +119,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre-release
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
@@ -136,8 +137,8 @@ jobs:
|
||||
- name: Create font in ./lucide-font
|
||||
run: pnpm build:font
|
||||
|
||||
- name: "Upload to Artifacts"
|
||||
uses: actions/upload-artifact@v1
|
||||
- name: 'Upload to Artifacts'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lucide-font
|
||||
path: lucide-font
|
||||
@@ -145,14 +146,11 @@ jobs:
|
||||
post-release:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [
|
||||
pre-release,
|
||||
lucide-font,
|
||||
]
|
||||
needs: [pre-release, lucide-font]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/download-artifact@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v3
|
||||
- name: Zip font and icons
|
||||
run: |
|
||||
zip -r lucide-font-${{ needs.pre-release.outputs.VERSION }}.zip lucide-font
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -34,6 +34,7 @@ docs/.vitepress/data/iconNodes
|
||||
docs/.vitepress/data/iconMetaData.ts
|
||||
docs/.vitepress/data/releaseMetaData.json
|
||||
docs/.vitepress/data/releaseMetaData
|
||||
docs/.vitepress/data/categoriesData.json
|
||||
docs/.vitepress/data/iconDetails
|
||||
docs/.vitepress/data/relatedIcons.json
|
||||
docs/.vercel
|
||||
|
||||
@@ -4,7 +4,6 @@ stats
|
||||
node_modules
|
||||
tests
|
||||
scripts
|
||||
site
|
||||
src
|
||||
build
|
||||
babel.config.js
|
||||
|
||||
23
.prettierignore
Normal file
23
.prettierignore
Normal file
@@ -0,0 +1,23 @@
|
||||
pnpm-lock.yaml
|
||||
|
||||
# docs examples
|
||||
docs/**/examples/
|
||||
docs/.vitepress/.temp
|
||||
docs/.vitepress/cache
|
||||
docs/.vitepress/data
|
||||
docs/.nitro
|
||||
|
||||
# lucide-angular
|
||||
packages/lucide-angular/.angular/cache
|
||||
|
||||
# lucide-static
|
||||
packages/lucide-static/icons
|
||||
packages/lucide-static/lib
|
||||
packages/lucide-static/sprite.svg
|
||||
packages/lucide-static/tags.json
|
||||
packages/lucide-static/icon-nodes.json
|
||||
packages/lucide-static/font
|
||||
|
||||
# lucide-svelte
|
||||
packages/lucide-svelte/src/icons/*.svelte
|
||||
packages/lucide-svelte/.svelte-kit
|
||||
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@@ -1,13 +1,6 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"devs",
|
||||
"preact",
|
||||
"Preact"
|
||||
],
|
||||
"cSpell.words": ["devs", "preact", "Preact"],
|
||||
"eslint.enable": true,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"svg"
|
||||
],
|
||||
"eslint.validate": ["javascript", "svg"],
|
||||
"svg.preview.background": "transparent"
|
||||
}
|
||||
|
||||
2
.vscode/svg.code-snippets
vendored
2
.vscode/svg.code-snippets
vendored
@@ -49,7 +49,7 @@
|
||||
"circle",
|
||||
"<circle"
|
||||
],
|
||||
"body": "<circle cx=\"${2:12}\" cy=\"${3:$2}\" r=\"${1|10,2,.5|}\" />"
|
||||
"body": "<circle cx=\"${2:12}\" cy=\"${3:$2}\" r=\"${1|10,2,.5\" fill=\"currentColor|}\" />"
|
||||
},
|
||||
"Ellipse": {
|
||||
"scope": "xml",
|
||||
|
||||
@@ -39,6 +39,8 @@ You can also [download an Adobe Illustrator template](https://github.com/lucide-
|
||||
|
||||
#### [Figma Guide](https://lucide.dev/docs/figma-guide)
|
||||
|
||||
#### [Affinity Designer Guide](https://lucide.dev/guide/design/affinity-designer-guide)
|
||||
|
||||
### Submitting Multiple Icons
|
||||
|
||||
If you want submit multiple icons, please separate the icons and group them. That makes reviewing the icons easier and keep the thread clean and scoped.
|
||||
@@ -50,7 +52,7 @@ Seperate them by two PRs; 'pr-01' `arrow`, `arrow-down` and 'pr-02' `bicycle`.
|
||||
|
||||
Before creating an icon request, please search to see if someone has requested the icon already. If there is an open request, please add a :+1:.
|
||||
|
||||
If the icon has not already been requested, [create an issue](https://github.com/lucide-icons/lucide/issues/new?title=Icon%20Request:) with a title of `Icon request: <icon name>` and add as much information as possible.
|
||||
If the icon has not already been requested, [create an icon request issue](https://github.com/lucide-icons/lucide/issues/new?assignees=&labels=%F0%9F%99%8C+icon+request&projects=&template=01_icon_request.yml) and add as much information as possible.
|
||||
|
||||
### Icon Requests from Feather
|
||||
|
||||
@@ -72,8 +74,8 @@ pnpm install # Install dependencies, including the workspace packages
|
||||
|
||||
To distribute different packages we use PNPM workspaces. Before you start make sure you are familiar with this concept. The concept of working in workspaces is created by Yarn, they have a well written introduction: [yarn workspaces](https://classic.yarnpkg.com/lang/enhttps://lucide.dev/docs/workspaces).
|
||||
|
||||
The configured directory for workspaces is the [packages](./packages) directory, located in the root directory. There you will find all the current packages from lucide.
|
||||
There are more workspaces defined, see [`pnpm-workspace.yaml`](./pnpm-workspace.yaml).
|
||||
The configured directory for workspaces is the [packages](https://github.com/lucide-icons/lucide/tree/main/packages) directory, located in the root directory. There you will find all the current packages from lucide.
|
||||
There are more workspaces defined, see [`pnpm-workspace.yaml`](https://github.com/lucide-icons/lucide/blob/main/pnpm-workspace.yaml).
|
||||
|
||||
> Note: One package is not managed by pnpm: **lucide-flutter**, this package is written in Dart and used pub for publishing.
|
||||
|
||||
@@ -144,16 +146,19 @@ Root directories
|
||||
|
||||
```sh
|
||||
lucide
|
||||
|
|
||||
├── docs
|
||||
│ ├── guide
|
||||
├── icons
|
||||
├── packages
|
||||
├── scripts
|
||||
└── site
|
||||
└── scripts
|
||||
```
|
||||
|
||||
### Docs
|
||||
|
||||
The lucide.dev website is using [vitepress](https://vitepress.dev/) to generate the static website. The markdown files are located in the docs directory.
|
||||
|
||||
### Guides
|
||||
|
||||
Detailed documentation about: installation, guides, packages, design guides etc.
|
||||
|
||||
### Icons
|
||||
@@ -170,10 +175,6 @@ Includes all the (npm) packages of lucide.
|
||||
|
||||
Includes usefully scripts to automate certain jobs. Big part of the scripts is the template generation, for example it generates icon components for all the packages. These scripts are usually executed from the "scripts" section in the package.json.
|
||||
|
||||
### site
|
||||
|
||||
The lucide.dev website is using [vitepress](https://vitepress.dev/) to generate the static website. The markdown files are located in the docs directory.
|
||||
|
||||
## Documentation
|
||||
|
||||
The documentation files are located in the [docs](https://github.com/lucide-icons/lucide/tree/main/docs) directory. All these markdown files will be loaded in the build of the lucide.dev website.
|
||||
|
||||
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">
|
||||
<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>
|
||||
@@ -274,9 +281,12 @@ Thank you to all the people who contributed to Lucide!
|
||||
|
||||
## Sponsors
|
||||
|
||||
|
||||
<a href="https://vercel.com?utm_source=lucide&utm_campaign=oss">
|
||||
<img src="docs/public/vercel.svg" alt="Powered by Vercel" width="200" />
|
||||
</a>
|
||||
|
||||
<a href="https://www.digitalocean.com/?refcode=b0877a2caebd&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge"><img src="docs/public/digitalocean.svg" width="200" alt="DigitalOcean Referral Badge" /></a>
|
||||
|
||||
### Awesome 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",
|
||||
"title": "Coding & development",
|
||||
"icon": "code-2"
|
||||
"icon": "code-xml"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "File icons",
|
||||
"icon": "layout"
|
||||
"icon": "panels-top-left"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Home",
|
||||
"icon": "home"
|
||||
"icon": "house"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Layout",
|
||||
"icon": "layout"
|
||||
"icon": "panels-top-left"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Multimedia",
|
||||
"icon": "play-circle"
|
||||
"icon": "circle-play"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Notifications",
|
||||
"icon": "alert-triangle"
|
||||
"icon": "triangle-alert"
|
||||
}
|
||||
28
commitlint.config.ts
Normal file
28
commitlint.config.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { UserConfig } from '@commitlint/types';
|
||||
import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
|
||||
// Read directory
|
||||
const getAllPackageFromDirectory = async (directory: string) => {
|
||||
return fs.readdir(path.resolve(process.cwd(), directory))
|
||||
}
|
||||
|
||||
const Configuration: UserConfig = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
rules: {
|
||||
'scope-enum': async () => {
|
||||
const packages = await getAllPackageFromDirectory('packages')
|
||||
const toolPackages = await getAllPackageFromDirectory('tools')
|
||||
|
||||
return [2, 'always', [
|
||||
'site',
|
||||
'meta',
|
||||
...packages,
|
||||
...toolPackages,
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default Configuration;
|
||||
10
cspell.json
Normal file
10
cspell.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"dictionaries": ["en-us", "custom-words"],
|
||||
"dictionaryDefinitions": [
|
||||
{
|
||||
"name": "custom-words",
|
||||
"path": "./.cspell/custom-words.txt",
|
||||
"addWords": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
import { eventHandler, setResponseHeader } from 'h3'
|
||||
import iconMetaData from '../../data/iconMetaData'
|
||||
import iconMetaData from '../../data/iconMetaData';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(iconMetaData).map(([name, { categories }]) => [ name, categories ])
|
||||
)
|
||||
})
|
||||
Object.entries(iconMetaData).map(([name, { categories }]) => [name, categories]),
|
||||
);
|
||||
});
|
||||
|
||||
40
docs/.vitepress/api/figma/data.ts
Normal file
40
docs/.vitepress/api/figma/data.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import iconNodes from '../../data/iconNodes/index.ts';
|
||||
import { IconNodeWithKeys } from '../../theme/types';
|
||||
import iconMetaData from '../../data/iconMetaData';
|
||||
import releaseMeta from '../../data/releaseMetaData.json';
|
||||
import categories from '../../data/categoriesData.json';
|
||||
|
||||
const dataResponse = {
|
||||
icons: Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
|
||||
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
|
||||
return [name, attrs];
|
||||
});
|
||||
|
||||
acc[name] = {
|
||||
iconNode: newIconNode,
|
||||
aliases: (iconMetaData[name]?.aliases ?? []).map((alias) =>
|
||||
typeof alias === 'string' ? alias : alias.name,
|
||||
),
|
||||
tags: iconMetaData[name].tags ?? [],
|
||||
categories: iconMetaData[name].categories ?? [],
|
||||
...releaseMeta[name],
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
aliases: Object.entries(iconNodes).reduce((acc, [name]) => {
|
||||
for (const alias of iconMetaData[name]?.aliases ?? []) {
|
||||
acc[typeof alias === 'string' ? alias : alias.name] = name;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
categories,
|
||||
};
|
||||
|
||||
export default eventHandler((event) => {
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
return dataResponse;
|
||||
});
|
||||
@@ -13,7 +13,10 @@ export default eventHandler((event) => {
|
||||
const data = pathData.at(-1).slice(0, -4);
|
||||
const [name] = pathData;
|
||||
|
||||
const src = Buffer.from(data, 'base64').toString('utf8');
|
||||
const src = Buffer.from(data, 'base64')
|
||||
.toString('utf8')
|
||||
.replaceAll('\n', '')
|
||||
.replace(/<svg[^>]*>|<\/svg>/g, '');
|
||||
|
||||
const children = [];
|
||||
|
||||
@@ -30,20 +33,29 @@ export default eventHandler((event) => {
|
||||
|
||||
const LucideIcon = createLucideIcon(backdropName, iconNode);
|
||||
const svg = renderToStaticMarkup(createElement(LucideIcon));
|
||||
const backdropString = svg.replace(/<svg[^>]*>|<\/svg>/g, '');
|
||||
const backdropString = svg.replaceAll('\n', '').replace(/<svg[^>]*>|<\/svg>/g, '');
|
||||
|
||||
children.push(
|
||||
createElement(Backdrop, {
|
||||
backdropString,
|
||||
src,
|
||||
color: name in iconNodes ? 'red' : '#777',
|
||||
})
|
||||
}),
|
||||
);
|
||||
if (name in iconNodes) {
|
||||
children.push(
|
||||
createElement(Backdrop, {
|
||||
backdropString: src,
|
||||
src: backdropString,
|
||||
color: 'lime',
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const svg = Buffer.from(
|
||||
// We can't use jsx here, is not supported here by nitro.
|
||||
renderToString(createElement(SvgPreview, { src, showGrid: true }, children))
|
||||
renderToString(createElement(SvgPreview, { src, showGrid: true }, children)),
|
||||
).toString('utf8');
|
||||
|
||||
defaultContentType(event, 'image/svg+xml');
|
||||
|
||||
@@ -28,7 +28,7 @@ export default eventHandler(async (event) => {
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
`
|
||||
`,
|
||||
);
|
||||
|
||||
const resvg = new Resvg(svg, { background: '#000' });
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { eventHandler, setResponseHeader, defaultContentType } from 'h3'
|
||||
import { renderToString } from 'react-dom/server'
|
||||
import { createElement } from 'react'
|
||||
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
import { createElement } from 'react';
|
||||
import SvgPreview from '../../../lib/SvgPreview/index.tsx';
|
||||
import createLucideIcon, { IconNode } from 'lucide-react/src/createLucideIcon'
|
||||
import createLucideIcon, { IconNode } from 'lucide-react/src/createLucideIcon';
|
||||
import { parseSync } from 'svgson';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const { params } = event.context
|
||||
const { params } = event.context;
|
||||
|
||||
const [strokeWidth, svgData] = params.data.split('/');
|
||||
const data = svgData.slice(0, -4);
|
||||
@@ -16,8 +16,8 @@ export default eventHandler((event) => {
|
||||
const Icon = createLucideIcon(
|
||||
'icon',
|
||||
parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`).children.map(
|
||||
({ name, attributes }) => [name, attributes]
|
||||
) as IconNode
|
||||
({ name, attributes }) => [name, attributes],
|
||||
) as IconNode,
|
||||
);
|
||||
|
||||
const svg = Buffer.from(
|
||||
@@ -33,12 +33,12 @@ export default eventHandler((event) => {
|
||||
@media screen and (prefers-color-scheme: dark) {
|
||||
svg { stroke: #fff; fill: transparent !important; }
|
||||
}
|
||||
</style>`
|
||||
)
|
||||
</style>`,
|
||||
),
|
||||
).toString('utf8');
|
||||
|
||||
defaultContentType(event, 'image/svg+xml')
|
||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000')
|
||||
defaultContentType(event, 'image/svg+xml');
|
||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
|
||||
|
||||
return svg
|
||||
})
|
||||
return svg;
|
||||
});
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import { eventHandler, getQuery, setResponseHeader } from 'h3'
|
||||
import iconNodes from '../../data/iconNodes'
|
||||
import { IconNodeWithKeys } from '../../theme/types'
|
||||
import { eventHandler, getQuery, setResponseHeader } from 'h3';
|
||||
import iconNodes from '../../data/iconNodes';
|
||||
import { IconNodeWithKeys } from '../../theme/types';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const query = getQuery(event)
|
||||
const query = getQuery(event);
|
||||
|
||||
const withUniqueKeys = query.withUniqueKeys === 'true'
|
||||
const withUniqueKeys = query.withUniqueKeys === 'true';
|
||||
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
if (withUniqueKeys) {
|
||||
return iconNodes
|
||||
return iconNodes;
|
||||
}
|
||||
|
||||
return Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
|
||||
if (withUniqueKeys) {
|
||||
return [name, iconNode]
|
||||
return [name, iconNode];
|
||||
}
|
||||
|
||||
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs}]) => {
|
||||
return [name, attrs]
|
||||
})
|
||||
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
|
||||
return [name, attrs];
|
||||
});
|
||||
|
||||
acc[name] = newIconNode
|
||||
acc[name] = newIconNode;
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
})
|
||||
return acc;
|
||||
}, {});
|
||||
});
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import { eventHandler, getQuery, setResponseHeader, createError } from 'h3'
|
||||
import iconNodes from '../../data/iconNodes'
|
||||
import createLucideIcon from 'lucide-react/src/createLucideIcon'
|
||||
import { renderToString } from 'react-dom/server'
|
||||
import { createElement } from 'react'
|
||||
import { eventHandler, getQuery, setResponseHeader, createError } from 'h3';
|
||||
import iconNodes from '../../data/iconNodes';
|
||||
import createLucideIcon from 'lucide-react/src/createLucideIcon';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
import { createElement } from 'react';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const { params } = event.context
|
||||
const { params } = event.context;
|
||||
|
||||
const iconNode = iconNodes[params.iconName]
|
||||
const iconNode = iconNodes[params.iconName];
|
||||
|
||||
if (iconNode == null) {
|
||||
const error = createError({
|
||||
statusCode: 404,
|
||||
message: `Icon "${params.iconName}" not found`,
|
||||
})
|
||||
});
|
||||
|
||||
return sendError(event, error)
|
||||
return sendError(event, error);
|
||||
}
|
||||
|
||||
const width = getQuery(event).width || undefined
|
||||
const height = getQuery(event).height || undefined
|
||||
const color = getQuery(event).color || undefined
|
||||
const strokeWidth = getQuery(event).strokeWidth || undefined
|
||||
const width = getQuery(event).width || undefined;
|
||||
const height = getQuery(event).height || undefined;
|
||||
const color = getQuery(event).color || undefined;
|
||||
const strokeWidth = getQuery(event).strokeWidth || undefined;
|
||||
|
||||
const LucideIcon = createLucideIcon(params.iconName, iconNode)
|
||||
const LucideIcon = createLucideIcon(params.iconName, iconNode);
|
||||
|
||||
const svg = Buffer.from(
|
||||
renderToString(
|
||||
@@ -32,14 +32,13 @@ export default eventHandler((event) => {
|
||||
height,
|
||||
color: color ? `#${color}` : undefined,
|
||||
strokeWidth,
|
||||
}
|
||||
))
|
||||
}),
|
||||
),
|
||||
).toString('utf8');
|
||||
|
||||
defaultContentType(event, 'image/svg+xml')
|
||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000')
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||
defaultContentType(event, 'image/svg+xml');
|
||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
return svg
|
||||
|
||||
})
|
||||
return svg;
|
||||
});
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { eventHandler, setResponseHeader } from 'h3'
|
||||
import iconMetaData from '../../data/iconMetaData'
|
||||
import iconMetaData from '../../data/iconMetaData';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(iconMetaData).map(([name, { tags }]) => [ name, tags ])
|
||||
)
|
||||
})
|
||||
return Object.fromEntries(Object.entries(iconMetaData).map(([name, { tags }]) => [name, tags]));
|
||||
});
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export default eventHandler(() => {
|
||||
return { nitro: 'Is Awesome! asda' }
|
||||
})
|
||||
return { nitro: 'Is Awesome! asda' };
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import { defineConfig } from 'vitepress'
|
||||
import { fileURLToPath, URL } from 'node:url';
|
||||
import { defineConfig } from 'vitepress';
|
||||
import sidebar from './sidebar';
|
||||
|
||||
const title = "Lucide";
|
||||
const socialTitle = "Lucide Icons";
|
||||
const description = "Beautiful & consistent icon toolkit made by the community."
|
||||
const title = 'Lucide';
|
||||
const socialTitle = 'Lucide Icons';
|
||||
const description = 'Beautiful & consistent icon toolkit made by the community.';
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
@@ -19,86 +19,135 @@ export default defineConfig({
|
||||
{
|
||||
find: /^.*\/VPIconAlignLeft\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/overrides/VPIconAlignLeft.vue', import.meta.url)
|
||||
)
|
||||
new URL('./theme/components/overrides/VPIconAlignLeft.vue', import.meta.url),
|
||||
),
|
||||
},
|
||||
{
|
||||
find: /^.*\/VPFooter\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/overrides/VPFooter.vue', import.meta.url)
|
||||
)
|
||||
}
|
||||
]
|
||||
new URL('./theme/components/overrides/VPFooter.vue', import.meta.url),
|
||||
),
|
||||
},
|
||||
{
|
||||
find: '~/.vitepress',
|
||||
replacement: fileURLToPath(new URL('./', import.meta.url)),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
head: [
|
||||
[ 'script', {
|
||||
src: 'https://analytics.lucide.dev/js/script.js',
|
||||
'data-domain': 'lucide.dev',
|
||||
defer: ''
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:locale",
|
||||
content:"en_US"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:type",
|
||||
content:"website"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:site_name",
|
||||
content: title,
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:title",
|
||||
content: socialTitle,
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:description",
|
||||
content: description
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:url",
|
||||
content:"https://lucide.dev"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:image",
|
||||
content: "https://lucide.dev/og.png"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:image:width",
|
||||
content:"1200"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:image:height",
|
||||
content:"630"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:image:type",
|
||||
content:"image/png"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"twitter:card",
|
||||
content:"summary_large_image"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"twitter:title",
|
||||
content: socialTitle,
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"twitter:description",
|
||||
content: description
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"twitter:image",
|
||||
content:"https://lucide.dev/og.png"
|
||||
}],
|
||||
[
|
||||
'script',
|
||||
{
|
||||
src: 'https://analytics.lucide.dev/js/script.js',
|
||||
'data-domain': 'lucide.dev',
|
||||
defer: '',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:locale',
|
||||
content: 'en_US',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:type',
|
||||
content: 'website',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:site_name',
|
||||
content: title,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:title',
|
||||
content: socialTitle,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:description',
|
||||
content: description,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:url',
|
||||
content: 'https://lucide.dev',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image',
|
||||
content: 'https://lucide.dev/og.png',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image:width',
|
||||
content: '1200',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image:height',
|
||||
content: '630',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image:type',
|
||||
content: 'image/png',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:card',
|
||||
content: 'summary_large_image',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:title',
|
||||
content: socialTitle,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:description',
|
||||
content: description,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:image',
|
||||
content: 'https://lucide.dev/og.png',
|
||||
},
|
||||
],
|
||||
],
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
logo: {
|
||||
light: '/logo.light.svg',
|
||||
dark: '/logo.dark.svg'
|
||||
dark: '/logo.dark.svg',
|
||||
},
|
||||
nav: [
|
||||
{ text: 'Icons', link: '/icons/' },
|
||||
@@ -110,21 +159,21 @@ export default defineConfig({
|
||||
sidebar,
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://github.com/lucide-icons/lucide' },
|
||||
{ icon: 'discord', link: 'https://discord.gg/EH6nSts' }
|
||||
{ icon: 'discord', link: 'https://discord.gg/EH6nSts' },
|
||||
],
|
||||
footer: {
|
||||
message: 'Released under the ISC License.',
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`,
|
||||
},
|
||||
editLink: {
|
||||
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path'
|
||||
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path',
|
||||
},
|
||||
carbonAds: {
|
||||
code: 'CWYIC53U',
|
||||
placement: 'lucidedev'
|
||||
}
|
||||
placement: 'lucidedev',
|
||||
},
|
||||
},
|
||||
sitemap: {
|
||||
hostname: 'https://lucide.dev/'
|
||||
}
|
||||
})
|
||||
hostname: 'https://lucide.dev/',
|
||||
},
|
||||
});
|
||||
|
||||
186
docs/.vitepress/data/categoriesData.json
Normal file
186
docs/.vitepress/data/categoriesData.json
Normal file
@@ -0,0 +1,186 @@
|
||||
[
|
||||
{
|
||||
"name": "accessibility",
|
||||
"title": "Accessibility"
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"title": "Accounts & access"
|
||||
},
|
||||
{
|
||||
"name": "animals",
|
||||
"title": "Animals"
|
||||
},
|
||||
{
|
||||
"name": "arrows",
|
||||
"title": "Arrows"
|
||||
},
|
||||
{
|
||||
"name": "brands",
|
||||
"title": "Brands"
|
||||
},
|
||||
{
|
||||
"name": "buildings",
|
||||
"title": "Buildings"
|
||||
},
|
||||
{
|
||||
"name": "charts",
|
||||
"title": "Charts"
|
||||
},
|
||||
{
|
||||
"name": "communication",
|
||||
"title": "Communication"
|
||||
},
|
||||
{
|
||||
"name": "connectivity",
|
||||
"title": "Connectivity"
|
||||
},
|
||||
{
|
||||
"name": "currency",
|
||||
"title": "Currency"
|
||||
},
|
||||
{
|
||||
"name": "cursors",
|
||||
"title": "Cursors"
|
||||
},
|
||||
{
|
||||
"name": "design",
|
||||
"title": "Design"
|
||||
},
|
||||
{
|
||||
"name": "development",
|
||||
"title": "Coding & development"
|
||||
},
|
||||
{
|
||||
"name": "devices",
|
||||
"title": "Devices"
|
||||
},
|
||||
{
|
||||
"name": "emoji",
|
||||
"title": "Emoji"
|
||||
},
|
||||
{
|
||||
"name": "files",
|
||||
"title": "File icons"
|
||||
},
|
||||
{
|
||||
"name": "food-beverage",
|
||||
"title": "Food & beverage"
|
||||
},
|
||||
{
|
||||
"name": "furniture",
|
||||
"title": "Furniture"
|
||||
},
|
||||
{
|
||||
"name": "gaming",
|
||||
"title": "Gaming"
|
||||
},
|
||||
{
|
||||
"name": "home",
|
||||
"title": "Home"
|
||||
},
|
||||
{
|
||||
"name": "layout",
|
||||
"title": "Layout"
|
||||
},
|
||||
{
|
||||
"name": "mail",
|
||||
"title": "Mail"
|
||||
},
|
||||
{
|
||||
"name": "maps",
|
||||
"title": "Maps"
|
||||
},
|
||||
{
|
||||
"name": "maths",
|
||||
"title": "Maths"
|
||||
},
|
||||
{
|
||||
"name": "medical",
|
||||
"title": "Medical"
|
||||
},
|
||||
{
|
||||
"name": "money",
|
||||
"title": "Money"
|
||||
},
|
||||
{
|
||||
"name": "multimedia",
|
||||
"title": "Multimedia"
|
||||
},
|
||||
{
|
||||
"name": "nature",
|
||||
"title": "Nature"
|
||||
},
|
||||
{
|
||||
"name": "navigation",
|
||||
"title": "Navigation"
|
||||
},
|
||||
{
|
||||
"name": "notifications",
|
||||
"title": "Notifications"
|
||||
},
|
||||
{
|
||||
"name": "people",
|
||||
"title": "People"
|
||||
},
|
||||
{
|
||||
"name": "photography",
|
||||
"title": "Photography"
|
||||
},
|
||||
{
|
||||
"name": "science",
|
||||
"title": "Science"
|
||||
},
|
||||
{
|
||||
"name": "seasons",
|
||||
"title": "Seasons"
|
||||
},
|
||||
{
|
||||
"name": "security",
|
||||
"title": "Security"
|
||||
},
|
||||
{
|
||||
"name": "shapes",
|
||||
"title": "Shapes"
|
||||
},
|
||||
{
|
||||
"name": "shopping",
|
||||
"title": "Shopping"
|
||||
},
|
||||
{
|
||||
"name": "social",
|
||||
"title": "Social"
|
||||
},
|
||||
{
|
||||
"name": "sports",
|
||||
"title": "Sports"
|
||||
},
|
||||
{
|
||||
"name": "sustainability",
|
||||
"title": "Sustainability"
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"title": "Text formatting"
|
||||
},
|
||||
{
|
||||
"name": "time",
|
||||
"title": "Time & calendar"
|
||||
},
|
||||
{
|
||||
"name": "tools",
|
||||
"title": "Tools"
|
||||
},
|
||||
{
|
||||
"name": "transportation",
|
||||
"title": "Transportation"
|
||||
},
|
||||
{
|
||||
"name": "travel",
|
||||
"title": "Travel"
|
||||
},
|
||||
{
|
||||
"name": "weather",
|
||||
"title": "Weather"
|
||||
}
|
||||
]
|
||||
@@ -3,87 +3,160 @@
|
||||
"order": 0,
|
||||
"icon": "js",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide", "href": "https://www.npmjs.com/package/lucide" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide", "href": "https://www.npmjs.com/package/lucide" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide",
|
||||
"href": "https://www.npmjs.com/package/lucide"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide",
|
||||
"href": "https://www.npmjs.com/package/lucide"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-react": {
|
||||
"order": 1,
|
||||
"icon": "react",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-react", "href": "https://www.npmjs.com/package/lucide-react" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-react", "href": "https://www.npmjs.com/package/lucide-react" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-react",
|
||||
"href": "https://www.npmjs.com/package/lucide-react"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-react",
|
||||
"href": "https://www.npmjs.com/package/lucide-react"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-vue": {
|
||||
"order": 2,
|
||||
"icon": "vue",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-vue", "href": "https://www.npmjs.com/package/lucide-vue" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-vue", "href": "https://www.npmjs.com/package/lucide-vue" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-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": {
|
||||
"order": 3,
|
||||
"icon": "vue-next",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-vue-next", "href": "https://www.npmjs.com/package/lucide-vue-next" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-vue-next", "href": "https://www.npmjs.com/package/lucide-vue-next" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-vue-next",
|
||||
"href": "https://www.npmjs.com/package/lucide-vue-next"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-vue-next",
|
||||
"href": "https://www.npmjs.com/package/lucide-vue-next"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-svelte": {
|
||||
"order": 4,
|
||||
"icon": "svelte",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-svelte", "href": "https://www.npmjs.com/package/lucide-svelte" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-svelte", "href": "https://www.npmjs.com/package/lucide-svelte" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-svelte",
|
||||
"href": "https://www.npmjs.com/package/lucide-svelte"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-svelte",
|
||||
"href": "https://www.npmjs.com/package/lucide-svelte"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-solid": {
|
||||
"order": 4,
|
||||
"icon": "solid",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-solid", "href": "https://www.npmjs.com/package/lucide-solid" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-solid", "href": "https://www.npmjs.com/package/lucide-solid" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-solid",
|
||||
"href": "https://www.npmjs.com/package/lucide-solid"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-solid",
|
||||
"href": "https://www.npmjs.com/package/lucide-solid"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-preact": {
|
||||
"order": 5,
|
||||
"icon": "preact",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-preact", "href": "https://www.npmjs.com/package/lucide-preact" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-preact", "href": "https://www.npmjs.com/package/lucide-preact" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-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": {
|
||||
"order": 6,
|
||||
"icon": "react-native",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-react-native", "href": "https://www.npmjs.com/package/lucide-react-native" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-react-native", "href": "https://www.npmjs.com/package/lucide-react-native" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-react-native",
|
||||
"href": "https://www.npmjs.com/package/lucide-react-native"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-react-native",
|
||||
"href": "https://www.npmjs.com/package/lucide-react-native"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-angular": {
|
||||
"order": 7,
|
||||
"icon": "angular",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-angular", "href": "https://www.npmjs.com/package/lucide-angular" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-angular", "href": "https://www.npmjs.com/package/lucide-angular" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-angular",
|
||||
"href": "https://www.npmjs.com/package/lucide-angular"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-angular",
|
||||
"href": "https://www.npmjs.com/package/lucide-angular"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-static": {
|
||||
"order": 8,
|
||||
"icon": "svg",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-static", "href": "https://www.npmjs.com/package/lucide-static" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-static", "href": "https://www.npmjs.com/package/lucide-static" }
|
||||
]
|
||||
},
|
||||
"lucide-flutter": {
|
||||
"order": 9,
|
||||
"icon": "flutter",
|
||||
"shields": [
|
||||
{ "alt": "flutter", "src": "https://img.shields.io/pub/v/lucide_icons", "href": "https://img.shields.io/pub/v/lucide_icons" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-static",
|
||||
"href": "https://www.npmjs.com/package/lucide-static"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-static",
|
||||
"href": "https://www.npmjs.com/package/lucide-static"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
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 id = React.useId();
|
||||
return (
|
||||
<>
|
||||
<defs xmlns="http://www.w3.org/2000/svg">
|
||||
<pattern
|
||||
id="pattern"
|
||||
id={`pattern-${id}`}
|
||||
width=".1"
|
||||
height=".1"
|
||||
patternUnits="userSpaceOnUse"
|
||||
patternTransform="rotate(45 50 50)"
|
||||
>
|
||||
<line stroke={color} strokeWidth={0.1} y2={1} />
|
||||
<line stroke={color} strokeWidth={0.1} y2={1} />
|
||||
<line
|
||||
stroke={color}
|
||||
strokeWidth={0.1}
|
||||
y2={1}
|
||||
/>
|
||||
<line
|
||||
stroke={color}
|
||||
strokeWidth={0.1}
|
||||
y2={1}
|
||||
/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<mask id="svg-preview-backdrop-mask-outline" maskUnits="userSpaceOnUse">
|
||||
<g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} />
|
||||
<g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} />
|
||||
<mask
|
||||
id={`svg-preview-backdrop-mask-${id}`}
|
||||
maskUnits="userSpaceOnUse"
|
||||
>
|
||||
<g
|
||||
stroke="#fff"
|
||||
dangerouslySetInnerHTML={{ __html: backdropString }}
|
||||
/>
|
||||
<g dangerouslySetInnerHTML={{ __html: src }} />
|
||||
</mask>
|
||||
<mask id="svg-preview-backdrop-mask-fill" maskUnits="userSpaceOnUse">
|
||||
<g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} />
|
||||
<g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} />
|
||||
<g strokeWidth={1.75} dangerouslySetInnerHTML={{ __html: backdropString }} />
|
||||
<mask
|
||||
id={`svg-preview-backdrop-mask-outline-${id}`}
|
||||
maskUnits="userSpaceOnUse"
|
||||
>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="#fff"
|
||||
stroke="none"
|
||||
/>
|
||||
<g
|
||||
strokeWidth={1.75}
|
||||
dangerouslySetInnerHTML={{ __html: backdropString }}
|
||||
/>
|
||||
</mask>
|
||||
<g strokeWidth={2.25} stroke="url(#pattern)" mask={'url(#svg-preview-backdrop-mask-outline)'}>
|
||||
<rect x="0" y="0" width="24" height="24" fill="url(#pattern)" opacity={0.5} stroke="none" />
|
||||
<g mask={`url(#svg-preview-backdrop-mask-${id})`}>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
opacity={0.5}
|
||||
fill={`url(#pattern-${id})`}
|
||||
stroke="none"
|
||||
/>
|
||||
<g
|
||||
stroke={color}
|
||||
strokeWidth={2.25}
|
||||
opacity={0.75}
|
||||
dangerouslySetInnerHTML={{ __html: src }}
|
||||
/>
|
||||
<g
|
||||
stroke={color}
|
||||
strokeWidth={2.25}
|
||||
opacity={0.75}
|
||||
mask={`url(#svg-preview-backdrop-mask-outline-${id})`}
|
||||
dangerouslySetInnerHTML={{ __html: backdropString }}
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="url(#pattern)"
|
||||
stroke="none"
|
||||
mask={'url(#svg-preview-backdrop-mask-fill)'}
|
||||
/>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
fill={color}
|
||||
opacity={0.5}
|
||||
stroke="none"
|
||||
mask={'url(#svg-preview-backdrop-mask-fill)'}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,7 +10,11 @@ const Grid = ({
|
||||
strokeWidth: number;
|
||||
radius: number;
|
||||
} & PathProps<'stroke', 'strokeWidth'>) => (
|
||||
<g className="svg-preview-grid-group" strokeLinecap="butt" {...props}>
|
||||
<g
|
||||
className="svg-preview-grid-group"
|
||||
strokeLinecap="butt"
|
||||
{...props}
|
||||
>
|
||||
<rect
|
||||
className="svg-preview-grid-rect"
|
||||
width={24 - props.strokeWidth}
|
||||
@@ -21,11 +25,29 @@ const Grid = ({
|
||||
fill={fill}
|
||||
/>
|
||||
<path
|
||||
strokeDasharray={'0 0.1 ' + '0.1 0.15 '.repeat(11) + '0 0.15'}
|
||||
strokeWidth={0.1}
|
||||
d={
|
||||
props.d ||
|
||||
new Array(Math.floor(24 - 1))
|
||||
.fill(null)
|
||||
.flatMap((_, i) => [
|
||||
.map((_, i) => i)
|
||||
.filter((i) => i % 3 !== 2)
|
||||
.flatMap((i) => [
|
||||
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
|
||||
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
|
||||
])
|
||||
.join('')
|
||||
}
|
||||
/>
|
||||
<path
|
||||
d={
|
||||
props.d ||
|
||||
new Array(Math.floor(24 - 1))
|
||||
.fill(null)
|
||||
.map((_, i) => i)
|
||||
.filter((i) => i % 3 === 2)
|
||||
.flatMap((i) => [
|
||||
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
|
||||
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
|
||||
])
|
||||
@@ -44,15 +66,21 @@ const Shadow = ({
|
||||
paths: Path[];
|
||||
} & PathProps<'stroke' | 'strokeWidth' | 'strokeOpacity', 'd'>) => {
|
||||
const groupedPaths = Object.entries(
|
||||
paths.reduce((groups, val) => {
|
||||
const key = val.c.id;
|
||||
groups[key] = [...(groups[key] || []), val];
|
||||
return groups;
|
||||
}, {} as Record<number, Path[]>)
|
||||
paths.reduce(
|
||||
(groups, val) => {
|
||||
const key = val.c.id;
|
||||
groups[key] = [...(groups[key] || []), val];
|
||||
return groups;
|
||||
},
|
||||
{} as Record<number, Path[]>,
|
||||
),
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<g className="svg-preview-shadow-mask-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-shadow-mask-group"
|
||||
{...props}
|
||||
>
|
||||
{groupedPaths.map(([id, paths]) => (
|
||||
<mask
|
||||
id={`svg-preview-shadow-mask-${id}`}
|
||||
@@ -61,7 +89,15 @@ const Shadow = ({
|
||||
strokeWidth={props.strokeWidth}
|
||||
stroke="#000"
|
||||
>
|
||||
<rect x={0} y={0} width={24} height={24} fill="#fff" stroke="none" rx={radius} />
|
||||
<rect
|
||||
x={0}
|
||||
y={0}
|
||||
width={24}
|
||||
height={24}
|
||||
fill="#fff"
|
||||
stroke="none"
|
||||
rx={radius}
|
||||
/>
|
||||
<path
|
||||
d={paths
|
||||
.flatMap(({ prev, next }) => [
|
||||
@@ -74,9 +110,16 @@ const Shadow = ({
|
||||
</mask>
|
||||
))}
|
||||
</g>
|
||||
<g className="svg-preview-shadow-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-shadow-group"
|
||||
{...props}
|
||||
>
|
||||
{paths.map(({ d, c: { id } }, i) => (
|
||||
<path key={i} mask={`url(#svg-preview-shadow-mask-${id})`} d={d} />
|
||||
<path
|
||||
key={i}
|
||||
mask={`url(#svg-preview-shadow-mask-${id})`}
|
||||
d={d}
|
||||
/>
|
||||
))}
|
||||
<path
|
||||
d={paths
|
||||
@@ -94,9 +137,16 @@ const ColoredPath = ({
|
||||
paths,
|
||||
...props
|
||||
}: { paths: Path[]; colors: string[] } & PathProps<never, 'd' | 'stroke'>) => (
|
||||
<g className="svg-preview-colored-path-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-colored-path-group"
|
||||
{...props}
|
||||
>
|
||||
{paths.map(({ d, c }, i) => (
|
||||
<path key={i} d={d} stroke={colors[(c.name === 'path' ? i : c.id) % colors.length]} />
|
||||
<path
|
||||
key={i}
|
||||
d={d}
|
||||
stroke={colors[(c.name === 'path' ? i : c.id) % colors.length]}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
);
|
||||
@@ -138,7 +188,15 @@ const ControlPath = ({
|
||||
key={i}
|
||||
maskUnits="userSpaceOnUse"
|
||||
>
|
||||
<rect x="0" y="0" width="24" height="24" fill="#fff" stroke="none" rx={radius} />
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="#fff"
|
||||
stroke="none"
|
||||
rx={radius}
|
||||
/>
|
||||
<path d={`M${prev.x} ${prev.y}h.01`} />
|
||||
<path d={`M${next.x} ${next.y}h.01`} />
|
||||
</mask>
|
||||
@@ -146,7 +204,10 @@ const ControlPath = ({
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
<g className="svg-preview-control-path-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-control-path-group"
|
||||
{...props}
|
||||
>
|
||||
{controlPaths.map(({ d, showMarker }, i) => (
|
||||
<path
|
||||
key={i}
|
||||
@@ -155,18 +216,33 @@ const ControlPath = ({
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
<g className="svg-preview-control-path-marker-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-control-path-marker-group"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d={controlPaths
|
||||
.flatMap(({ prev, next, showMarker }) =>
|
||||
showMarker ? [`M${prev.x} ${prev.y}h.01`, `M${next.x} ${next.y}h.01`] : []
|
||||
showMarker ? [`M${prev.x} ${prev.y}h.01`, `M${next.x} ${next.y}h.01`] : [],
|
||||
)
|
||||
.join('')}
|
||||
/>
|
||||
{controlPaths.map(({ d, prev, next, startMarker, endMarker }, i) => (
|
||||
<React.Fragment key={i}>
|
||||
{startMarker && <circle cx={prev.x} cy={prev.y} r={pointSize / 2} />}
|
||||
{endMarker && <circle cx={next.x} cy={next.y} r={pointSize / 2} />}
|
||||
{startMarker && (
|
||||
<circle
|
||||
cx={prev.x}
|
||||
cy={prev.y}
|
||||
r={pointSize / 2}
|
||||
/>
|
||||
)}
|
||||
{endMarker && (
|
||||
<circle
|
||||
cx={next.x}
|
||||
cy={next.y}
|
||||
r={pointSize / 2}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</g>
|
||||
@@ -182,19 +258,38 @@ const Radii = ({
|
||||
any
|
||||
>) => {
|
||||
return (
|
||||
<g className="svg-preview-radii-group" {...props}>
|
||||
{paths
|
||||
.filter(({ circle }) => circle)
|
||||
.map(({ c, prev, next, circle: { x, y, r } }) =>
|
||||
c.name === 'circle' ? (
|
||||
<path d={`M${x} ${y}h.01`} />
|
||||
) : (
|
||||
<>
|
||||
<path d={`M${prev.x} ${prev.y} ${x} ${y} ${next.x} ${next.y}`} />
|
||||
<circle cy={y} cx={x} r={r} />
|
||||
</>
|
||||
)
|
||||
)}
|
||||
<g
|
||||
className="svg-preview-radii-group"
|
||||
{...props}
|
||||
>
|
||||
{paths.map(
|
||||
({ c, prev, next, circle }, i) =>
|
||||
circle && (
|
||||
<React.Fragment key={i}>
|
||||
{c.name !== 'circle' && (
|
||||
<path d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`} />
|
||||
)}
|
||||
<circle
|
||||
cy={circle.y}
|
||||
cx={circle.x}
|
||||
r={0.25}
|
||||
strokeDasharray="0"
|
||||
stroke={
|
||||
(Math.round(circle.x * 100) / 100) % 1 !== 0 ||
|
||||
(Math.round(circle.y * 100) / 100) % 1 !== 0
|
||||
? 'red'
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<circle
|
||||
cy={circle.y}
|
||||
cx={circle.x}
|
||||
r={circle.r}
|
||||
stroke={(Math.round(circle.r * 1000) / 1000) % 1 !== 0 ? 'red' : undefined}
|
||||
/>
|
||||
</React.Fragment>
|
||||
),
|
||||
)}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
@@ -206,15 +301,29 @@ const Handles = ({
|
||||
'strokeWidth' | 'stroke' | 'strokeDasharray' | 'strokeOpacity',
|
||||
any
|
||||
>) => {
|
||||
console.log(paths);
|
||||
return (
|
||||
<g className="svg-preview-handles-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-handles-group"
|
||||
{...props}
|
||||
>
|
||||
{paths.map(({ c, prev, next, cp1, cp2 }) => (
|
||||
<>
|
||||
{cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />}
|
||||
{cp1 && <circle cy={cp1.y} cx={cp1.x} r={0.25} />}
|
||||
{cp1 && (
|
||||
<circle
|
||||
cy={cp1.y}
|
||||
cx={cp1.x}
|
||||
r={0.25}
|
||||
/>
|
||||
)}
|
||||
{cp2 && <path d={`M${next.x} ${next.y} ${cp2.x} ${cp2.y}`} />}
|
||||
{cp2 && <circle cy={cp2.y} cx={cp2.x} r={0.25} />}
|
||||
{cp2 && (
|
||||
<circle
|
||||
cy={cp2.y}
|
||||
cx={cp2.x}
|
||||
r={0.25}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</g>
|
||||
@@ -258,9 +367,27 @@ const SvgPreview = React.forwardRef<
|
||||
{...props}
|
||||
>
|
||||
<style>{darkModeCss}</style>
|
||||
{showGrid && <Grid strokeWidth={0.1} stroke="#777" strokeOpacity={0.3} radius={1} />}
|
||||
<Shadow paths={paths} strokeWidth={4} stroke="#777" radius={1} strokeOpacity={0.15} />
|
||||
<Handles paths={paths} strokeWidth={0.12} stroke="#777" strokeOpacity={0.6} />
|
||||
{showGrid && (
|
||||
<Grid
|
||||
strokeWidth={0.1}
|
||||
stroke="#777"
|
||||
strokeOpacity={0.3}
|
||||
radius={1}
|
||||
/>
|
||||
)}
|
||||
<Shadow
|
||||
paths={paths}
|
||||
strokeWidth={4}
|
||||
stroke="#777"
|
||||
radius={1}
|
||||
strokeOpacity={0.15}
|
||||
/>
|
||||
<Handles
|
||||
paths={paths}
|
||||
strokeWidth={0.12}
|
||||
stroke="#777"
|
||||
strokeOpacity={0.6}
|
||||
/>
|
||||
<ColoredPath
|
||||
paths={paths}
|
||||
colors={[
|
||||
@@ -285,8 +412,19 @@ const SvgPreview = React.forwardRef<
|
||||
stroke="#777"
|
||||
strokeOpacity={0.3}
|
||||
/>
|
||||
<ControlPath radius={1} paths={paths} pointSize={1} stroke="#fff" strokeWidth={0.125} />
|
||||
<Handles paths={paths} strokeWidth={0.12} stroke="#FFF" strokeOpacity={0.3} />
|
||||
<ControlPath
|
||||
radius={1}
|
||||
paths={paths}
|
||||
pointSize={1}
|
||||
stroke="#fff"
|
||||
strokeWidth={0.125}
|
||||
/>
|
||||
<Handles
|
||||
paths={paths}
|
||||
strokeWidth={0.12}
|
||||
stroke="#FFF"
|
||||
strokeOpacity={0.3}
|
||||
/>
|
||||
{children}
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,7 @@ export type Path = {
|
||||
|
||||
export type PathProps<
|
||||
RequiredProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
|
||||
NeverProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>
|
||||
NeverProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
|
||||
> = Required<Pick<React.SVGProps<SVGElement & SVGRectElement & SVGCircleElement>, RequiredProps>> &
|
||||
Omit<
|
||||
React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>,
|
||||
|
||||
@@ -51,7 +51,7 @@ export const getCommands = (src: string) =>
|
||||
getNodes(src)
|
||||
.map(convertToPathNode)
|
||||
.flatMap(({ d, name }, idx) =>
|
||||
new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name }))
|
||||
new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })),
|
||||
);
|
||||
|
||||
export const getPaths = (src: string) => {
|
||||
@@ -60,10 +60,10 @@ export const getPaths = (src: string) => {
|
||||
let prev: Point | undefined = undefined;
|
||||
let start: Point | undefined = undefined;
|
||||
const addPath = (
|
||||
c: typeof commands[number],
|
||||
c: (typeof commands)[number],
|
||||
next: Point,
|
||||
d?: string,
|
||||
extras?: { circle?: Path['circle']; cp1?: Path['cp1']; cp2?: Path['cp2'] }
|
||||
extras?: { circle?: Path['circle']; cp1?: Path['cp1']; cp2?: Path['cp2'] },
|
||||
) => {
|
||||
assert(prev);
|
||||
paths.push({
|
||||
@@ -151,9 +151,9 @@ export const getPaths = (src: string) => {
|
||||
y2: c.y2,
|
||||
})}`,
|
||||
{
|
||||
cp1: reflectedCp1,
|
||||
cp1: { x: prev.x - reflectedCp1.x, y: prev.y - reflectedCp1.y },
|
||||
cp2: { x: c.x2, y: c.y2 },
|
||||
}
|
||||
},
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -169,7 +169,7 @@ export const getPaths = (src: string) => {
|
||||
assert(prev);
|
||||
const backTrackCP = (
|
||||
index: number,
|
||||
currentPoint: { x: number; y: number }
|
||||
currentPoint: { x: number; y: number },
|
||||
): { x: number; y: number } => {
|
||||
const previousCommand = commands[index - 1];
|
||||
if (!previousCommand) {
|
||||
@@ -211,7 +211,7 @@ export const getPaths = (src: string) => {
|
||||
{
|
||||
cp1: { x: prevCP.x, y: prevCP.y },
|
||||
cp2: { x: prevCP.x, y: prevCP.y },
|
||||
}
|
||||
},
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -226,13 +226,13 @@ export const getPaths = (src: string) => {
|
||||
c.lArcFlag,
|
||||
c.sweepFlag,
|
||||
c.x,
|
||||
c.y
|
||||
c.y,
|
||||
);
|
||||
addPath(
|
||||
c,
|
||||
c,
|
||||
`M ${prev.x} ${prev.y} A${c.rX} ${c.rY} ${c.xRot} ${c.lArcFlag} ${c.sweepFlag} ${c.x} ${c.y}`,
|
||||
{ circle: c.rX === c.rY ? { ...center, r: c.rX } : undefined }
|
||||
{ circle: c.rX === c.rY ? { ...center, r: c.rX } : undefined },
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -253,7 +253,7 @@ export const arcEllipseCenter = (
|
||||
fa: number,
|
||||
fs: number,
|
||||
x2: number,
|
||||
y2: number
|
||||
y2: number,
|
||||
) => {
|
||||
const phi = (a * Math.PI) / 180;
|
||||
|
||||
@@ -280,7 +280,7 @@ export const arcEllipseCenter = (
|
||||
sign *
|
||||
Math.sqrt(
|
||||
Math.max(rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p, 0) /
|
||||
(rx * rx * y1p * y1p + ry * ry * x1p * x1p)
|
||||
(rx * rx * y1p * y1p + ry * ry * x1p * x1p),
|
||||
);
|
||||
|
||||
const V2 = [(rx * y1p) / ry, (-ry * x1p) / rx];
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import {Category, IconEntity} from "../theme/types";
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { Category, IconEntity } from '../theme/types';
|
||||
|
||||
const directory = path.join(process.cwd(), "../categories");
|
||||
const directory = path.join(process.cwd(), '../categories');
|
||||
|
||||
export function getAllCategoryFiles(): Category[] {
|
||||
const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json');
|
||||
|
||||
return fileNames.map((fileName) => {
|
||||
const name = path.basename(fileName, '.json')
|
||||
const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8')
|
||||
const name = path.basename(fileName, '.json');
|
||||
const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8');
|
||||
|
||||
const parsedFileContent = JSON.parse(fileContent)
|
||||
const parsedFileContent = JSON.parse(fileContent);
|
||||
|
||||
return {
|
||||
name,
|
||||
title: parsedFileContent.title,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function mapCategoryIconCount(categories: Category[], icons: { categories: IconEntity['categories'] }[]) {
|
||||
export function mapCategoryIconCount(
|
||||
categories: Category[],
|
||||
icons: { categories: IconEntity['categories'] }[],
|
||||
) {
|
||||
return categories.map((category) => ({
|
||||
...category,
|
||||
iconCount: icons.reduce((acc, curr) => (curr.categories.includes(category.name) ? ++acc : acc), 0)
|
||||
}))
|
||||
iconCount: icons.reduce(
|
||||
(acc, curr) => (curr.categories.includes(category.name) ? ++acc : acc),
|
||||
0,
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import {
|
||||
bundledLanguages,
|
||||
type ThemeRegistration
|
||||
} from 'shikiji'
|
||||
import {
|
||||
getHighlighter,
|
||||
} from 'shikiji'
|
||||
|
||||
import { bundledLanguages, type ThemeRegistration } from 'shikiji';
|
||||
import { getHighlighter } from 'shikiji';
|
||||
|
||||
type CodeExampleType = {
|
||||
title: string,
|
||||
language: string,
|
||||
code: string,
|
||||
}[]
|
||||
title: string;
|
||||
language: string;
|
||||
code: string;
|
||||
}[];
|
||||
|
||||
const getIconCodes = (): CodeExampleType => {
|
||||
return [
|
||||
{
|
||||
language: 'html',
|
||||
title: 'HTML',
|
||||
code: `<i data-lucide="Name"></i>`
|
||||
language: 'js',
|
||||
title: 'Vanilla',
|
||||
code: `\
|
||||
import { createIcons, icons } from 'lucide';
|
||||
|
||||
createIcons({ icons });
|
||||
|
||||
document.body.append('<i data-lucide="$Name"></i>');\
|
||||
`,
|
||||
},
|
||||
{
|
||||
language: 'tsx',
|
||||
title: 'React',
|
||||
code: `import { PascalCase } from 'lucide-react';
|
||||
code: `import { $PascalCase } from 'lucide-react';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<PascalCase />
|
||||
<$PascalCase />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -38,11 +38,11 @@ export default App;
|
||||
language: 'vue',
|
||||
title: 'Vue',
|
||||
code: `<script setup>
|
||||
import { PascalCase } from 'lucide-vue-next';
|
||||
import { $PascalCase } from 'lucide-vue-next';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PascalCase />
|
||||
<$PascalCase />
|
||||
</template>
|
||||
`,
|
||||
},
|
||||
@@ -50,20 +50,20 @@ export default App;
|
||||
language: 'svelte',
|
||||
title: 'Svelte',
|
||||
code: `<script>
|
||||
import { PascalCase } from 'lucide-svelte';
|
||||
import { $PascalCase } from 'lucide-svelte';
|
||||
</script>
|
||||
|
||||
<PascalCase />
|
||||
<$PascalCase />
|
||||
`,
|
||||
},
|
||||
{
|
||||
language: 'tsx',
|
||||
title: 'Preact',
|
||||
code: `import { PascalCase } from 'lucide-preact';
|
||||
code: `import { $PascalCase } from 'lucide-preact';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<PascalCase />
|
||||
<$PascalCase />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -73,11 +73,11 @@ export default App;
|
||||
{
|
||||
language: 'tsx',
|
||||
title: 'Solid',
|
||||
code: `import { PascalCase } from 'lucide-solid';
|
||||
code: `import { $PascalCase } from 'lucide-solid';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<PascalCase />
|
||||
<$PascalCase />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -88,16 +88,16 @@ export default App;
|
||||
language: 'tsx',
|
||||
title: 'Angular',
|
||||
code: `// app.module.ts
|
||||
import { LucideAngularModule, PascalCase } from 'lucide-angular';
|
||||
import { LucideAngularModule, $PascalCase } from 'lucide-angular';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
LucideAngularModule.pick({ PascalCase })
|
||||
LucideAngularModule.pick({ $PascalCase })
|
||||
],
|
||||
})
|
||||
|
||||
// app.component.html
|
||||
<lucide-icon name="Name"></lucide-icon>
|
||||
<lucide-icon name="$Name"></lucide-icon>
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -107,38 +107,39 @@ import { LucideAngularModule, PascalCase } from 'lucide-angular';
|
||||
@import ('~lucide-static/font/Lucide.css');
|
||||
</style>
|
||||
|
||||
<div class="icon-Name"></div>
|
||||
<div class="icon-$Name"></div>
|
||||
`,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export type ThemeOptions =
|
||||
| ThemeRegistration
|
||||
| { light: ThemeRegistration; dark: ThemeRegistration }
|
||||
| { light: ThemeRegistration; dark: ThemeRegistration };
|
||||
|
||||
const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
||||
const highlighter = await getHighlighter({
|
||||
themes: ['github-light', 'github-dark'],
|
||||
langs: Object.keys(bundledLanguages)
|
||||
})
|
||||
langs: Object.keys(bundledLanguages),
|
||||
});
|
||||
|
||||
const highlightedCode = highlighter.codeToHtml(code, {
|
||||
lang,
|
||||
themes: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark'
|
||||
},
|
||||
defaultColor: false
|
||||
}).replace('shiki-themes', 'shiki-themes vp-code')
|
||||
const highlightedCode = highlighter
|
||||
.codeToHtml(code, {
|
||||
lang,
|
||||
themes: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark',
|
||||
},
|
||||
defaultColor: false,
|
||||
})
|
||||
.replace('shiki-themes', 'shiki-themes vp-code');
|
||||
|
||||
return `<div class="language-${lang} ${active ? 'active' : ''}">
|
||||
<button title="Copy Code" class="copy"></button>
|
||||
<span class="lang">${lang}</span>
|
||||
${highlightedCode}
|
||||
</div>`
|
||||
}
|
||||
|
||||
</div>`;
|
||||
};
|
||||
|
||||
export default async function createCodeExamples() {
|
||||
const codes = getIconCodes();
|
||||
@@ -153,7 +154,7 @@ export default async function createCodeExamples() {
|
||||
language: language,
|
||||
code: codeString,
|
||||
};
|
||||
})
|
||||
});
|
||||
|
||||
return Promise.all(codeExamplePromises);
|
||||
}
|
||||
161
docs/.vitepress/lib/codeExamples/createLabCodeExamples.ts
Normal file
161
docs/.vitepress/lib/codeExamples/createLabCodeExamples.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { bundledLanguages, type ThemeRegistration } from 'shikiji';
|
||||
import { getHighlighter } from 'shikiji';
|
||||
|
||||
type CodeExampleType = {
|
||||
title: string;
|
||||
language: string;
|
||||
code: string;
|
||||
}[];
|
||||
|
||||
const getIconCodes = (): CodeExampleType => {
|
||||
return [
|
||||
{
|
||||
language: 'js',
|
||||
title: 'Vanilla',
|
||||
code: `\
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { $CamelCase } from '@lucide/lab';
|
||||
|
||||
createIcons({
|
||||
icons: {
|
||||
$CamelCase
|
||||
}
|
||||
});
|
||||
|
||||
document.body.append('<i data-lucide="$Name"></i>');\
|
||||
`,
|
||||
},
|
||||
{
|
||||
language: 'tsx',
|
||||
title: 'React',
|
||||
code: `import { Icon } from 'lucide-react';
|
||||
import { $CamelCase } from '@lucide/lab';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Icon iconNode={$CamelCase} />
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
`,
|
||||
},
|
||||
{
|
||||
language: 'vue',
|
||||
title: 'Vue',
|
||||
code: `<script setup>
|
||||
import { Icon } from 'lucide-vue-next';
|
||||
import { $CamelCase } from '@lucide/lab';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Icon :iconNode="burger" />
|
||||
</template>
|
||||
`,
|
||||
},
|
||||
{
|
||||
language: 'svelte',
|
||||
title: 'Svelte',
|
||||
code: `<script>
|
||||
import { Icon } from 'lucide-svelte';
|
||||
import { $CamelCase } from '@lucide/lab';
|
||||
</script>
|
||||
|
||||
<Icon iconNode={burger} />
|
||||
`,
|
||||
},
|
||||
{
|
||||
language: 'tsx',
|
||||
title: 'Preact',
|
||||
code: `import { Icon } from 'lucide-preact';
|
||||
import { $CamelCase } from '@lucide/lab';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Icon iconNode={$CamelCase} />
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
`,
|
||||
},
|
||||
{
|
||||
language: 'tsx',
|
||||
title: 'Solid',
|
||||
code: `import { Icon } from 'lucide-solid';
|
||||
import { $CamelCase } from '@lucide/lab';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Icon iconNode={$CamelCase} />
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
`,
|
||||
},
|
||||
{
|
||||
language: 'tsx',
|
||||
title: 'Angular',
|
||||
code: `// app.module.ts
|
||||
import { LucideAngularModule } from 'lucide-angular';
|
||||
import { $CamelCase } from '@lucide/lab';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
LucideAngularModule.pick({ $CamelCase })
|
||||
],
|
||||
})
|
||||
|
||||
// app.component.html
|
||||
<lucide-icon name="$CamelCase"></lucide-icon>
|
||||
`,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export type ThemeOptions =
|
||||
| ThemeRegistration
|
||||
| { light: ThemeRegistration; dark: ThemeRegistration };
|
||||
|
||||
const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
||||
const highlighter = await getHighlighter({
|
||||
themes: ['github-light', 'github-dark'],
|
||||
langs: Object.keys(bundledLanguages),
|
||||
});
|
||||
|
||||
const highlightedCode = highlighter
|
||||
.codeToHtml(code, {
|
||||
lang,
|
||||
themes: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark',
|
||||
},
|
||||
defaultColor: false,
|
||||
})
|
||||
.replace('shiki-themes', 'shiki-themes vp-code');
|
||||
|
||||
return `<div class="language-${lang} ${active ? 'active' : ''}">
|
||||
<button title="Copy Code" class="copy"></button>
|
||||
<span class="lang">${lang}</span>
|
||||
${highlightedCode}
|
||||
</div>`;
|
||||
};
|
||||
|
||||
export default async function createCodeExamples() {
|
||||
const codes = getIconCodes();
|
||||
|
||||
const codeExamplePromises = codes.map(async ({ title, language, code }, index) => {
|
||||
const isFirst = index === 0;
|
||||
|
||||
const codeString = await highLightCode(code, language, isFirst);
|
||||
|
||||
return {
|
||||
title,
|
||||
language: language,
|
||||
code: codeString,
|
||||
};
|
||||
});
|
||||
|
||||
return Promise.all(codeExamplePromises);
|
||||
}
|
||||
32
docs/.vitepress/lib/codeExamples/highLightCode.ts
Normal file
32
docs/.vitepress/lib/codeExamples/highLightCode.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { bundledLanguages, type ThemeRegistration } from 'shikiji';
|
||||
import { getHighlighter } from 'shikiji';
|
||||
|
||||
export type ThemeOptions =
|
||||
| ThemeRegistration
|
||||
| { light: ThemeRegistration; dark: ThemeRegistration };
|
||||
|
||||
const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
||||
const highlighter = await getHighlighter({
|
||||
themes: ['github-light', 'github-dark'],
|
||||
langs: Object.keys(bundledLanguages),
|
||||
});
|
||||
|
||||
const highlightedCode = highlighter
|
||||
.codeToHtml(code, {
|
||||
lang,
|
||||
themes: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark',
|
||||
},
|
||||
defaultColor: false,
|
||||
})
|
||||
.replace('shiki-themes', 'shiki-themes vp-code');
|
||||
|
||||
return `<div class="language-${lang} ${active ? 'active' : ''}">
|
||||
<button title="Copy Code" class="copy"></button>
|
||||
<span class="lang">${lang}</span>
|
||||
${highlightedCode}
|
||||
</div>`;
|
||||
};
|
||||
|
||||
export default highLightCode;
|
||||
5
docs/.vitepress/lib/codeExamples/types.ts
Normal file
5
docs/.vitepress/lib/codeExamples/types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export type CodeExampleType = {
|
||||
title: string;
|
||||
language: string;
|
||||
code: string;
|
||||
}[];
|
||||
@@ -1,38 +1,36 @@
|
||||
import { promises as fs, constants } from 'fs';
|
||||
import path from 'path';
|
||||
import yaml from 'js-yaml'
|
||||
import { PackageItem } from '../theme/types';
|
||||
|
||||
const fileExist = (filePath) => fs.access(filePath, constants.F_OK).then(() => true).catch(() => false)
|
||||
const fileExist = (filePath) =>
|
||||
fs
|
||||
.access(filePath, constants.F_OK)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
const fetchPackages = async (): Promise<PackageItem[]> => {
|
||||
const docsDir = path.resolve(process.cwd(), '../packages');
|
||||
const fileNames = await (await fs.readdir(docsDir)).map(filename => ({filename, directory: docsDir}))
|
||||
const fileNames = await (
|
||||
await fs.readdir(docsDir)
|
||||
).map((filename) => ({ filename, directory: docsDir }));
|
||||
|
||||
const packageJsons = await Promise.all(fileNames.map( async ({filename, directory}) => {
|
||||
const filePath = path.resolve(directory, filename)
|
||||
const fileStat = await fs.lstat(filePath);
|
||||
const packageJsons = await Promise.all(
|
||||
fileNames.map(async ({ filename, directory }) => {
|
||||
const filePath = path.resolve(directory, filename);
|
||||
const fileStat = await fs.lstat(filePath);
|
||||
|
||||
if(!fileStat.isDirectory()) return null;
|
||||
if (!fileStat.isDirectory()) return null;
|
||||
|
||||
const jsonFilePath = path.resolve(filePath, 'package.json')
|
||||
if (await fileExist(jsonFilePath)) {
|
||||
return JSON.parse(
|
||||
await fs.readFile(jsonFilePath, 'utf-8')
|
||||
)
|
||||
}
|
||||
const jsonFilePath = path.resolve(filePath, 'package.json');
|
||||
if (await fileExist(jsonFilePath)) {
|
||||
return JSON.parse(await fs.readFile(jsonFilePath, 'utf-8'));
|
||||
}
|
||||
|
||||
const ymlFilePath = path.resolve(filePath, 'pubspec.yaml')
|
||||
if(await fileExist(ymlFilePath)) {
|
||||
return yaml.load(
|
||||
await fs.readFile(ymlFilePath, 'utf-8')
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
);
|
||||
|
||||
return null
|
||||
}))
|
||||
|
||||
return packageJsons
|
||||
}
|
||||
return packageJsons;
|
||||
};
|
||||
|
||||
export default fetchPackages;
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
export type IconContent = [icon: string, src:string];
|
||||
export type IconContent = [icon: string, src: string];
|
||||
|
||||
async function generateZip(icons: IconContent[]) {
|
||||
const JSZip = (await import('jszip')).default
|
||||
const JSZip = (await import('jszip')).default;
|
||||
|
||||
const zip = new JSZip();
|
||||
|
||||
const addingZipPromises = icons.map(([name, src]) =>
|
||||
zip.file(`${name}.svg`, src),
|
||||
);
|
||||
const addingZipPromises = icons.map(([name, src]) => zip.file(`${name}.svg`, src));
|
||||
|
||||
await Promise.all(addingZipPromises)
|
||||
await Promise.all(addingZipPromises);
|
||||
|
||||
return zip.generateAsync({ type: 'blob' });
|
||||
}
|
||||
|
||||
export default generateZip
|
||||
export default generateZip;
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { createLucideIcon } from "lucide-react/src/lucide-react"
|
||||
import { type LucideProps, type IconNode } from "lucide-react/src/createLucideIcon"
|
||||
import { IconEntity } from "../theme/types"
|
||||
import { createLucideIcon } from 'lucide-react/src/lucide-react';
|
||||
import { type LucideProps, type IconNode } from 'lucide-react/src/createLucideIcon';
|
||||
import { IconEntity } from '../theme/types';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import { IconContent } from "./generateZip";
|
||||
import { IconContent } from './generateZip';
|
||||
|
||||
const getFallbackZip = (icons: IconEntity[], params: LucideProps) => {
|
||||
return icons
|
||||
.map<IconContent>((icon) => {
|
||||
const Icon = createLucideIcon(icon.name, icon.iconNode as IconNode)
|
||||
const src = renderToStaticMarkup(<Icon {...params} />)
|
||||
return [icon.name, src]
|
||||
})
|
||||
}
|
||||
return icons.map<IconContent>((icon) => {
|
||||
const Icon = createLucideIcon(icon.name, icon.iconNode as IconNode);
|
||||
const src = renderToStaticMarkup(<Icon {...params} />);
|
||||
return [icon.name, src];
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export default getFallbackZip
|
||||
export default getFallbackZip;
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { IconNodeWithKeys } from "../theme/types";
|
||||
import iconNodes from '../data/iconNodes'
|
||||
import releaseMeta from "../data/releaseMetaData.json";
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { IconNodeWithKeys } from '../theme/types';
|
||||
import iconNodes from '../data/iconNodes';
|
||||
import releaseMeta from '../data/releaseMetaData.json';
|
||||
|
||||
const DATE_OF_FORK = '2020-06-08T16:39:52+0100';
|
||||
|
||||
const directory = path.join(process.cwd(), "../icons");
|
||||
const directory = path.join(process.cwd(), '../icons');
|
||||
|
||||
export interface GetDataOptions {
|
||||
withChildKeys?: boolean
|
||||
withChildKeys?: boolean;
|
||||
}
|
||||
|
||||
export async function getData(name: string) {
|
||||
const jsonPath = path.join(directory, `${name}.json`);
|
||||
const jsonContent = fs.readFileSync(jsonPath, "utf8");
|
||||
const jsonContent = fs.readFileSync(jsonPath, 'utf8');
|
||||
const { tags, categories, contributors } = JSON.parse(jsonContent);
|
||||
|
||||
const iconNode = iconNodes[name]
|
||||
const iconNode = iconNodes[name];
|
||||
|
||||
const releaseData = releaseMeta?.[name] ?? {
|
||||
"createdRelease": {
|
||||
"version": "0.0.0",
|
||||
"date": DATE_OF_FORK
|
||||
createdRelease: {
|
||||
version: '0.0.0',
|
||||
date: DATE_OF_FORK,
|
||||
},
|
||||
"changedRelease": {
|
||||
"version": "0.0.0",
|
||||
"date": DATE_OF_FORK
|
||||
}
|
||||
}
|
||||
changedRelease: {
|
||||
version: '0.0.0',
|
||||
date: DATE_OF_FORK,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
name,
|
||||
@@ -36,11 +36,11 @@ export async function getData(name: string) {
|
||||
categories,
|
||||
iconNode,
|
||||
contributors,
|
||||
...releaseData
|
||||
...releaseData,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getAllData(): Promise<{ name: string, iconNode: IconNodeWithKeys}[]> {
|
||||
export async function getAllData(): Promise<{ name: string; iconNode: IconNodeWithKeys }[]> {
|
||||
const names = Object.keys(iconNodes);
|
||||
|
||||
return Promise.all(names.map((name) => getData(name)));
|
||||
|
||||
@@ -1,135 +1,131 @@
|
||||
import { DefaultTheme, UserConfig } from "vitepress"
|
||||
import { DefaultTheme, UserConfig } from 'vitepress';
|
||||
|
||||
const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
|
||||
'guide':[
|
||||
guide: [
|
||||
{
|
||||
text: 'Introduction',
|
||||
items: [
|
||||
{ text: 'What is lucide?', link: '/guide/' },
|
||||
{ text: 'Installation', link: '/guide/installation' },
|
||||
{ text: 'Comparison', link: '/guide/comparison' }
|
||||
]
|
||||
{ text: 'Comparison', link: '/guide/comparison' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Basics',
|
||||
items: [
|
||||
{
|
||||
text: 'Color',
|
||||
link: '/guide/basics/color'
|
||||
link: '/guide/basics/color',
|
||||
},
|
||||
{
|
||||
text: 'Sizing',
|
||||
link: '/guide/basics/sizing'
|
||||
link: '/guide/basics/sizing',
|
||||
},
|
||||
{
|
||||
text: 'Stroke width',
|
||||
link: '/guide/basics/stroke-width'
|
||||
link: '/guide/basics/stroke-width',
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
// TODO: Add this section
|
||||
// {
|
||||
// text: 'Advanced',
|
||||
// items: [
|
||||
// {
|
||||
// text: 'Accessibility',
|
||||
// link: '/guide/advanced/accessibility'
|
||||
// },
|
||||
// {
|
||||
// text: 'Global styling',
|
||||
// },
|
||||
// {
|
||||
// text: 'Animations',
|
||||
// },
|
||||
// {
|
||||
// text: 'Filled icons',
|
||||
// },
|
||||
// {
|
||||
// text: 'Combining icons',
|
||||
// },
|
||||
// {
|
||||
// text: 'Dynamic imports'
|
||||
// },
|
||||
// // {
|
||||
// // text: 'Auto importing'
|
||||
// // },
|
||||
// ]
|
||||
// },
|
||||
{
|
||||
text: 'Advanced',
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility',
|
||||
link: '/guide/advanced/accessibility',
|
||||
},
|
||||
{
|
||||
text: 'Global styling',
|
||||
link: '/guide/advanced/global-styling',
|
||||
},
|
||||
// {
|
||||
// text: 'Animations',
|
||||
// },
|
||||
{
|
||||
text: 'Filled icons',
|
||||
link: '/guide/advanced/filled-icons',
|
||||
},
|
||||
// {
|
||||
// text: 'Combining icons',
|
||||
// },
|
||||
// {
|
||||
// text: 'Dynamic imports'
|
||||
// },
|
||||
// {
|
||||
// text: 'Auto importing'
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Packages',
|
||||
items: [
|
||||
{
|
||||
text: 'Lucide',
|
||||
link: '/guide/packages/lucide'
|
||||
link: '/guide/packages/lucide',
|
||||
},
|
||||
{
|
||||
text: 'Lucide React',
|
||||
link: '/guide/packages/lucide-react'
|
||||
link: '/guide/packages/lucide-react',
|
||||
},
|
||||
{
|
||||
text: 'Lucide React Native',
|
||||
link: '/guide/packages/lucide-react-native'
|
||||
link: '/guide/packages/lucide-react-native',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Vue',
|
||||
link: '/guide/packages/lucide-vue'
|
||||
},
|
||||
{
|
||||
text: 'Lucide Vue Next (Vue 3)',
|
||||
link: '/guide/packages/lucide-vue-next'
|
||||
link: '/guide/packages/lucide-vue-next',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Svelte',
|
||||
link: '/guide/packages/lucide-svelte'
|
||||
link: '/guide/packages/lucide-svelte',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Solid',
|
||||
link: '/guide/packages/lucide-solid'
|
||||
link: '/guide/packages/lucide-solid',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Preact',
|
||||
link: '/guide/packages/lucide-preact'
|
||||
link: '/guide/packages/lucide-preact',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Angular',
|
||||
link: '/guide/packages/lucide-angular'
|
||||
link: '/guide/packages/lucide-angular',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Static',
|
||||
link: '/guide/packages/lucide-static'
|
||||
link: '/guide/packages/lucide-static',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Flutter',
|
||||
link: '/guide/packages/lucide-flutter'
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Contributing',
|
||||
items: [
|
||||
{
|
||||
text: 'Icon Design Principles',
|
||||
link: '/guide/design/icon-design-guide'
|
||||
link: '/guide/design/icon-design-guide',
|
||||
},
|
||||
{
|
||||
text: 'Designing in Illustrator',
|
||||
link: '/guide/design/illustrator-guide'
|
||||
link: '/guide/design/illustrator-guide',
|
||||
},
|
||||
{
|
||||
text: 'Designing in InkScape',
|
||||
link: '/guide/design/inkscape-guide'
|
||||
link: '/guide/design/inkscape-guide',
|
||||
},
|
||||
{
|
||||
text: 'Designing in Figma',
|
||||
link: '/guide/design/figma-guide'
|
||||
link: '/guide/design/figma-guide',
|
||||
},
|
||||
]
|
||||
{
|
||||
text: 'Designing in Affinity Designer',
|
||||
link: '/guide/design/affinity-designer-guide',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
// This should be here to keep the sidebar shown on the icons page
|
||||
'icons': [
|
||||
{ text: '', link: '/' },
|
||||
],
|
||||
}
|
||||
icons: [{ text: '', link: '/' }],
|
||||
};
|
||||
|
||||
export default sidebar
|
||||
export default sidebar;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useRouter } from 'vitepress';
|
||||
|
||||
const { go } = useRouter()
|
||||
@@ -8,9 +7,16 @@ const props = defineProps<{
|
||||
href?: string
|
||||
}>()
|
||||
|
||||
const isExternal = computed(() => props.href?.startsWith('http') ?? false)
|
||||
|
||||
const component = computed(() => props.href ? 'a' : 'div')
|
||||
const target = computed(() => isExternal.value ? '_blank' : undefined)
|
||||
const rel = computed(() => isExternal.value ? 'noreferrer noopener' : undefined)
|
||||
|
||||
|
||||
const onClick = computed(() => {
|
||||
if(!props.href || isExternal) return
|
||||
return go(props.href)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -18,14 +24,16 @@ const component = computed(() => props.href ? 'a' : 'div')
|
||||
:is="component"
|
||||
:href="href"
|
||||
class="badge"
|
||||
@click="props?.href ? go(href) : undefined"
|
||||
:target="target"
|
||||
:rel="rel"
|
||||
@click="onClick"
|
||||
>
|
||||
<slot/>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.badge, a.badge {
|
||||
.badge, a.badge, .vp-doc a.badge {
|
||||
display: block;
|
||||
border: 1px solid transparent;
|
||||
text-align: center;
|
||||
@@ -60,4 +68,6 @@ const component = computed(() => props.href ? 'a' : 'div')
|
||||
/* color: var(--vp-button-alt-active-text);
|
||||
background-color: var(--vp-button-alt-active-bg); */
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
@@ -94,8 +94,7 @@ const ChevronUp = createLucideIcon('ChevronUp', chevronUp)
|
||||
.menu {
|
||||
position: relative;
|
||||
}
|
||||
.menu-items {
|
||||
--menu-offset: 44px;
|
||||
ul.menu-items {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -103,7 +102,7 @@ const ChevronUp = createLucideIcon('ChevronUp', chevronUp)
|
||||
padding: 12px;
|
||||
min-width: 128px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background-color: var(--vp-c-bg);
|
||||
background-color: var(--vp-c-bg-elv);
|
||||
box-shadow: var(--vp-shadow-3);
|
||||
transition: background-color 0.5s;
|
||||
max-height: calc(100vh - var(--vp-nav-height));
|
||||
@@ -124,11 +123,12 @@ const ChevronUp = createLucideIcon('ChevronUp', chevronUp)
|
||||
color: var(--vp-c-text-1);
|
||||
white-space: nowrap;
|
||||
transition: background-color .25s,color .25s;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
color: var(--vp-c-brand);
|
||||
background-color: var(--vp-c-bg-elv-mute);
|
||||
background-color: var(--vp-c-default-soft);
|
||||
}
|
||||
|
||||
.menu-item:active {
|
||||
@@ -170,11 +170,11 @@ const ChevronUp = createLucideIcon('ChevronUp', chevronUp)
|
||||
}
|
||||
|
||||
.menu-items.bottom {
|
||||
top: var(--menu-offset);
|
||||
top: 32px;
|
||||
}
|
||||
|
||||
.menu-items.top {
|
||||
bottom: var(--menu-offset);
|
||||
bottom: 48px;
|
||||
}
|
||||
|
||||
.arrow-up-button.top::before {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="grid">
|
||||
<div class="card-grid-flex">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.grid {
|
||||
<style>
|
||||
.card-grid-flex {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: stretch;
|
||||
@@ -15,20 +15,20 @@
|
||||
margin: -8px;
|
||||
}
|
||||
|
||||
.grid > * {
|
||||
.card-grid-flex > * {
|
||||
flex-basis: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.grid > * {
|
||||
.card-grid-flex > * {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.grid > * {
|
||||
.card-grid-flex > * {
|
||||
flex-basis: 33.33%;
|
||||
}
|
||||
}
|
||||
|
||||
90
docs/.vitepress/theme/components/base/Checkbox.vue
Normal file
90
docs/.vitepress/theme/components/base/Checkbox.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
label: string
|
||||
id: string
|
||||
value: string
|
||||
modelValue: string | string[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['change', 'input', 'update:modelValue'])
|
||||
|
||||
const model = computed({
|
||||
get: () => {
|
||||
if (Array.isArray(props.modelValue)) {
|
||||
return props.modelValue.includes(props.value)
|
||||
}
|
||||
return props.modelValue === props.value
|
||||
|
||||
},
|
||||
set: (value: string) => {
|
||||
if (Array.isArray(props.modelValue)) {
|
||||
const newValue = [...props.modelValue]
|
||||
const index = newValue.indexOf(props.value)
|
||||
if (value) {
|
||||
if (index === -1) {
|
||||
newValue.push(props.value)
|
||||
}
|
||||
} else {
|
||||
if (index !== -1) {
|
||||
newValue.splice(index, 1)
|
||||
}
|
||||
}
|
||||
emit('update:modelValue', newValue)
|
||||
} else {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="checkbox-wrapper">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
ref="input"
|
||||
:id="id"
|
||||
v-model="model"
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
<label :for="id" class="checkbox-label">
|
||||
{{ label }}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.checkbox-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
line-height: 20px;
|
||||
font-size: 13px;
|
||||
color: var(--vt-c-text-1);
|
||||
transition: color .5s;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--vp-input-border-color);
|
||||
background-color: var(--vp-input-switch-bg-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.checkbox:checked {
|
||||
border-color: transparent;
|
||||
background: url("data:image/svg+xml,%3Csvg width='12px' height='12px' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='4' stroke-linecap='round' stroke-linejoin='round' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E")
|
||||
center no-repeat var(--vp-c-brand);;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineProps, onMounted } from 'vue'
|
||||
import { computed, onMounted } from 'vue'
|
||||
const props = defineProps<{
|
||||
groups: string[] | undefined,
|
||||
groupName: string,
|
||||
|
||||
@@ -17,7 +17,7 @@ const SearchIcon = createLucideIcon('search', search)
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 8px;
|
||||
color: var(--vp-c-text-2);
|
||||
padding: 12px 16px;
|
||||
padding: 11px 16px;
|
||||
height: auto;
|
||||
font-size: 14px;
|
||||
/* box-shadow: var(--vp-shadow-4), var(--vp-shadow-2); */
|
||||
|
||||
@@ -24,40 +24,10 @@ const headingElement = computed(() => `h${props.headingLevel}`)
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-bottom: 96px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
align-content: space-evenly;
|
||||
box-sizing: border-box;
|
||||
margin: -8px;
|
||||
}
|
||||
|
||||
.grid > * {
|
||||
flex-basis: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.grid > * {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.grid > * {
|
||||
flex-basis: 33.33%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
export default {
|
||||
async load() {
|
||||
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest').then(res => {
|
||||
if (res.ok) {
|
||||
const releaseData = res.json() as Promise<{ tag_name: string }>
|
||||
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest')
|
||||
.then((res) => {
|
||||
if (res.ok) {
|
||||
const releaseData = res.json() as Promise<{ tag_name: string }>;
|
||||
|
||||
return releaseData
|
||||
}
|
||||
return null
|
||||
}).then(res => res.tag_name)
|
||||
return releaseData;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.then((res) => res.tag_name);
|
||||
|
||||
return {
|
||||
version
|
||||
}
|
||||
}
|
||||
}
|
||||
version,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -8,8 +8,6 @@ import { data } from './HomeHeroBefore.data'
|
||||
<HomeContainer class="container">
|
||||
<Badge
|
||||
:href="`https://github.com/lucide-icons/lucide/releases/tag/${data.version}`"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>v{{ data.version }}</Badge>
|
||||
</HomeContainer>
|
||||
</template>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import iconNodes from '../../../data/iconNodes'
|
||||
import iconNodes from '../../../data/iconNodes';
|
||||
|
||||
const getRandomItem = <Item>(items: Item[]): Item => items[Math.floor(Math.random()*items.length)];
|
||||
const getRandomItem = <Item>(items: Item[]): Item =>
|
||||
items[Math.floor(Math.random() * items.length)];
|
||||
|
||||
export default {
|
||||
async load() {
|
||||
const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode }))
|
||||
const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode }));
|
||||
|
||||
const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons))
|
||||
const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons));
|
||||
|
||||
return {
|
||||
icons: randomIcons,
|
||||
iconsCount: icons.length,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -28,8 +28,6 @@ function insert() {
|
||||
const replaceIndex = random(0, 48)
|
||||
const newIcon = getRandomNewIcon()
|
||||
|
||||
// items.value.splice(replaceIndex, 0, newIcon);
|
||||
|
||||
items.value[replaceIndex] = newIcon
|
||||
}
|
||||
|
||||
@@ -76,7 +74,6 @@ onBeforeUnmount(() => {
|
||||
|
||||
<style scoped>
|
||||
.card-wrapper {
|
||||
/* padding: 0 24px; */
|
||||
margin-left: auto;
|
||||
margin-bottom: auto;
|
||||
margin-top: 48px;
|
||||
@@ -87,13 +84,10 @@ onBeforeUnmount(() => {
|
||||
border-radius: 8px;
|
||||
width: 100%;
|
||||
height:100%;
|
||||
/* box-shadow: var(--vp-shadow-2); */
|
||||
max-height: 220px;
|
||||
max-width: 560px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
/* max-height: 240px; */
|
||||
/* margin-top: 96px; */
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
@@ -107,7 +101,6 @@ onBeforeUnmount(() => {
|
||||
max-width: 512px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
/* white-space: nowrap; */
|
||||
}
|
||||
|
||||
.list-enter-active {
|
||||
|
||||
@@ -47,7 +47,7 @@ export default {
|
||||
logo: '/framework-logos/flutter.svg',
|
||||
label: 'Lucide documentation for Flutter',
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import HomeContainer from './HomeContainer.vue'
|
||||
import HomeSectionTitle from './HomeSectionTitle.vue'
|
||||
import { useRouter } from 'vitepress';
|
||||
import { data } from './HomePackagesSection.data'
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||
@@ -9,7 +10,7 @@ const { go } = useRouter()
|
||||
|
||||
<template>
|
||||
<HomeContainer>
|
||||
<h2 class="section-title">Available For:</h2>
|
||||
<HomeSectionTitle>Available For:</HomeSectionTitle>
|
||||
<div class="packages-list">
|
||||
<a
|
||||
v-for="{ name, logo } in data.packages"
|
||||
@@ -34,14 +35,6 @@ const { go } = useRouter()
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.section-title {
|
||||
color: var(--vp-c-text-2);
|
||||
font-weight: 500;
|
||||
line-height: 32px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.packages-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
26
docs/.vitepress/theme/components/home/HomeSectionTitle.vue
Normal file
26
docs/.vitepress/theme/components/home/HomeSectionTitle.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
headingLevel: 1 | 2 | 3 | 4 | 5 | 6,
|
||||
}>()
|
||||
|
||||
const headingElement = computed(() => `h${props.headingLevel ?? 2}`)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="headingElement" class="section-title">
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.section-title {
|
||||
color: var(--vp-c-text-2);
|
||||
font-weight: 500;
|
||||
line-height: 32px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
59
docs/.vitepress/theme/components/home/HomeSponsorCard.vue
Normal file
59
docs/.vitepress/theme/components/home/HomeSponsorCard.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import Card from '../base/Card.vue';
|
||||
import HomeSectionTitle from './HomeSectionTitle.vue';
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HomeSectionTitle :headingLevel="3"> Sponsor the Lucide maintainers </HomeSectionTitle>
|
||||
<Card class="sponsor-card">
|
||||
<img
|
||||
src="/company-logos/open-collective-light.svg"
|
||||
alt="Open Collective logo"
|
||||
class="logo light"
|
||||
width="242"
|
||||
height="42"
|
||||
/>
|
||||
<img
|
||||
src="/company-logos/open-collective-dark.svg"
|
||||
alt="Open Collective logo"
|
||||
class="logo dark"
|
||||
width="242"
|
||||
height="42"
|
||||
/>
|
||||
<VPButton
|
||||
href="https://opencollective.com/lucide-icons"
|
||||
class="sponsor-button"
|
||||
text="Become a sponsor"
|
||||
/>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sponsor-card {
|
||||
margin: 0 auto;
|
||||
max-width: 500px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.sponsor-button {
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
html.dark .logo.dark {
|
||||
display: none;
|
||||
}
|
||||
html:not(.dark) .logo.light {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.sponsor-card {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
91
docs/.vitepress/theme/components/home/HomeTeamSection.vue
Normal file
91
docs/.vitepress/theme/components/home/HomeTeamSection.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<script setup lang="ts">
|
||||
import { useData } from 'vitepress';
|
||||
import HomeContainer from './HomeContainer.vue'
|
||||
import GridSection from '../base/GridSection.vue'
|
||||
import TeamMemberCard, { TeamMember } from './TeamMemberCard.vue'
|
||||
import teamData from '../../../data/teamData.json'
|
||||
import HomeSponsorCard from './HomeSponsorCard.vue';
|
||||
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue'
|
||||
|
||||
|
||||
const { theme } = useData()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HomeContainer>
|
||||
<GridSection
|
||||
title="Meet the team"
|
||||
:headingLevel="2"
|
||||
class="team-members"
|
||||
>
|
||||
<TeamMemberCard
|
||||
v-for="teamMember in (teamData as TeamMember[])"
|
||||
v-bind="teamMember"
|
||||
/>
|
||||
</GridSection>
|
||||
<HomeSponsorCard />
|
||||
<VPDocAsideCarbonAds
|
||||
:carbon-ads="theme.carbonAds"
|
||||
class="ad-card"
|
||||
/>
|
||||
</HomeContainer>
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.team-members {
|
||||
gap: 24px;
|
||||
margin-top: 48px;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.team-members :deep(.card-grid > *) {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.team-members :deep(.card-grid > *) {
|
||||
flex-basis: 33.33%;
|
||||
}
|
||||
}
|
||||
|
||||
.ad-card {
|
||||
margin: 32px auto 0;
|
||||
width: 100%;;
|
||||
max-width: 500px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.ad-card {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.ad-card :deep(.VPCarbonAds) {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.ad-card :deep(.VPCarbonAds .carbon-wrap) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.ad-card :deep(.VPCarbonAds .carbon-text) {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.ad-card :deep(.VPCarbonAds .carbon-poweredby) {
|
||||
text-align: right;
|
||||
margin-top: -24px;
|
||||
}
|
||||
</style>
|
||||
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 { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
|
||||
import iconsMetaData from '../../../data/iconMetaData'
|
||||
|
||||
import iconsMetaData from '../../../data/iconMetaData';
|
||||
|
||||
export default {
|
||||
async load() {
|
||||
let categories = getAllCategoryFiles()
|
||||
let categories = getAllCategoryFiles();
|
||||
|
||||
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData))
|
||||
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData));
|
||||
|
||||
return {
|
||||
categories,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useData } from 'vitepress'
|
||||
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
|
||||
import { isActive } from 'vitepress/dist/client/shared'
|
||||
import { useActiveAnchor } from '../../composables/useActiveAnchor'
|
||||
import { data } from './CategoryList.data'
|
||||
import CategoryListItem from './CategoryListItem.vue'
|
||||
import SidebarTitle from './SidebarTitle.vue'
|
||||
import { useCategoryView } from '../../composables/useCategoryView'
|
||||
|
||||
const { page } = useData()
|
||||
const { categoryCounts } = useCategoryView();
|
||||
|
||||
const categoriesIsActive = computed(() => {
|
||||
return isActive(page.value.relativePath, '/icons/categories');
|
||||
@@ -25,22 +28,32 @@ const headers = computed(() => {
|
||||
level: 2,
|
||||
link: `${linkPrefix}#${name}`,
|
||||
title,
|
||||
iconCount
|
||||
iconCount: categoryCounts.value[name] ?? iconCount,
|
||||
name
|
||||
}))
|
||||
})
|
||||
|
||||
const container = ref()
|
||||
const marker = ref()
|
||||
|
||||
useActiveAnchor(container, marker)
|
||||
const { setActiveLinkDebounced } = useActiveAnchor(container, marker)
|
||||
|
||||
watch(headers, () => {
|
||||
setTimeout(() => {
|
||||
setActiveLinkDebounced()
|
||||
}, 200)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="category-list" ref="container">
|
||||
<VPLink class="sidebar-title" href="/icons/" :class="{ 'active': overviewIsActive } ">
|
||||
<SidebarTitle>
|
||||
View
|
||||
</SidebarTitle>
|
||||
<VPLink class="sidebar-link sidebar-text" href="/icons/" :class="{ 'active': overviewIsActive } ">
|
||||
All
|
||||
</VPLink>
|
||||
<VPLink class="sidebar-title" href="/icons/categories" :class="{ 'active': categoriesIsActive } ">
|
||||
<VPLink class="sidebar-link sidebar-text" href="/icons/categories" :class="{ 'active': categoriesIsActive } ">
|
||||
Categories
|
||||
</VPLink>
|
||||
<div class="content">
|
||||
@@ -53,17 +66,20 @@ useActiveAnchor(container, marker)
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sidebar-title {
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 6px;
|
||||
.sidebar-text {
|
||||
line-height: 24px;
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
transition: color 0.25s;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.sidebar-title:hover, .sidebar-title.active {
|
||||
.sidebar-link {
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.sidebar-link:hover, .sidebar-link.active {
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
.content {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { useCategoryView } from '../../composables/useCategoryView';
|
||||
|
||||
interface Header {
|
||||
@@ -8,6 +7,7 @@ interface Header {
|
||||
slug: string;
|
||||
iconCount: number;
|
||||
link: string;
|
||||
name: string;
|
||||
children: Header[];
|
||||
}
|
||||
|
||||
@@ -15,36 +15,35 @@ type MenuItem = Omit<Header, 'slug' | 'children'> & {
|
||||
children?: MenuItem[];
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
headers: MenuItem[];
|
||||
root?: boolean;
|
||||
}>();
|
||||
|
||||
const { selectedCategory } = useCategoryView();
|
||||
|
||||
function onClick(event: Event) {
|
||||
const target =
|
||||
(event.target as HTMLElement).nodeName === 'span'
|
||||
? (event.target as HTMLElement).parentNode
|
||||
: (event.target as HTMLElement);
|
||||
const href = (target as HTMLAnchorElement)?.href;
|
||||
function onClick(categoryName: string) {
|
||||
selectedCategory.value = categoryName;
|
||||
|
||||
if (href) {
|
||||
const id = '#' + href.split('#')[1];
|
||||
const decodedId = decodeURIComponent(id);
|
||||
const heading = document.querySelector<HTMLAnchorElement>(categoryName);
|
||||
heading?.focus();
|
||||
|
||||
selectedCategory.value = decodedId.replace('#', '');
|
||||
|
||||
const heading = document.querySelector<HTMLAnchorElement>(decodedId);
|
||||
heading?.focus();
|
||||
}
|
||||
window.history.pushState({}, '', `/icons/categories#${categoryName}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul :class="root ? 'root' : 'nested'">
|
||||
<li v-for="{ children, link, title, iconCount } in headers">
|
||||
<a class="outline-link" :href="link" @click="onClick" :title="title">
|
||||
<li v-for="{ children, link, title, iconCount, name } in headers">
|
||||
<a
|
||||
class="outline-link"
|
||||
:href="link"
|
||||
@click="onClick(name)"
|
||||
:title="title"
|
||||
:class="{
|
||||
inactive: iconCount === 0,
|
||||
}"
|
||||
>
|
||||
<span>
|
||||
{{ title }}
|
||||
</span>
|
||||
@@ -84,6 +83,10 @@ function onClick(event: Event) {
|
||||
transition: color 0.25s;
|
||||
}
|
||||
|
||||
.outline-link.inactive {
|
||||
color: var(--vp-c-text-4);
|
||||
}
|
||||
|
||||
.outline-link.nested {
|
||||
padding-left: 13px;
|
||||
}
|
||||
@@ -94,4 +97,8 @@ function onClick(event: Event) {
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.outline-link.inactive .icon-count {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { startCase, camelCase } from 'lodash-es'
|
||||
import ButtonMenu from '../base/ButtonMenu.vue'
|
||||
import { useIconStyleContext } from '../../composables/useIconStyle';
|
||||
@@ -39,6 +39,12 @@ function copyJSX() {
|
||||
navigator.clipboard.writeText(code)
|
||||
}
|
||||
|
||||
function copyComponentName() {
|
||||
const code = componentName.value
|
||||
|
||||
navigator.clipboard.writeText(code)
|
||||
}
|
||||
|
||||
function copyVue() {
|
||||
let attrs = ['']
|
||||
|
||||
@@ -101,6 +107,7 @@ function copyAngular() {
|
||||
:popoverPosition="popoverPosition"
|
||||
:options="[
|
||||
{ text: 'Copy JSX' , onClick: copyJSX },
|
||||
{ text: 'Copy Component Name' , onClick: copyComponentName },
|
||||
{ text: 'Copy Vue' , onClick: copyVue },
|
||||
{ text: 'Copy Svelte' , onClick: copyJSX },
|
||||
{ text: 'Copy Angular' , onClick: copyAngular },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { IconEntity } from '../../types'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||
import IconButton from '../base/IconButton.vue';
|
||||
import IconContributors from './IconContributors.vue';
|
||||
@@ -11,14 +11,31 @@ import IconInfo from './IconInfo.vue';
|
||||
import Badge from '../base/Badge.vue';
|
||||
import { computedAsync } from '@vueuse/core';
|
||||
import { satisfies } from 'semver';
|
||||
import { useExternalLibs } from '../../composables/useExternalLibs';
|
||||
|
||||
const props = defineProps<{
|
||||
iconName: string
|
||||
iconName: string | null
|
||||
}>()
|
||||
|
||||
const { externalIconNodes } = useExternalLibs()
|
||||
|
||||
const { go } = useRouter()
|
||||
|
||||
const icon = computedAsync<IconEntity | null>(async () => {
|
||||
if (props.iconName) {
|
||||
return (await import(`../../../data/iconDetails/${props.iconName}.ts`)).default as IconEntity
|
||||
try {
|
||||
if (props.iconName.includes(':')) {
|
||||
const [library, name] = props.iconName.split(':')
|
||||
|
||||
return externalIconNodes.value[library].find((icon) => icon.name === name)
|
||||
} else {
|
||||
return (await import(`../../../data/iconDetails/${props.iconName}.ts`)).default as IconEntity
|
||||
}
|
||||
} catch (err) {
|
||||
if (!props.iconName.includes(':')) {
|
||||
go(`/icons/${props.iconName}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}, null)
|
||||
@@ -36,8 +53,6 @@ function onClose() {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const { go } = useRouter()
|
||||
|
||||
const CloseIcon = createLucideIcon('Close', x)
|
||||
const Expand = createLucideIcon('Expand', expand)
|
||||
</script>
|
||||
@@ -51,10 +66,8 @@ const Expand = createLucideIcon('Expand', expand)
|
||||
v-if="icon.createdRelease"
|
||||
class="version"
|
||||
:href="releaseTagLink(icon.createdRelease.version)"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>v{{ icon.createdRelease.version }}</Badge>
|
||||
<IconButton @click="go(`/icons/${icon.name}`)">
|
||||
<IconButton @click="go(icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}` : `/icons/${icon.name}`)">
|
||||
<component :is="Expand" />
|
||||
</IconButton>
|
||||
<IconButton @click="onClose">
|
||||
@@ -144,11 +157,11 @@ const Expand = createLucideIcon('Expand', expand)
|
||||
}
|
||||
|
||||
.drawer-enter-active {
|
||||
transition: all 0.2s cubic-bezier(.21,.8,.46,.9);
|
||||
transition: opacity 0.5s, transform 0.25s ease;
|
||||
}
|
||||
|
||||
.drawer-leave-active {
|
||||
transition: all 0.4s cubic-bezier(1, 0.5, 0.8, 1);
|
||||
transition: opacity 0.25s ease, transform 1.6s ease-out;
|
||||
}
|
||||
|
||||
.drawer-enter-from,
|
||||
|
||||
@@ -4,7 +4,7 @@ import IconItem from './IconItem.vue'
|
||||
|
||||
const emit = defineEmits(['setActiveIcon'])
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
icons: IconEntity[]
|
||||
activeIcon?: string
|
||||
overlayMode?: boolean
|
||||
@@ -19,10 +19,16 @@ function setActiveIcon(name: string) {
|
||||
|
||||
<template>
|
||||
<div class="icons">
|
||||
<div class="icon" v-for="icon in icons" :key="icon.name">
|
||||
<div
|
||||
class="icon"
|
||||
v-for="icon in icons"
|
||||
:key="icon.name"
|
||||
>
|
||||
<IconItem
|
||||
v-bind="icon"
|
||||
@setActiveIcon="setActiveIcon(icon.name)"
|
||||
:iconNode="icon.iconNode"
|
||||
:name="icon.name"
|
||||
:externalLibrary="icon.externalLibrary"
|
||||
@setActiveIcon="setActiveIcon"
|
||||
:active="activeIcon === icon.name"
|
||||
customizable
|
||||
:overlayMode="overlayMode"
|
||||
@@ -36,7 +42,6 @@ function setActiveIcon(name: string) {
|
||||
.icons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
|
||||
/* padding: 32px 32px 96px; */
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import CopyCodeButton from './CopyCodeButton.vue';
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||
import {useData, useRouter} from 'vitepress';
|
||||
import { computed } from 'vue';
|
||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||
import { diamond } from '../../../data/iconNodes'
|
||||
|
||||
const props = defineProps<{
|
||||
icon: IconEntity
|
||||
@@ -20,13 +22,21 @@ const tags = computed(() => {
|
||||
if (!props.icon || !props?.icon?.tags) return []
|
||||
return props.icon.tags.join(' • ')
|
||||
})
|
||||
|
||||
const DiamondIcon = createLucideIcon('Diamond', diamond)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="icon-info">
|
||||
<IconDetailName class="icon-name">
|
||||
{{ icon.name }}
|
||||
</IconDetailName>
|
||||
<div class="icon-name-wrapper">
|
||||
<IconDetailName class="icon-name">
|
||||
{{ icon.name }}
|
||||
</IconDetailName>
|
||||
<div v-if="icon.externalLibrary" class="icon-external-lib">
|
||||
<DiamondIcon fill="currentColor" :size="12"/>
|
||||
{{ icon.externalLibrary }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tags-scroller" v-if="tags.length">
|
||||
<p class="icon-tags horizontal-scroller">
|
||||
{{ tags }}
|
||||
@@ -44,10 +54,10 @@ const tags = computed(() => {
|
||||
|
||||
<div class="group buttons">
|
||||
<VPButton
|
||||
v-if="!page?.relativePath?.startsWith?.(`icons/${icon.name}`)"
|
||||
:href="`/icons/${icon.name}`"
|
||||
v-if="!page?.relativePath?.startsWith?.(icon.externalLibrary ? `icons/${icon.externalLibrary}/${icon.name}`: `icons/${icon.name}`)"
|
||||
:href="icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}`: `/icons/${icon.name}`"
|
||||
text="See in action"
|
||||
@click="go(`/icons/${icon.name}`)"
|
||||
@click="go(icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}`: `/icons/${icon.name}`)"
|
||||
/>
|
||||
<CopySVGButton :name="icon.name" :popoverPosition="popoverPosition"/>
|
||||
<CopyCodeButton :name="icon.name" :popoverPosition="popoverPosition"/>
|
||||
@@ -67,9 +77,27 @@ const tags = computed(() => {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.icon-name {
|
||||
margin-right: -36px;
|
||||
}
|
||||
|
||||
.icon-name-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.icon-external-lib {
|
||||
color: var(--vp-c-brand-dark);
|
||||
padding: 4px 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 28px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-tags {
|
||||
font-size: 16px;
|
||||
color: var(--vp-c-text-2);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||
import { useMediaQuery } from '@vueuse/core';
|
||||
import { useRouter } from 'vitepress';
|
||||
import getSVGIcon from '../../utils/getSVGIcon';
|
||||
import useConfetti from '../../composables/useConfetti';
|
||||
import Tooltip from '../base/Tooltip.vue';
|
||||
import { diamond } from '../../../data/iconNodes'
|
||||
|
||||
const downloadText = 'Download!'
|
||||
const copiedText = 'Copied!'
|
||||
@@ -15,6 +17,7 @@ const props = defineProps<{
|
||||
name: string;
|
||||
iconNode: IconNode;
|
||||
active: boolean;
|
||||
externalLibrary?: string;
|
||||
customizable?: boolean;
|
||||
overlayMode?: boolean
|
||||
hideIcon?: boolean
|
||||
@@ -27,10 +30,14 @@ const showOverlay = useMediaQuery('(min-width: 860px)');
|
||||
const { animate, confetti, confettiText } = useConfetti()
|
||||
|
||||
|
||||
const icon = createLucideIcon(props.name, props.iconNode)
|
||||
const icon = computed(() => {
|
||||
if (!props.name || !props.iconNode) return null
|
||||
return createLucideIcon(props.name, props.iconNode)
|
||||
})
|
||||
|
||||
const href = computed(() => props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`)
|
||||
|
||||
async function navigateToIcon(event) {
|
||||
|
||||
if (event.shiftKey) {
|
||||
event.preventDefault()
|
||||
const svgString = getSVGIcon(event.target.firstChild, {
|
||||
@@ -46,14 +53,16 @@ async function navigateToIcon(event) {
|
||||
|
||||
if(props.overlayMode && showOverlay.value) {
|
||||
event.preventDefault()
|
||||
window.history.pushState({}, '', `/icons/${props.name}`)
|
||||
emit('setActiveIcon', props.name)
|
||||
}
|
||||
else {
|
||||
|
||||
window.history.pushState({}, '', props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`)
|
||||
emit('setActiveIcon', props.externalLibrary ? `${props.externalLibrary}:${props.name}`: props.name)
|
||||
} else {
|
||||
event.preventDefault()
|
||||
go(`/icons/${props.name}`)
|
||||
go(props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
const DiamondIcon = createLucideIcon('Diamond', diamond)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -63,7 +72,7 @@ async function navigateToIcon(event) {
|
||||
@click="navigateToIcon"
|
||||
:class="{ active, animate }"
|
||||
:aria-label="name"
|
||||
:href="`/icons/${props.name}`"
|
||||
|
||||
:data-confetti-text="confettiText"
|
||||
ref="ref"
|
||||
>
|
||||
@@ -77,6 +86,13 @@ async function navigateToIcon(event) {
|
||||
}"
|
||||
/>
|
||||
</KeepAlive>
|
||||
<div
|
||||
v-if="externalLibrary"
|
||||
class="floating-diamond"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<DiamondIcon fill="currentColor" :size="8"/>
|
||||
</div>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</template>
|
||||
@@ -85,6 +101,7 @@ async function navigateToIcon(event) {
|
||||
|
||||
<style scoped>
|
||||
.icon-button {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border: 1px solid transparent;
|
||||
text-align: center;
|
||||
@@ -101,6 +118,13 @@ async function navigateToIcon(event) {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.floating-diamond {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.confetti-button:before,
|
||||
.confetti-button:after {
|
||||
z-index: 100;
|
||||
|
||||
@@ -1,56 +1,53 @@
|
||||
<script lang="ts">
|
||||
import { IconEntity } from '../../types';
|
||||
|
||||
type CategoryNameRow = {
|
||||
type: 'category';
|
||||
title: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type CategoryIconsRow = {
|
||||
type: 'icons';
|
||||
icons: IconEntity[];
|
||||
};
|
||||
|
||||
export type CategoryRow = CategoryNameRow | CategoryIconsRow;
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Category } from '../../types';
|
||||
import IconGrid from './IconGrid.vue'
|
||||
import { vIntersectionObserver } from '@vueuse/components'
|
||||
|
||||
defineProps<{
|
||||
activeIconName: string
|
||||
category: Category
|
||||
activeIconName: string | null
|
||||
categoryRow: CategoryRow
|
||||
}>()
|
||||
|
||||
|
||||
const emit = defineEmits(['setActiveIcon'])
|
||||
|
||||
const showIcons = ref(false)
|
||||
|
||||
// Added intersection observer to improve performance
|
||||
const onIntersectionObserver: IntersectionObserverCallback = ([{ isIntersecting }]) => {
|
||||
showIcons.value = isIntersecting
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="category"
|
||||
:key="category.name"
|
||||
:id="category.name"
|
||||
v-intersection-observer="onIntersectionObserver"
|
||||
<h2
|
||||
v-if="categoryRow.type === 'category'"
|
||||
class="title"
|
||||
>
|
||||
<h2 class="title" >
|
||||
<a class="header-anchor" :href="`#${category.name}`" :aria-label="`Permalink to "${category.title}"`">​</a>
|
||||
{{ category.title }}
|
||||
</h2>
|
||||
<IconGrid
|
||||
:activeIcon="activeIconName"
|
||||
:icons="category.icons"
|
||||
@setActiveIcon="$event => $emit('setActiveIcon', $event)"
|
||||
overlayMode
|
||||
:hideIcons="!showIcons"
|
||||
/>
|
||||
</section>
|
||||
<a class="header-anchor" :href="`#${categoryRow.name}`" :aria-label="`Permalink to "${categoryRow.title}"`">​</a>
|
||||
{{ categoryRow.title }}
|
||||
</h2>
|
||||
<IconGrid
|
||||
v-else-if="categoryRow.type === 'icons'"
|
||||
:activeIcon="activeIconName"
|
||||
:icons="categoryRow.icons"
|
||||
@setActiveIcon="$event => $emit('setActiveIcon', $event)"
|
||||
overlayMode
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.title {
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 19px;
|
||||
font-weight: 500;
|
||||
padding-top: 86px;
|
||||
/* scroll-padding-top: 240px; */
|
||||
}
|
||||
|
||||
.category {
|
||||
margin-bottom: calc(-86px + 32px);
|
||||
padding: 24px 0 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, defineAsyncComponent } from 'vue';
|
||||
import { ref, computed, defineAsyncComponent, onMounted, watch, watchEffect } from 'vue';
|
||||
import type { IconEntity, Category } from '../../types';
|
||||
import useSearch from '../../composables/useSearch';
|
||||
import InputSearch from '../base/InputSearch.vue';
|
||||
import useSearchInput from '../../composables/useSearchInput';
|
||||
import StickyBar from './StickyBar.vue';
|
||||
import IconsCategory from './IconsCategory.vue';
|
||||
import { useFetch } from '@vueuse/core';
|
||||
import useFetchTags from '../../composables/useFetchTags';
|
||||
import useFetchCategories from '../../composables/useFetchCategories';
|
||||
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
||||
import chunkArray from '../../utils/chunkArray';
|
||||
import { CategoryRow } from './IconsCategory.vue';
|
||||
import useScrollToCategory from '../../composables/useScrollToCategory';
|
||||
import CarbonAdOverlay from './CarbonAdOverlay.vue';
|
||||
|
||||
const ICON_SIZE = 56;
|
||||
const ICON_GRID_GAP = 8;
|
||||
|
||||
const props = defineProps<{
|
||||
icons: IconEntity[];
|
||||
@@ -18,7 +25,6 @@ const props = defineProps<{
|
||||
|
||||
const activeIconName = ref(null);
|
||||
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
||||
|
||||
const isSearching = computed(() => !!searchQuery.value);
|
||||
|
||||
function setActiveIconName(name: string) {
|
||||
@@ -28,6 +34,13 @@ function setActiveIconName(name: string) {
|
||||
const { execute: fetchTags, data: tags } = useFetchTags();
|
||||
const { execute: fetchCategories, data: categoriesMap } = useFetchCategories();
|
||||
|
||||
const overviewEl = ref<HTMLElement | null>(null);
|
||||
const { width: containerWidth } = useElementSize(overviewEl)
|
||||
|
||||
const columnSize = computed(() => {
|
||||
return Math.floor((containerWidth.value) / ((ICON_SIZE + ICON_GRID_GAP)));
|
||||
});
|
||||
|
||||
const mappedIcons = computed(() => {
|
||||
if (tags.value == null) {
|
||||
return props.icons;
|
||||
@@ -44,9 +57,10 @@ const mappedIcons = computed(() => {
|
||||
});
|
||||
});
|
||||
|
||||
const searchResults = useSearch(searchQuery, mappedIcons, [
|
||||
{ name: 'name', weight: 2 },
|
||||
{ name: 'tags', weight: 1 },
|
||||
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
|
||||
{ name: 'name', weight: 3 },
|
||||
{ name: 'aliases', weight: 3 },
|
||||
{ name: 'tags', weight: 2 },
|
||||
]);
|
||||
|
||||
const categories = computed(() => {
|
||||
@@ -55,7 +69,7 @@ const categories = computed(() => {
|
||||
return props.categories
|
||||
.map(({ name, title }) => {
|
||||
const categoryIcons = props.icons.filter((icon) => {
|
||||
const iconCategories = props.iconCategories[icon.name];
|
||||
const iconCategories = icon?.externalLibrary ? icon.categories : props.iconCategories[icon.name]
|
||||
|
||||
return iconCategories?.includes(name);
|
||||
});
|
||||
@@ -72,9 +86,43 @@ const categories = computed(() => {
|
||||
icons: searchedCategoryIcons,
|
||||
};
|
||||
})
|
||||
.filter(({ icons }) => icons.length);
|
||||
});
|
||||
|
||||
const categoriesList = computed(() => {
|
||||
return categories.value
|
||||
.filter(({ icons }) => icons.length)
|
||||
.reduce<CategoryRow[]>((acc, category) => {
|
||||
acc.push({ type: 'category', title: category.title, name: category.name });
|
||||
|
||||
const categoryIcons = chunkArray(category.icons, columnSize.value);
|
||||
categoryIcons.forEach((icons) => {
|
||||
acc.push({ type: 'icons', icons });
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
});
|
||||
|
||||
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
|
||||
categoriesList,
|
||||
{
|
||||
itemHeight: ICON_SIZE + ICON_GRID_GAP,
|
||||
overscan: 10
|
||||
},
|
||||
)
|
||||
|
||||
useScrollToCategory({
|
||||
categories,
|
||||
categoriesList,
|
||||
scrollTo,
|
||||
searchQueryDebounced,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
containerProps.ref.value = document.documentElement;
|
||||
useEventListener(window, 'scroll', containerProps.onScroll)
|
||||
})
|
||||
|
||||
function onFocusSearchInput() {
|
||||
if (tags.value == null) {
|
||||
fetchTags();
|
||||
@@ -85,33 +133,54 @@ function onFocusSearchInput() {
|
||||
}
|
||||
|
||||
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
||||
|
||||
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
|
||||
|
||||
function handleCloseDrawer() {
|
||||
setActiveIconName('');
|
||||
|
||||
window.history.pushState({}, '', '/icons/categories');
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
|
||||
console.log(props.icons.find((icon) => icon.name === 'burger'));
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StickyBar class="search-bar category-search">
|
||||
<InputSearch
|
||||
:placeholder="`Search ${icons.length} icons ...`"
|
||||
v-model="searchQuery"
|
||||
class="input-wrapper"
|
||||
ref="searchInput"
|
||||
@focus="onFocusSearchInput"
|
||||
<div ref="overviewEl" class="overview-container">
|
||||
<StickyBar class="category-search">
|
||||
<InputSearch
|
||||
:placeholder="`Search ${icons.length} icons ...`"
|
||||
v-model="searchQuery"
|
||||
class="input-wrapper"
|
||||
ref="searchInput"
|
||||
@focus="onFocusSearchInput"
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="categories.length === 0"
|
||||
:searchQuery="searchQuery"
|
||||
@clear="searchQuery = ''"
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults v-if="categories.length === 0" :searchQuery="searchQuery" @clear="searchQuery = ''" />
|
||||
<IconsCategory
|
||||
v-for="category in categories"
|
||||
:key="category.name"
|
||||
:category="category"
|
||||
:activeIconName="activeIconName"
|
||||
@setActiveIcon="setActiveIconName"
|
||||
/>
|
||||
<div v-bind="wrapperProps">
|
||||
<IconsCategory
|
||||
v-for="{ index, data } in list"
|
||||
:categoryRow="data"
|
||||
:activeIconName="activeIconName"
|
||||
@setActiveIcon="setActiveIconName"
|
||||
:key="index"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<IconDetailOverlay
|
||||
v-if="activeIconName != null"
|
||||
:iconName="activeIconName"
|
||||
@close="setActiveIconName('')"
|
||||
@close="handleCloseDrawer"
|
||||
/>
|
||||
|
||||
<CarbonAdOverlay :drawerOpen="!!activeIconName" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -120,6 +189,21 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
|
||||
}
|
||||
|
||||
.search-bar.category-search {
|
||||
margin-bottom: -54px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 8px;
|
||||
font-size: 19px;
|
||||
font-weight: 500;
|
||||
padding: 24px 0 8px;
|
||||
}
|
||||
|
||||
.icons {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.overview-container {
|
||||
padding-bottom: 288px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, defineAsyncComponent } from 'vue';
|
||||
import { ref, computed, defineAsyncComponent, onMounted, watch } from 'vue';
|
||||
import type { IconEntity } from '../../types';
|
||||
import { useMediaQuery, useOffsetPagination } from '@vueuse/core';
|
||||
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
||||
import IconGrid from './IconGrid.vue';
|
||||
import InputSearch from '../base/InputSearch.vue';
|
||||
import useSearch from '../../composables/useSearch';
|
||||
import EndOfPage from '../base/EndOfPage.vue';
|
||||
import useSearchInput from '../../composables/useSearchInput';
|
||||
import StickyBar from './StickyBar.vue';
|
||||
import useFetchTags from '../../composables/useFetchTags';
|
||||
import useFetchCategories from '../../composables/useFetchCategories';
|
||||
import chunkArray from '../../utils/chunkArray';
|
||||
import CarbonAdOverlay from './CarbonAdOverlay.vue';
|
||||
|
||||
const ICON_SIZE = 56;
|
||||
const ICON_GRID_GAP = 8;
|
||||
|
||||
const props = defineProps<{
|
||||
icons: IconEntity[];
|
||||
@@ -17,32 +21,16 @@ const props = defineProps<{
|
||||
|
||||
const activeIconName = ref(null);
|
||||
|
||||
const isExtraLargeScreen = useMediaQuery('(min-width: 1440px)');
|
||||
const isLargeScreen = useMediaQuery('(min-width: 1280px)');
|
||||
const isMediumScreen = useMediaQuery('(min-width: 960px)');
|
||||
const isSmallScreen = useMediaQuery('(min-width: 640px)');
|
||||
|
||||
const pageSize = computed(() => {
|
||||
if (isExtraLargeScreen.value) {
|
||||
return 16 * 20;
|
||||
}
|
||||
if (isLargeScreen.value) {
|
||||
return 16 * 12;
|
||||
}
|
||||
if (isMediumScreen.value) {
|
||||
return 13 * 12;
|
||||
}
|
||||
|
||||
if (isSmallScreen.value) {
|
||||
return 10 * 10;
|
||||
}
|
||||
|
||||
return 10 * 5;
|
||||
});
|
||||
|
||||
const { execute: fetchTags, data: tags } = useFetchTags();
|
||||
const { execute: fetchCategories, data: categories } = useFetchCategories();
|
||||
|
||||
const overviewEl = ref<HTMLElement | null>(null);
|
||||
const { width: containerWidth } = useElementSize(overviewEl)
|
||||
|
||||
const columnSize = computed(() => {
|
||||
return Math.floor((containerWidth.value) / ((ICON_SIZE + ICON_GRID_GAP)));
|
||||
});
|
||||
|
||||
const mappedIcons = computed(() => {
|
||||
if (tags.value == null) {
|
||||
return props.icons;
|
||||
@@ -63,26 +51,33 @@ const mappedIcons = computed(() => {
|
||||
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
||||
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
|
||||
{ name: 'name', weight: 3 },
|
||||
{ name: 'aliases', weight: 3 },
|
||||
{ name: 'tags', weight: 2 },
|
||||
{ name: 'categories', weight: 1 },
|
||||
]);
|
||||
|
||||
const { next, currentPage } = useOffsetPagination({ pageSize });
|
||||
|
||||
const paginatedIcons = computed(() => {
|
||||
const end = pageSize.value * currentPage.value;
|
||||
|
||||
return searchResults.value.slice(0, end);
|
||||
const chunkedIcons = computed(() => {
|
||||
return chunkArray(searchResults.value, columnSize.value);
|
||||
});
|
||||
|
||||
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
|
||||
chunkedIcons,
|
||||
{
|
||||
itemHeight: ICON_SIZE + ICON_GRID_GAP,
|
||||
overscan: 10
|
||||
},
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
containerProps.ref.value = document.documentElement;
|
||||
useEventListener(window, 'scroll', containerProps.onScroll)
|
||||
})
|
||||
|
||||
|
||||
function setActiveIconName(name: string) {
|
||||
activeIconName.value = name;
|
||||
}
|
||||
|
||||
watch(searchQueryDebounced, (searchString) => {
|
||||
currentPage.value = 1;
|
||||
});
|
||||
|
||||
function onFocusSearchInput() {
|
||||
if (tags.value == null) {
|
||||
fetchTags();
|
||||
@@ -95,43 +90,57 @@ function onFocusSearchInput() {
|
||||
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
||||
|
||||
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
|
||||
|
||||
watch(searchQueryDebounced, () => {
|
||||
scrollTo(0)
|
||||
})
|
||||
|
||||
function handleCloseDrawer() {
|
||||
setActiveIconName('');
|
||||
|
||||
window.history.pushState({}, '', '/icons/');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StickyBar>
|
||||
<InputSearch
|
||||
:placeholder="`Search ${icons.length} icons ...`"
|
||||
v-model="searchQuery"
|
||||
ref="searchInput"
|
||||
class="input-wrapper"
|
||||
@focus="onFocusSearchInput"
|
||||
<div ref="overviewEl" class="overview-container">
|
||||
<StickyBar>
|
||||
<InputSearch
|
||||
:placeholder="`Search ${icons.length} icons ...`"
|
||||
v-model="searchQuery"
|
||||
ref="searchInput"
|
||||
class="input-wrapper"
|
||||
@focus="onFocusSearchInput"
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="list.length === 0"
|
||||
:searchQuery="searchQuery"
|
||||
@clear="searchQuery = ''"
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="paginatedIcons.length === 0"
|
||||
:searchQuery="searchQuery"
|
||||
@clear="searchQuery = ''"
|
||||
/>
|
||||
<IconGrid
|
||||
overlayMode
|
||||
:activeIcon="activeIconName"
|
||||
:icons="paginatedIcons"
|
||||
@setActiveIcon="setActiveIconName"
|
||||
/>
|
||||
<EndOfPage @end-of-page="next" class="bottom-page" />
|
||||
<div v-bind="wrapperProps" class="icon">
|
||||
<IconGrid
|
||||
v-for="{ index, data: icons } in list"
|
||||
:key="index"
|
||||
overlayMode
|
||||
:icons="icons"
|
||||
:activeIcon="activeIconName"
|
||||
@setActiveIcon="setActiveIconName"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IconDetailOverlay
|
||||
v-if="activeIconName != null"
|
||||
:iconName="activeIconName"
|
||||
@close="setActiveIconName('')"
|
||||
@close="handleCloseDrawer"
|
||||
/>
|
||||
|
||||
<CarbonAdOverlay :drawerOpen="!!activeIconName" />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.icons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@@ -142,7 +151,7 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bottom-page {
|
||||
height: 288px;
|
||||
.overview-container {
|
||||
padding-bottom: 288px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import {ref} from 'vue'
|
||||
import {bird} from '../../../data/iconNodes'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { bird, squirrel, rabbit } from '../../../data/iconNodes'
|
||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
||||
import {useEventListener} from '@vueuse/core'
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
|
||||
import { IconNode } from '../../types'
|
||||
|
||||
defineProps<{
|
||||
searchQuery: string
|
||||
@@ -11,32 +12,48 @@ defineProps<{
|
||||
|
||||
defineEmits(['clear'])
|
||||
|
||||
const birdIcon = ref<HTMLElement>()
|
||||
const Bird = createLucideIcon('bird', bird)
|
||||
const animalIcon = ref<HTMLElement>()
|
||||
const randomAnimal = computed<IconNode>(() => {
|
||||
return Math.random() > 0.5 ? squirrel : Math.random() > 0.5 ? rabbit : bird
|
||||
})
|
||||
const animalComponent = computed(() => createLucideIcon('animal', randomAnimal.value))
|
||||
const flip = ref(false)
|
||||
|
||||
useEventListener(document, 'mousemove', (mouseEvent) => {
|
||||
const {width, height, x, y} = birdIcon.value.getBoundingClientRect()
|
||||
onMounted(() => {
|
||||
useEventListener(document, 'mousemove', (mouseEvent) => {
|
||||
const {width, height, x, y} = animalIcon.value.getBoundingClientRect()
|
||||
|
||||
const centerX = (width / 2) + x
|
||||
const centerX = (width / 2) + x
|
||||
|
||||
flip.value = mouseEvent.x < centerX
|
||||
flip.value = mouseEvent.x < centerX
|
||||
})
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="no-results">
|
||||
<Bird class="bird-icon" ref="birdIcon" :class="{ flip }" :strokeWidth="1"/>
|
||||
<component
|
||||
:is="animalComponent"
|
||||
class="animal-icon"
|
||||
ref="animalIcon"
|
||||
:class="{ flip }"
|
||||
:strokeWidth="1"
|
||||
/>
|
||||
<h2 class="no-results-text">
|
||||
No icons found for '{{ searchQuery }}'
|
||||
</h2>
|
||||
<VPButton text="Clear your search and try again" theme="alt" @click="$emit('clear')"/>
|
||||
<VPButton
|
||||
text="Clear your search and try again"
|
||||
theme="alt"
|
||||
@click="$emit('clear')"
|
||||
/>
|
||||
<span class="text-divider">or</span>
|
||||
<VPButton text="Search on Github issues"
|
||||
theme="alt"
|
||||
:href="`https://github.com/lucide-icons/lucide/issues?q=is%3Aopen+${searchQuery}`"
|
||||
target="_blank"
|
||||
<VPButton
|
||||
text="Search on Github issues"
|
||||
theme="alt"
|
||||
:href="`https://github.com/lucide-icons/lucide/issues?q=is%3Aopen+${searchQuery}`"
|
||||
target="_blank"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -48,7 +65,7 @@ useEventListener(document, 'mousemove', (mouseEvent) => {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bird-icon {
|
||||
.animal-icon {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
color: var(--vp-c-neutral);
|
||||
@@ -56,12 +73,12 @@ useEventListener(document, 'mousemove', (mouseEvent) => {
|
||||
margin-top: 72px;
|
||||
}
|
||||
|
||||
.bird-icon.flip {
|
||||
.animal-icon.flip {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.bird-icon {
|
||||
.animal-icon {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user