mirror of
https://github.com/lucide-icons/lucide.git
synced 2025-12-17 21:47:40 +01:00
Compare commits
429 Commits
fix/fixed-
...
update-sit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
797cf7667d | ||
|
|
979e07fbf9 | ||
|
|
2f5f78c1c2 | ||
|
|
634322e96c | ||
|
|
53ebc4c076 | ||
|
|
7d023209f0 | ||
|
|
d58a2e43c6 | ||
|
|
5ecf78bb8a | ||
|
|
f78061b488 | ||
|
|
aa8f74eb9e | ||
|
|
7327637532 | ||
|
|
076e0bbcd9 | ||
|
|
33bb95edcd | ||
|
|
4c8650a7bf | ||
|
|
ebf400befd | ||
|
|
0f6dc52a8a | ||
|
|
08bd4b33a0 | ||
|
|
83d5ccc3e8 | ||
|
|
a3886eb829 | ||
|
|
86957f0205 | ||
|
|
b23d4f110b | ||
|
|
465b98df74 | ||
|
|
d3b2255c0a | ||
|
|
a4beac39a3 | ||
|
|
53311befff | ||
|
|
156b3379e4 | ||
|
|
8dd82dd592 | ||
|
|
80d6f737e0 | ||
|
|
7d86d083bd | ||
|
|
a94044cc3a | ||
|
|
2c46ea9948 | ||
|
|
190e8372af | ||
|
|
5c523be780 | ||
|
|
d3826ce952 | ||
|
|
ee66dd258d | ||
|
|
265770857f | ||
|
|
1c20fc66d7 | ||
|
|
c34c5f2aea | ||
|
|
6432dddfe0 | ||
|
|
028fc12657 | ||
|
|
2e794a296a | ||
|
|
498b8390d7 | ||
|
|
528de5284b | ||
|
|
a19fb21c5d | ||
|
|
5770f62d02 | ||
|
|
be59e647c1 | ||
|
|
e560b8bf3a | ||
|
|
74d6fbc941 | ||
|
|
94e3f56166 | ||
|
|
a2a0821c66 | ||
|
|
b787576f4a | ||
|
|
375ad12aeb | ||
|
|
a432aeee69 | ||
|
|
b0ed2827f8 | ||
|
|
bfbd5d7bda | ||
|
|
1090cbc810 | ||
|
|
fa6e015520 | ||
|
|
1756617885 | ||
|
|
f5d0a2a220 | ||
|
|
be55088e75 | ||
|
|
32e93c043f | ||
|
|
920fa7888b | ||
|
|
95766b6682 | ||
|
|
db15bb85c0 | ||
|
|
b1675c4c33 | ||
|
|
84c210acf5 | ||
|
|
5e27d7d11a | ||
|
|
3a608bac58 | ||
|
|
bcebce5927 | ||
|
|
84346a148e | ||
|
|
fac9fa4efd | ||
|
|
cde1ebaa0b | ||
|
|
9f67c0943b | ||
|
|
999bca4575 | ||
|
|
3c7815c5c0 | ||
|
|
d7e7493bd6 | ||
|
|
f34e889831 | ||
|
|
7bb27142c8 | ||
|
|
2811d60184 | ||
|
|
730c24ec55 | ||
|
|
56ebfeb596 | ||
|
|
905d38186c | ||
|
|
65403c9298 | ||
|
|
8eb64f4574 | ||
|
|
d1dd94a717 | ||
|
|
758fa4b75f | ||
|
|
1cfb3ff70e | ||
|
|
7bbb1e1fea | ||
|
|
31cfa79818 | ||
|
|
d1903220db | ||
|
|
ec567e59fc | ||
|
|
1d6b5d6858 | ||
|
|
c368d01d92 | ||
|
|
99427026a3 | ||
|
|
81a1f6e1dc | ||
|
|
7fb4ffc7a7 | ||
|
|
30529bbeb2 | ||
|
|
279ac9ce13 | ||
|
|
2dede9886b | ||
|
|
781fc399a2 | ||
|
|
087b59d55e | ||
|
|
e1f96d8f54 | ||
|
|
7cf38b5b6e | ||
|
|
9c441bd4ff | ||
|
|
507a86a890 | ||
|
|
8d4d9cd6d5 | ||
|
|
341be34b1f | ||
|
|
d52c369b99 | ||
|
|
a7a1973d3f | ||
|
|
b6dfcef41b | ||
|
|
1c5a9dafc1 | ||
|
|
546bb1f391 | ||
|
|
64c1a55e01 | ||
|
|
0e3ac6beb9 | ||
|
|
c7c0e2bead | ||
|
|
85dba80322 | ||
|
|
f8a1a76b83 | ||
|
|
bd50af7afb | ||
|
|
cfabc9f608 | ||
|
|
09f98127c6 | ||
|
|
867c0581af | ||
|
|
987edb14f8 | ||
|
|
9db4dc7e1c | ||
|
|
2150ce75ce | ||
|
|
106e37c3ce | ||
|
|
528ffd6fa5 | ||
|
|
3b92fae633 | ||
|
|
fcf42bf754 | ||
|
|
0b6149c02d | ||
|
|
bc9a8de419 | ||
|
|
26ab33a832 | ||
|
|
ac4c3581c4 | ||
|
|
1ddba6da04 | ||
|
|
8754c9b6fd | ||
|
|
e9febe4fab | ||
|
|
570f7d7e76 | ||
|
|
6d43b9feb3 | ||
|
|
16166f838b | ||
|
|
425bcc3f3d | ||
|
|
982af91724 | ||
|
|
a3ec4428f1 | ||
|
|
673e76831d | ||
|
|
b4291576b7 | ||
|
|
95f5ecacad | ||
|
|
ef5c1b9bdd | ||
|
|
b01715ebe8 | ||
|
|
05dd5fcfde | ||
|
|
e71198d9b3 | ||
|
|
57714e36ea | ||
|
|
358c9c1e80 | ||
|
|
64ddff76c5 | ||
|
|
31a9cdcbcc | ||
|
|
69319ee2f0 | ||
|
|
7beb3ee666 | ||
|
|
396d6d42ce | ||
|
|
37a60eefb2 | ||
|
|
c2c46b21ed | ||
|
|
0a0c8aa7e7 | ||
|
|
dc14e6ea8e | ||
|
|
1f67ef5307 | ||
|
|
ff6f9a09ad | ||
|
|
585b65f28f | ||
|
|
405feea8f6 | ||
|
|
5931a4a227 | ||
|
|
d3bafc2848 | ||
|
|
a6f4a08bda | ||
|
|
5a93aff903 | ||
|
|
e6c50d543b | ||
|
|
0d7c70cab6 | ||
|
|
379d1b1002 | ||
|
|
7d6998941d | ||
|
|
667bb8034d | ||
|
|
fd9c489d7a | ||
|
|
acb0e37ac2 | ||
|
|
6c25cbee67 | ||
|
|
08121af31e | ||
|
|
2d484469cd | ||
|
|
f66d167ead | ||
|
|
c63e5a456e | ||
|
|
aac12f991a | ||
|
|
3844a4b346 | ||
|
|
98b43826a4 | ||
|
|
e8194cbabe | ||
|
|
17bbbe1cbb | ||
|
|
c698121170 | ||
|
|
7a380a8177 | ||
|
|
55623e1611 | ||
|
|
803d237fef | ||
|
|
151bd33cd7 | ||
|
|
b3b8cfd59c | ||
|
|
996fc68c7e | ||
|
|
69e99093b8 | ||
|
|
030c7b9cb9 | ||
|
|
6f384680b4 | ||
|
|
e810b7273f | ||
|
|
599c517188 | ||
|
|
95830a0d88 | ||
|
|
5f0c19afaa | ||
|
|
37135af384 | ||
|
|
0fdc2413f6 | ||
|
|
d565b951d7 | ||
|
|
3e7d748827 | ||
|
|
0e54626bdb | ||
|
|
110d8cce27 | ||
|
|
9db00bfe3c | ||
|
|
e21d0e2dd6 | ||
|
|
483a594622 | ||
|
|
2136843515 | ||
|
|
ad842796dc | ||
|
|
f6a7eadd99 | ||
|
|
f51a01a7d0 | ||
|
|
30ed887ad1 | ||
|
|
a87ac013f7 | ||
|
|
87a67cc314 | ||
|
|
e418be64a1 | ||
|
|
0ca76a1de1 | ||
|
|
be81f20a6b | ||
|
|
41d32b8f40 | ||
|
|
c543914fb0 | ||
|
|
40d9e44b03 | ||
|
|
5821c62880 | ||
|
|
d15973e7ef | ||
|
|
475439bca9 | ||
|
|
13fc99f80d | ||
|
|
6a49a51164 | ||
|
|
ba5a701faf | ||
|
|
9e74dbb617 | ||
|
|
3e644fda2d | ||
|
|
7517894f2d | ||
|
|
5e363f48af | ||
|
|
537040bf81 | ||
|
|
308e738e0e | ||
|
|
e16e8b2783 | ||
|
|
e7f03df844 | ||
|
|
8aed9b826d | ||
|
|
e332d8b7ce | ||
|
|
92aeac569f | ||
|
|
53c16e191b | ||
|
|
4791674365 | ||
|
|
8527049827 | ||
|
|
8205ef68ed | ||
|
|
f5c708adf2 | ||
|
|
d42539d2b4 | ||
|
|
3614b6b46e | ||
|
|
86443054e2 | ||
|
|
cc09c2640f | ||
|
|
394399bfb8 | ||
|
|
af1b0e033e | ||
|
|
bdb5f6c137 | ||
|
|
d42a21610f | ||
|
|
f5be7a9862 | ||
|
|
52d408e6d0 | ||
|
|
f69c760598 | ||
|
|
22a43cbdeb | ||
|
|
1ca70df074 | ||
|
|
5514709fb3 | ||
|
|
34a8280ba8 | ||
|
|
f814c94672 | ||
|
|
e1cfbe14be | ||
|
|
a549fd65ef | ||
|
|
239bbd9a9a | ||
|
|
85b6de6684 | ||
|
|
19fa01b5fc | ||
|
|
e3e6dd9ccc | ||
|
|
ba77aa6a76 | ||
|
|
42f36a9da3 | ||
|
|
9abed5ee16 | ||
|
|
e6246ed5c6 | ||
|
|
877b467d96 | ||
|
|
781620d669 | ||
|
|
3ad1f07ffc | ||
|
|
47880403d3 | ||
|
|
89d204a35d | ||
|
|
bdcd417c1c | ||
|
|
4c7738fea1 | ||
|
|
12e7cfd7f7 | ||
|
|
09841dd721 | ||
|
|
8ae65a8fb1 | ||
|
|
fda32bee52 | ||
|
|
e913eab8bb | ||
|
|
2408b8bb8f | ||
|
|
8b257abaf6 | ||
|
|
c00ef3a835 | ||
|
|
0d3c8f4ea2 | ||
|
|
771cb0bbd6 | ||
|
|
3f34f4e7b6 | ||
|
|
e4f411ce9c | ||
|
|
0f47888a17 | ||
|
|
81ce8fed07 | ||
|
|
03b3a4f179 | ||
|
|
24178ca1bc | ||
|
|
e91dd659f0 | ||
|
|
6277ceb274 | ||
|
|
45b22c9f2d | ||
|
|
3a375e1a17 | ||
|
|
ac09719ba6 | ||
|
|
8d8c41c371 | ||
|
|
951c142e96 | ||
|
|
dcdc0b8bb3 | ||
|
|
e11e134ce1 | ||
|
|
e149de42ff | ||
|
|
5baa37b85a | ||
|
|
7916c44484 | ||
|
|
922ba5c781 | ||
|
|
c9c1b47e7a | ||
|
|
03eb86287a | ||
|
|
1ab3826607 | ||
|
|
2fde138c46 | ||
|
|
2640a7c585 | ||
|
|
25ce89b191 | ||
|
|
fd3f3eb607 | ||
|
|
b473c10af9 | ||
|
|
a01c15cc8c | ||
|
|
0fccc276c3 | ||
|
|
06c33964cb | ||
|
|
232d7b4557 | ||
|
|
3205e11173 | ||
|
|
0778ec87ad | ||
|
|
85983552ee | ||
|
|
7b954807da | ||
|
|
9105f0e3dd | ||
|
|
fe57bdd291 | ||
|
|
34cb6b12f8 | ||
|
|
6a8591c348 | ||
|
|
547b1846ae | ||
|
|
187ce5cff8 | ||
|
|
40f544433b | ||
|
|
05fa11e073 | ||
|
|
e4988bc1df | ||
|
|
3a24adec64 | ||
|
|
a620a0b08c | ||
|
|
2cc06fa703 | ||
|
|
9aac2e8424 | ||
|
|
2517eb642c | ||
|
|
7acf492f63 | ||
|
|
abe8910247 | ||
|
|
b35b16e96e | ||
|
|
b49c387cfe | ||
|
|
61d6f93023 | ||
|
|
1a3e9f273e | ||
|
|
8023c01f37 | ||
|
|
674a6bf799 | ||
|
|
e3a476a027 | ||
|
|
a021c1dbce | ||
|
|
a5ea7932dc | ||
|
|
6eb67bde5a | ||
|
|
19eb810537 | ||
|
|
6c865ccd9a | ||
|
|
667c80b39f | ||
|
|
dbada2a227 | ||
|
|
849d9c8af2 | ||
|
|
d61d47470f | ||
|
|
8b80cf2cef | ||
|
|
41f6c26228 | ||
|
|
02efe87c5a | ||
|
|
dc39f3d96a | ||
|
|
9879d8f4ed | ||
|
|
f95afd258a | ||
|
|
6ca07ea1bc | ||
|
|
3ff082cfd9 | ||
|
|
f776aaa9bc | ||
|
|
1587bee16d | ||
|
|
c78686ceca | ||
|
|
a11ba9e1cd | ||
|
|
99d992678a | ||
|
|
14822456aa | ||
|
|
64d6b10239 | ||
|
|
0232ce490d | ||
|
|
9d9e2c995a | ||
|
|
c68ee7f9a0 | ||
|
|
5f5e052038 | ||
|
|
aeb376f3a2 | ||
|
|
ed733916f3 | ||
|
|
64a8b7d4d0 | ||
|
|
c138c2e97d | ||
|
|
d82718b222 | ||
|
|
acdcf74faf | ||
|
|
9fa525e8c6 | ||
|
|
20c0012e62 | ||
|
|
70248c0f42 | ||
|
|
f8af6ae309 | ||
|
|
e8f64d8dcc | ||
|
|
4ea41dc329 | ||
|
|
4835ae67a9 | ||
|
|
6d6aa4c4cc | ||
|
|
70dad77b96 | ||
|
|
cc09030f9d | ||
|
|
73a6c8045d | ||
|
|
fccfaa3dd0 | ||
|
|
14fec0e184 | ||
|
|
fcebb5c4db | ||
|
|
1058ea5ae3 | ||
|
|
e08a1f7973 | ||
|
|
07f9d3ea79 | ||
|
|
73a2c9da6c | ||
|
|
afa4176737 | ||
|
|
cb86576661 | ||
|
|
9b738c1cb6 | ||
|
|
aefb710e5c | ||
|
|
df063fa378 | ||
|
|
2c3b6754d5 | ||
|
|
47303ac790 | ||
|
|
3aab3b9b08 | ||
|
|
3d29ab9279 | ||
|
|
e0fbbb6f2d | ||
|
|
c1331798f5 | ||
|
|
ddad14a96d | ||
|
|
a17609b5e4 | ||
|
|
e619963183 | ||
|
|
56948fe7c2 | ||
|
|
68116414d0 | ||
|
|
16a18f7908 | ||
|
|
ae8af191f7 | ||
|
|
1787b82cfe | ||
|
|
0e49b2b88c | ||
|
|
bab52d7333 | ||
|
|
c96260d82a | ||
|
|
3c23331e9e | ||
|
|
5a7c49dc55 | ||
|
|
6ae047557e | ||
|
|
fa064306fa | ||
|
|
4f46f43379 | ||
|
|
fc87643aae | ||
|
|
6f4f47684f | ||
|
|
c6632ce8d7 | ||
|
|
a59c5de61a | ||
|
|
2bd0ae55fd | ||
|
|
3f2ad5b2a6 | ||
|
|
1250fc276d |
@@ -8,10 +8,4 @@ squircle
|
|||||||
strikethrough
|
strikethrough
|
||||||
touchpad
|
touchpad
|
||||||
ungroup
|
ungroup
|
||||||
pilcrow
|
|
||||||
toc
|
toc
|
||||||
|
|
||||||
# Brands
|
|
||||||
codepen
|
|
||||||
codesandbox
|
|
||||||
dribbble
|
|
||||||
|
|||||||
10
.github/ISSUE_TEMPLATE/01_icon_request.yml
vendored
10
.github/ISSUE_TEMPLATE/01_icon_request.yml
vendored
@@ -7,8 +7,10 @@ body:
|
|||||||
value: |
|
value: |
|
||||||
Before submitting an icon request check if it has already been requested. If there is an open request, please add a 👍.
|
Before submitting an icon request check if it has already been requested. If there is an open request, please add a 👍.
|
||||||
|
|
||||||
**Important note**: No new brand logos are allowed, see https://github.com/lucide-icons/lucide/issues/670.
|
> [!CAUTION]
|
||||||
Existing brand icons will also be phased out. For brand icons, consider using https://simpleicons.org, which offers purpose-built SVGs that are also on a 24×24px grid.
|
> New brand logos are **not** allowed, see our official statement: https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md.
|
||||||
|
>
|
||||||
|
> Existing brand icons are being phased out. Consider using https://simpleicons.org, which offers purpose-built SVGs that are also on a 24×24px grid.
|
||||||
- type: input
|
- type: input
|
||||||
id: name
|
id: name
|
||||||
attributes:
|
attributes:
|
||||||
@@ -41,9 +43,9 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have searched existing icons to make sure it does not already exist and I didn't find any.
|
- label: I have searched existing icons to make sure it does not already exist and I didn't find any.
|
||||||
required: true
|
required: true
|
||||||
- label: I am not requesting a brand logo and the art is not protected by copyright.
|
- label: I am not requesting a brand logo and the art is not protected by copyright, see more at https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md
|
||||||
required: true
|
required: true
|
||||||
- label: I am not requesting an icon that includes religious, political imagery or hate symbols.
|
- label: I am not requesting an icon that includes religious, war/violence related, political imagery or hate symbols.
|
||||||
required: true
|
required: true
|
||||||
- label: I have provided appropriate use cases for the icon(s) requested.
|
- label: I have provided appropriate use cases for the icon(s) requested.
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
1
.github/ISSUE_TEMPLATE/02_bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/02_bug_report.yml
vendored
@@ -22,6 +22,7 @@ body:
|
|||||||
- label: lucide-svelte
|
- label: lucide-svelte
|
||||||
- label: lucide-vue
|
- label: lucide-vue
|
||||||
- label: lucide-vue-next
|
- label: lucide-vue-next
|
||||||
|
- label: lucide-astro
|
||||||
- label: Figma plugin
|
- label: Figma plugin
|
||||||
- label: source/main
|
- label: source/main
|
||||||
- label: other/not relevant
|
- label: other/not relevant
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ body:
|
|||||||
- label: lucide-svelte
|
- label: lucide-svelte
|
||||||
- label: lucide-vue
|
- label: lucide-vue
|
||||||
- label: lucide-vue-next
|
- label: lucide-vue-next
|
||||||
|
- label: lucide-astro
|
||||||
- label: Figma plugin
|
- label: Figma plugin
|
||||||
- label: all JS packages
|
- label: all JS packages
|
||||||
- label: site
|
- label: site
|
||||||
|
|||||||
44
.github/actions/build-and-test.yml
vendored
44
.github/actions/build-and-test.yml
vendored
@@ -1,44 +0,0 @@
|
|||||||
name: 'Build and Test'
|
|
||||||
description: 'Builds and test a package'
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
name:
|
|
||||||
description: “Name of the package”
|
|
||||||
required: true
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: 'composite'
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2
|
|
||||||
name: Install pnpm
|
|
||||||
id: pnpm-install
|
|
||||||
with:
|
|
||||||
version: 8
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
id: pnpm-cache
|
|
||||||
run: |
|
|
||||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --filter lucide-preact
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm --filter lucide-preact build
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: pnpm --filter lucide-preact test
|
|
||||||
41
.github/actions/check-icons.yml
vendored
41
.github/actions/check-icons.yml
vendored
@@ -1,41 +0,0 @@
|
|||||||
name: 'Check icons'
|
|
||||||
description: 'Cross-checks icon and category references in JSON descriptors'
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
name:
|
|
||||||
description: “Name of the package”
|
|
||||||
required: true
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: 'composite'
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2
|
|
||||||
name: Install pnpm
|
|
||||||
id: pnpm-install
|
|
||||||
with:
|
|
||||||
version: 8
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
id: pnpm-cache
|
|
||||||
run: |
|
|
||||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --filter .
|
|
||||||
|
|
||||||
- name: Check icons and categories
|
|
||||||
run: pnpm checkIcons
|
|
||||||
6
.github/labeler.yml
vendored
6
.github/labeler.yml
vendored
@@ -79,6 +79,12 @@
|
|||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- 'packages/lucide-solid/*'
|
- 'packages/lucide-solid/*'
|
||||||
|
|
||||||
|
# For changes in the lucide astro package
|
||||||
|
🚀 astro package:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- 'packages/astro/*'
|
||||||
|
|
||||||
# For changes in the lucide static package
|
# For changes in the lucide static package
|
||||||
🪨 static package:
|
🪨 static package:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
|
|||||||
28
.github/pull_request_template.md
vendored
28
.github/pull_request_template.md
vendored
@@ -1,16 +1,18 @@
|
|||||||
<!-- Thank you for contributing! -->
|
<!-- Thank you for contributing! -->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
PR Title Guidelines:
|
||||||
|
|
||||||
|
Please use the format: <type>(<scope>): <short description>
|
||||||
|
|
||||||
|
Example: feat(icons): added `camera` icon
|
||||||
|
|
||||||
|
Available types: fix, feat, perf, docs, style, refactor, test, chore, ci, build
|
||||||
|
Common scopes: icons, docs, studio, site, dev
|
||||||
|
-->
|
||||||
|
|
||||||
<!-- Insert `closes #issueNumber` here if merging this PR will resolve an existing issue -->
|
<!-- Insert `closes #issueNumber` here if merging this PR will resolve an existing issue -->
|
||||||
|
## Description
|
||||||
## What is the purpose of this pull request?
|
|
||||||
<!-- Please choose one of the following, and put an "x" next to it. -->
|
|
||||||
- [ ] New Icon
|
|
||||||
- [ ] Bug fix
|
|
||||||
- [ ] New Feature
|
|
||||||
- [ ] Documentation update
|
|
||||||
- [ ] Other:
|
|
||||||
|
|
||||||
### Description
|
|
||||||
<!-- Please insert your description here and provide info about the "what" this PR is contribution -->
|
<!-- Please insert your description here and provide info about the "what" this PR is contribution -->
|
||||||
|
|
||||||
### Icon use case <!-- ONLY for new icons, remove this part if not icon PR -->
|
### Icon use case <!-- ONLY for new icons, remove this part if not icon PR -->
|
||||||
@@ -23,10 +25,12 @@
|
|||||||
|
|
||||||
### Concept <!-- ONLY for new icons -->
|
### Concept <!-- ONLY for new icons -->
|
||||||
<!-- All of these requirements must be fulfilled. -->
|
<!-- All of these requirements must be fulfilled. -->
|
||||||
|
<!-- IMPORTANT! Please read our official statement on brand logos in Lucide: -->
|
||||||
|
<!-- https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md -->
|
||||||
- [ ] I have provided valid use cases for each icon.
|
- [ ] I have provided valid use cases for each icon.
|
||||||
- [ ] I have not added any a brand or logo icon.
|
- [ ] I have [not added any brand or logo icon](https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md).
|
||||||
- [ ] I have not used any hate symbols.
|
- [ ] I have not used any hate symbols.
|
||||||
- [ ] I have not included any religious or political imagery.
|
- [ ] I have not included any religious, war/violence related or political imagery.
|
||||||
|
|
||||||
### Author, credits & license<!-- ONLY for new icons. -->
|
### Author, credits & license<!-- ONLY for new icons. -->
|
||||||
<!-- Please choose one of the following, and put an "x" next to it. -->
|
<!-- Please choose one of the following, and put an "x" next to it. -->
|
||||||
|
|||||||
50
.github/workflows/ci.yml
vendored
50
.github/workflows/ci.yml
vendored
@@ -7,20 +7,24 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- icons/**/*.svg
|
- icons/**/*.svg
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write # Required for OIDC
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
create-release:
|
create-release:
|
||||||
if: github.repository == 'lucide-icons/lucide'
|
if: github.repository == 'lucide-icons/lucide' && startsWith(github.event.head_commit.message, 'feat(icons)')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
VERSION: ${{ steps.new-version.outputs.NEW_VERSION }}
|
VERSION: ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
@@ -61,8 +65,40 @@ jobs:
|
|||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ steps.new-version.outputs.NEW_VERSION }}
|
tag_name: ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||||
name: New icons ${{ steps.new-version.outputs.NEW_VERSION }}
|
name: Version ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||||
body: ${{ steps.change-log.outputs.CHANGE_LOG }}
|
generate_release_notes: true
|
||||||
|
|
||||||
|
test-semantic-release:
|
||||||
|
if: github.repository == 'lucide-icons/lucide'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Semantic Release
|
||||||
|
id: semantic
|
||||||
|
uses: cycjimmy/semantic-release-action@v4
|
||||||
|
with:
|
||||||
|
tag_format: ${version}
|
||||||
|
branches: |
|
||||||
|
['new-release-workflow']
|
||||||
|
extends: |
|
||||||
|
semantic-release-monorepo
|
||||||
|
extra_plugins: |
|
||||||
|
@semantic-release/github
|
||||||
|
@semantic-release/git
|
||||||
|
@semantic-release/release-notes-generator
|
||||||
|
conventional-changelog-conventionalcommits
|
||||||
|
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Log output
|
||||||
|
if: steps.semantic.outputs.new_release_published == 'true'
|
||||||
|
run: |
|
||||||
|
echo ${{ steps.semantic.outputs.new_release_version }}
|
||||||
|
echo ${{ steps.semantic.outputs.new_release_major_version }}
|
||||||
|
echo ${{ steps.semantic.outputs.new_release_minor_version }}
|
||||||
|
echo ${{ steps.semantic.outputs.new_release_patch_version }}
|
||||||
|
|
||||||
start-release:
|
start-release:
|
||||||
if: github.repository == 'lucide-icons/lucide'
|
if: github.repository == 'lucide-icons/lucide'
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Check for blocked phrases in issue title
|
- name: Check for blocked phrases in issue title
|
||||||
run: |
|
run: |
|
||||||
ISSUE_TITLE=$(jq -r '.issue.title' "$GITHUB_EVENT_PATH")
|
ISSUE_TITLE=$(jq -r '.issue.title' "$GITHUB_EVENT_PATH")
|
||||||
BLOCKED_PHRASES=("twitter" "whatsapp" "logo" "google" "tiktok" "facebook" "slack" "discord" "bluesky")
|
BLOCKED_PHRASES=("twitter" "whatsapp" "logo" "google" "tiktok" "facebook" "slack" "discord" "bluesky" "spotify" "behance" "pix" "x.com" "telegram")
|
||||||
|
|
||||||
# Check title and body for blocked phrases
|
# Check title and body for blocked phrases
|
||||||
for PHRASE in "${BLOCKED_PHRASES[@]}"
|
for PHRASE in "${BLOCKED_PHRASES[@]}"
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
if echo "$ISSUE_TITLE" | grep -i "$PHRASE"; then
|
if echo "$ISSUE_TITLE" | grep -i "$PHRASE"; then
|
||||||
gh issue close ${{ github.event.issue.number }} --reason "not planned" --comment "This looks like a duplicate, use the [search](https://github.com/lucide-icons/lucide/issues?q=is%3Aissue+$PHRASE) to find similar issues.
|
gh issue close ${{ github.event.issue.number }} --reason "not planned" --comment "This looks like a duplicate, use the [search](https://github.com/lucide-icons/lucide/issues?q=is%3Aissue+$PHRASE) to find similar issues.
|
||||||
|
|
||||||
Read more about brand guideline rules at [We're not accepting new Brand icons #670](https://github.com/lucide-icons/lucide/issues/670).
|
Read [our official statement about brand logos in Lucide](https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md).
|
||||||
|
|
||||||
Always happy to help on [Discord](https://discord.gg/EH6nSts)."
|
Always happy to help on [Discord](https://discord.gg/EH6nSts)."
|
||||||
gh issue lock ${{ github.event.issue.number }} --reason spam
|
gh issue lock ${{ github.event.issue.number }} --reason spam
|
||||||
|
|||||||
58
.github/workflows/comment-icon-preview.yml
vendored
Normal file
58
.github/workflows/comment-icon-preview.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
name: Icon preview comment
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ['Pull request icon preview']
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
upload:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >
|
||||||
|
github.event.workflow_run.event == 'pull_request' &&
|
||||||
|
github.event.workflow_run.conclusion == 'success'
|
||||||
|
steps:
|
||||||
|
- name: 'Download artifact'
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
run_id: context.payload.workflow_run.id,
|
||||||
|
});
|
||||||
|
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
|
||||||
|
return artifact.name == "pr_number"
|
||||||
|
})[0];
|
||||||
|
let download = await github.rest.actions.downloadArtifact({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
artifact_id: matchArtifact.id,
|
||||||
|
archive_format: 'zip',
|
||||||
|
});
|
||||||
|
const fs = require('fs');
|
||||||
|
fs.writeFileSync('${{github.workspace}}/pr_number.zip', Buffer.from(download.data));
|
||||||
|
|
||||||
|
- name: 'Unzip artifact'
|
||||||
|
run: unzip pr_number.zip
|
||||||
|
|
||||||
|
- name: 'Get PR number'
|
||||||
|
run: echo "number=$(cat NR)" >> $GITHUB_OUTPUT
|
||||||
|
id: pr-number
|
||||||
|
|
||||||
|
- name: Find Comment
|
||||||
|
uses: peter-evans/find-comment@v2
|
||||||
|
id: pr-comment
|
||||||
|
with:
|
||||||
|
issue-number: ${{ steps.pr-number.outputs.number }}
|
||||||
|
comment-author: 'github-actions[bot]'
|
||||||
|
body-includes: Added or changed icons
|
||||||
|
|
||||||
|
- name: Create or update comment
|
||||||
|
uses: peter-evans/create-or-update-comment@v3
|
||||||
|
with:
|
||||||
|
comment-id: ${{ steps.pr-comment.outputs.comment-id }}
|
||||||
|
issue-number: ${{ steps.pr-number.outputs.number }}
|
||||||
|
body-path: comment-markup.md
|
||||||
|
edit-mode: replace
|
||||||
1
.github/workflows/labeler.yml
vendored
1
.github/workflows/labeler.yml
vendored
@@ -9,4 +9,5 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/labeler@v5
|
- uses: actions/labeler@v5
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
name: Linting
|
name: Linting PR
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
paths-ignore:
|
||||||
- '**'
|
- icons/*.svg
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
linting:
|
lint-code:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
32
.github/workflows/lint-pr-title.yml
vendored
Normal file
32
.github/workflows/lint-pr-title.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Linting PR
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- edited
|
||||||
|
- reopened
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint-pr-title:
|
||||||
|
name: PR Title Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: amannn/action-semantic-pull-request@v5
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
types: |
|
||||||
|
fix
|
||||||
|
feat
|
||||||
|
perf
|
||||||
|
refactor
|
||||||
|
test
|
||||||
|
style
|
||||||
|
docs
|
||||||
|
ci
|
||||||
|
build
|
||||||
|
chore
|
||||||
|
requireScope: true
|
||||||
|
ignoreLabels: |
|
||||||
|
bot
|
||||||
39
.github/workflows/linting-icons.yml
vendored
Normal file
39
.github/workflows/linting-icons.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: Linting Icons
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'icons/*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint-filenames:
|
||||||
|
name: Lint Filenames
|
||||||
|
if: github.repository == 'lucide-icons/lucide'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
- name: Get changed files
|
||||||
|
id: changed-files
|
||||||
|
uses: tj-actions/changed-files@v46
|
||||||
|
with:
|
||||||
|
files: icons/*
|
||||||
|
|
||||||
|
- name: Generate annotations
|
||||||
|
run: node ./scripts/lintFilenames.mts
|
||||||
|
env:
|
||||||
|
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||||
|
|
||||||
|
lint-aliases:
|
||||||
|
name: Check Uniqueness of Aliases
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- 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 '"
|
||||||
16
.github/workflows/lucide-angular.yml
vendored
16
.github/workflows/lucide-angular.yml
vendored
@@ -11,12 +11,12 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
@@ -27,12 +27,12 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v3.8.1
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|||||||
42
.github/workflows/lucide-astro.yml
vendored
Normal file
42
.github/workflows/lucide-astro.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Lucide Astro Checks
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- packages/astro/**
|
||||||
|
- packages/shared/**
|
||||||
|
- tools/build-icons/**
|
||||||
|
- pnpm-lock.yaml
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: pnpm --filter @lucide/astro build
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
- uses: actions/setup-node@v3.8.1
|
||||||
|
with:
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: pnpm --filter @lucide/astro test
|
||||||
11
.github/workflows/lucide-font.yml
vendored
11
.github/workflows/lucide-font.yml
vendored
@@ -3,7 +3,6 @@ name: Lucide font checks
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- icons/**
|
|
||||||
- tools/build-font/**
|
- tools/build-font/**
|
||||||
- pnpm-lock.yaml
|
- pnpm-lock.yaml
|
||||||
|
|
||||||
@@ -11,12 +10,12 @@ jobs:
|
|||||||
lucide-font:
|
lucide-font:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
@@ -28,7 +27,7 @@ jobs:
|
|||||||
run: pnpm build:font
|
run: pnpm build:font
|
||||||
|
|
||||||
- name: 'Upload to Artifacts'
|
- name: 'Upload to Artifacts'
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: lucide-font
|
name: lucide-font
|
||||||
path: lucide-font
|
path: lucide-font
|
||||||
|
|||||||
8
.github/workflows/lucide-preact.yml
vendored
8
.github/workflows/lucide-preact.yml
vendored
@@ -13,12 +13,12 @@ jobs:
|
|||||||
lucide-preact:
|
lucide-preact:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|||||||
26
.github/workflows/lucide-react-native.yml
vendored
26
.github/workflows/lucide-react-native.yml
vendored
@@ -10,15 +10,31 @@ on:
|
|||||||
- pnpm-lock.yaml
|
- pnpm-lock.yaml
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lucide-react-native:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: pnpm --filter lucide-react-native build
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|||||||
16
.github/workflows/lucide-react.yml
vendored
16
.github/workflows/lucide-react.yml
vendored
@@ -14,12 +14,12 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
@@ -30,12 +30,12 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v3.8.1
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|||||||
8
.github/workflows/lucide-shared.yml
vendored
8
.github/workflows/lucide-shared.yml
vendored
@@ -10,12 +10,12 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v3.8.1
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|||||||
16
.github/workflows/lucide-solid.yml
vendored
16
.github/workflows/lucide-solid.yml
vendored
@@ -13,12 +13,12 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
@@ -29,12 +29,12 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v3.8.1
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|||||||
8
.github/workflows/lucide-static.yml
vendored
8
.github/workflows/lucide-static.yml
vendored
@@ -11,12 +11,12 @@ jobs:
|
|||||||
lucide-static:
|
lucide-static:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|||||||
43
.github/workflows/lucide-svelte-5.yml
vendored
Normal file
43
.github/workflows/lucide-svelte-5.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
name: Lucide Svelte 5 checks
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- packages/svelte/**
|
||||||
|
- packages/shared/**
|
||||||
|
- tools/build-icons/**
|
||||||
|
- tools/rollup-plugins/**
|
||||||
|
- pnpm-lock.yaml
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
|
- 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@v6
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: pnpm --filter @lucide/svelte test
|
||||||
16
.github/workflows/lucide-svelte.yml
vendored
16
.github/workflows/lucide-svelte.yml
vendored
@@ -13,12 +13,12 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
@@ -29,12 +29,12 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v3.8.1
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|||||||
16
.github/workflows/lucide-vue-next.yml
vendored
16
.github/workflows/lucide-vue-next.yml
vendored
@@ -13,12 +13,12 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
@@ -29,12 +29,12 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v3.8.1
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|||||||
16
.github/workflows/lucide-vue.yml
vendored
16
.github/workflows/lucide-vue.yml
vendored
@@ -14,30 +14,30 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm --filter lucide-vue build
|
run: pnpm --filter @lucide/vue build
|
||||||
|
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v3.8.1
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm --filter lucide-vue test
|
run: pnpm --filter @lucide/vue test
|
||||||
|
|||||||
16
.github/workflows/lucide.yml
vendored
16
.github/workflows/lucide.yml
vendored
@@ -12,12 +12,12 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
@@ -28,12 +28,12 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v3.8.1
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|||||||
46
.github/workflows/pull-request-icon-preview.yml
vendored
Normal file
46
.github/workflows/pull-request-icon-preview.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
name: Pull request icon preview
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'icons/*.svg'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
generate-changed-icons-comment:
|
||||||
|
name: Generate Changed Icons Comment
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
|
- name: Get changed files
|
||||||
|
id: changed-files
|
||||||
|
uses: tj-actions/changed-files@v46
|
||||||
|
with:
|
||||||
|
files: icons/*.svg
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
- name: Install svgson for code preview (safer and faster than installing all deps)
|
||||||
|
run: npm install svgson@5.3.1 --force
|
||||||
|
|
||||||
|
- name: Save PR number
|
||||||
|
run: |
|
||||||
|
mkdir -p ./pr
|
||||||
|
echo ${{ github.event.number }} > ./pr/NR
|
||||||
|
|
||||||
|
- name: Generate comment markup
|
||||||
|
run: node ./scripts/generateChangedIconsCommentMarkup.mts >> ./pr/comment-markup.md
|
||||||
|
id: comment-markup
|
||||||
|
env:
|
||||||
|
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: pr_number
|
||||||
|
path: pr/
|
||||||
37
.github/workflows/pull-request-tags-suggestions.yml
vendored
Normal file
37
.github/workflows/pull-request-tags-suggestions.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Pull request tags suggestions
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
paths:
|
||||||
|
- 'icons/*.json'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pull-request-tags-suggestions:
|
||||||
|
name: Pull Request Tags Suggestions
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# We checkout the main branch of main repository for security reasons.
|
||||||
|
# This is to prevent the workflow from running on a forked repository.
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
repository: lucide-icons/lucide
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Generate comment markup
|
||||||
|
run: node ./scripts/suggestTags.mts
|
||||||
|
env:
|
||||||
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||||
|
PULL_REQUEST_NUMBER: ${{ github.event.number }}
|
||||||
|
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
123
.github/workflows/pull-request.yml
vendored
123
.github/workflows/pull-request.yml
vendored
@@ -1,123 +0,0 @@
|
|||||||
name: Add Changed Icons comment
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
paths:
|
|
||||||
- '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@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/*
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
- name: Install simple-git (safer and faster than installing all deps)
|
|
||||||
run: npm install simple-git
|
|
||||||
- name: Generate annotations
|
|
||||||
run: node ./scripts/updateContributors.mjs
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
FETCH_DEPTH: ${{ github.event.pull_request.commits }}
|
|
||||||
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
|
||||||
- name: Generate annotations
|
|
||||||
env:
|
|
||||||
ANNOTATION_SEVERITY: notice
|
|
||||||
ANNOTATION_TITLE: Contributors have changed!
|
|
||||||
ANNOTATION_DESCRIPTION: Don't add people who have only performed automatic optimizations.
|
|
||||||
run: |
|
|
||||||
git diff --unified=0 -- icons/*.json | # diff icon metadata (unified=0 gives the correct chunk line number)
|
|
||||||
perl -ne '/^(\+|- |@)/ && print' | # get chunks (lines that start with "+++", "@@", "+ ", "- ")
|
|
||||||
perl -pe 's/\n/%0A/' | # url encode line breaks (\n -> %0A)
|
|
||||||
perl -pe 's/%0A(\+\+\+ b\/)/\n\1/g' | # split chunks(one chunk per line)
|
|
||||||
perl -pe "s/\+\+\+ b\/([^@]*)%0A@@ -(\d+)[^\s]* \+(\d+)[^@]*@@(.*)/::$ANNOTATION_SEVERITY file=\1,line=\2,endLine=\3,title=$ANNOTATION_TITLE::$ANNOTATION_DESCRIPTION%0A\4/"
|
|
||||||
# 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
|
|
||||||
|
|
||||||
lint-aliases:
|
|
||||||
name: Check Uniqueness of Aliases
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Check Uniqueness of Aliases
|
|
||||||
run: "! cat <(printf \"%s\\n\" icons/*.json | while read -r name; do basename \"$name\" .json; done) <(jq -cr 'select(.aliases) | .aliases[] | if type==\"string\" then . else .name end' icons/*.json) | sort | uniq -c | grep -ve '^\\s*1 '"
|
|
||||||
|
|
||||||
generate-changed-icons-comment:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
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/*.svg
|
|
||||||
|
|
||||||
- name: Find Comment
|
|
||||||
uses: peter-evans/find-comment@v2
|
|
||||||
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@v3
|
|
||||||
with:
|
|
||||||
comment-id: ${{ steps.pr-comment.outputs.comment-id }}
|
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
|
||||||
body-path: ./comment-markup.md
|
|
||||||
edit-mode: replace
|
|
||||||
57
.github/workflows/release.yml
vendored
57
.github/workflows/release.yml
vendored
@@ -18,9 +18,13 @@ on:
|
|||||||
description: Version
|
description: Version
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write # Required for OIDC
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre-release:
|
pre-release:
|
||||||
if: github.repository == 'lucide-icons/lucide' && contains('["ericfennis", "karsa-mistmere"]', github.actor)
|
if: github.repository == 'lucide-icons/lucide' && contains('["ericfennis", "karsa-mistmere", "jguddas"]', github.actor)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
VERSION: ${{ steps.get_version.outputs.VERSION }}
|
VERSION: ${{ steps.get_version.outputs.VERSION }}
|
||||||
@@ -38,6 +42,9 @@ jobs:
|
|||||||
if: github.repository == 'lucide-icons/lucide'
|
if: github.repository == 'lucide-icons/lucide'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: pre-release
|
needs: pre-release
|
||||||
|
permissions:
|
||||||
|
id-token: write # Required for OIDC
|
||||||
|
contents: read
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -46,20 +53,22 @@ jobs:
|
|||||||
'lucide',
|
'lucide',
|
||||||
'lucide-react',
|
'lucide-react',
|
||||||
'lucide-react-native',
|
'lucide-react-native',
|
||||||
'lucide-vue',
|
|
||||||
'lucide-vue-next',
|
'lucide-vue-next',
|
||||||
'lucide-angular',
|
'lucide-angular',
|
||||||
'lucide-preact',
|
'lucide-preact',
|
||||||
'lucide-solid',
|
'lucide-solid',
|
||||||
'lucide-svelte',
|
'lucide-svelte',
|
||||||
|
'@lucide/astro',
|
||||||
|
'@lucide/svelte',
|
||||||
|
'@lucide/vue',
|
||||||
]
|
]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
@@ -77,20 +86,26 @@ jobs:
|
|||||||
run: pnpm --filter ${{ matrix.package }} test
|
run: pnpm --filter ${{ matrix.package }} test
|
||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: pnpm --filter ${{ matrix.package }} publish --no-git-checks --ignore-scripts
|
run: pnpm --filter ${{ matrix.package }} publish --access public --no-git-checks --ignore-scripts
|
||||||
|
env:
|
||||||
|
NPM_CONFIG_PROVENANCE: true
|
||||||
|
|
||||||
lucide-static:
|
lucide-static:
|
||||||
if: github.repository == 'lucide-icons/lucide'
|
if: github.repository == 'lucide-icons/lucide'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [pre-release, lucide-font]
|
needs: [pre-release, lucide-font]
|
||||||
|
permissions:
|
||||||
|
id-token: write # Required for OIDC
|
||||||
|
contents: read
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v4
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
@@ -109,18 +124,20 @@ jobs:
|
|||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: pnpm --filter lucide-static publish --no-git-checks
|
run: pnpm --filter lucide-static publish --no-git-checks
|
||||||
|
env:
|
||||||
|
NPM_CONFIG_PROVENANCE: true
|
||||||
|
|
||||||
lucide-font:
|
lucide-font:
|
||||||
if: github.repository == 'lucide-icons/lucide'
|
if: github.repository == 'lucide-icons/lucide'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: pre-release
|
needs: pre-release
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
node-version-file: 'package.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
@@ -132,7 +149,7 @@ jobs:
|
|||||||
run: pnpm build:font
|
run: pnpm build:font
|
||||||
|
|
||||||
- name: 'Upload to Artifacts'
|
- name: 'Upload to Artifacts'
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: lucide-font
|
name: lucide-font
|
||||||
path: lucide-font
|
path: lucide-font
|
||||||
@@ -143,15 +160,15 @@ jobs:
|
|||||||
needs: [pre-release, lucide-font]
|
needs: [pre-release, lucide-font]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v4
|
||||||
- name: Zip font and icons
|
- name: Zip font and icons
|
||||||
run: |
|
run: |
|
||||||
zip -r lucide-font-${{ needs.pre-release.outputs.VERSION }}.zip lucide-font
|
zip -r lucide-font-${{ needs.pre-release.outputs.VERSION }}.zip lucide-font
|
||||||
zip -r lucide-icons-${{ needs.pre-release.outputs.VERSION }}.zip icons
|
zip -r lucide-icons-${{ needs.pre-release.outputs.VERSION }}.zip icons
|
||||||
|
|
||||||
- name: Release zip and fonts
|
- name: Release zip and fonts
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ needs.pre-release.outputs.VERSION }}
|
tag_name: ${{ needs.pre-release.outputs.VERSION }}
|
||||||
files: |
|
files: |
|
||||||
|
|||||||
33
.github/workflows/request-review.yml
vendored
Normal file
33
.github/workflows/request-review.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
name: 'Request Review'
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened]
|
||||||
|
paths:
|
||||||
|
- icons/*.svg
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
request-review:
|
||||||
|
if: github.event.pull_request.draft == false
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
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/*.svg
|
||||||
|
- run: |
|
||||||
|
while IFS= read -r file; do
|
||||||
|
jq -r '.contributors[]' "${file%.svg}.json"
|
||||||
|
done <<< "$CHANGED_FILES" | while read -r contributor; do
|
||||||
|
gh pr edit "${{ github.event.pull_request.number }}" --add-reviewer "$contributor" || true
|
||||||
|
done
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@
|
|||||||
.obsidian
|
.obsidian
|
||||||
.now
|
.now
|
||||||
.idea
|
.idea
|
||||||
|
.env
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
build
|
build
|
||||||
|
|||||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -2,5 +2,5 @@
|
|||||||
"cSpell.words": ["devs", "preact", "Preact"],
|
"cSpell.words": ["devs", "preact", "Preact"],
|
||||||
"eslint.enable": true,
|
"eslint.enable": true,
|
||||||
"eslint.validate": ["javascript", "svg"],
|
"eslint.validate": ["javascript", "svg"],
|
||||||
"svg.preview.background": "transparent"
|
"svg.preview.background": "editor"
|
||||||
}
|
}
|
||||||
|
|||||||
11
.vscode/svg.code-snippets
vendored
11
.vscode/svg.code-snippets
vendored
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/Yash-Singh1/vscode-snippets-json-schema/main/schema.json",
|
||||||
"Lucide SVG": {
|
"Lucide SVG": {
|
||||||
"scope": "xml",
|
"scope": "xml",
|
||||||
"description": "Base SVG with Lucide attributes.",
|
"description": "Base SVG with Lucide attributes.",
|
||||||
@@ -51,6 +52,16 @@
|
|||||||
],
|
],
|
||||||
"body": "<circle cx=\"${2:12}\" cy=\"${3:$2}\" r=\"${1|10,2,.5\" fill=\"currentColor|}\" />"
|
"body": "<circle cx=\"${2:12}\" cy=\"${3:$2}\" r=\"${1|10,2,.5\" fill=\"currentColor|}\" />"
|
||||||
},
|
},
|
||||||
|
"Squircle": {
|
||||||
|
"scope": "xml",
|
||||||
|
"description": "SVG `path` with Lucide defaults.",
|
||||||
|
"prefix": [
|
||||||
|
"squircle",
|
||||||
|
"path",
|
||||||
|
"<path"
|
||||||
|
],
|
||||||
|
"body": "<path d=\"M12 3c7.2 0 9 1.8 9 9s-1.8 9-9 9-9-1.8-9-9 1.8-9 9-9\" />"
|
||||||
|
},
|
||||||
"Ellipse": {
|
"Ellipse": {
|
||||||
"scope": "xml",
|
"scope": "xml",
|
||||||
"description": "SVG `ellipse`.",
|
"description": "SVG `ellipse`.",
|
||||||
|
|||||||
71
BRAND_LOGOS_STATEMENT.md
Normal file
71
BRAND_LOGOS_STATEMENT.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# Our Official Stance on Including Brand Logos in Lucide
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
Lucide **does not accept** brand logos, and we do not plan to add them in the future.
|
||||||
|
|
||||||
|
This is due to a combination of **legal restrictions**, **design consistency concerns**, and **practical maintenance reasons**.
|
||||||
|
|
||||||
|
If you need brand logos, we recommend [Simple Icons](https://simpleicons.org/), which provides an extensive, legally safer collection of brand marks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Historical Context
|
||||||
|
|
||||||
|
This is not a new debate — other icon sets have gone through the same discussion:
|
||||||
|
|
||||||
|
- **Material Design Icons** [deprecated all brand icons](https://github.com/Templarian/MaterialDesign/issues/6602) because they didn't fit the style, didn't work well in one color, and often looked out of place in a 24×24px grid.
|
||||||
|
- **Feather Icons** [came to the same conclusion](https://github.com/feathericons/feather/issues/763): brand logos have their own style, and forcing them into another inevitably leads to aesthetic compromises.
|
||||||
|
- **Lucide** learned from these examples — we'd rather focus on making a consistent set of non-brand icons that all work together.
|
||||||
|
|
||||||
|
## 2. Legal Considerations
|
||||||
|
|
||||||
|
Most brand logos:
|
||||||
|
- Are **protected by trademark or copyright**.
|
||||||
|
- Have **strict rules** for how they can be used (colors, spacing, proportions, etc.).
|
||||||
|
- **Don't allow modification** — but we'd have to change them to fit Lucide's style.
|
||||||
|
|
||||||
|
This means adding them could:
|
||||||
|
1. Break copyright or trademark law.
|
||||||
|
2. Make both you and the Lucide project legally responsible.
|
||||||
|
3. Force us to review every new request one by one for legal issues — something we simply can't do.
|
||||||
|
|
||||||
|
> **Note:** Simple Icons avoids this by keeping logos exactly as the brand provides them — though even they sometimes face [legal challenges](https://github.com/simple-icons/simple-icons/issues/11236).
|
||||||
|
|
||||||
|
## 3. Design & Consistency
|
||||||
|
|
||||||
|
Lucide is all about **visual consistency**.
|
||||||
|
|
||||||
|
Adding brand logos would:
|
||||||
|
- Break [our own design rules](https://lucide.dev/guide/design/icon-design-guide#icon-design-principles) for shapes, proportions, and stroke.
|
||||||
|
- Mix two fundamentally different categories of graphics (pictograms vs. corporate logos).
|
||||||
|
- Create a library where a subset of icons will always look "out of place".
|
||||||
|
|
||||||
|
If the logos are not in Lucide's style, why include them in Lucide at all? Better to use them from a dedicated brand icon source.
|
||||||
|
|
||||||
|
## 4. Maintenance Burden
|
||||||
|
|
||||||
|
Even with our current **"no brand icon requests"** policy, people still request them regularly.
|
||||||
|
|
||||||
|
Having any brand icons in the set:
|
||||||
|
- Makes people think we might add more in the future.
|
||||||
|
- Leads to repeated requests and the same conversations over and over.
|
||||||
|
- Wastes maintainer time redirecting people to the same explanation.
|
||||||
|
|
||||||
|
Removing them entirely solves this problem.
|
||||||
|
|
||||||
|
## 5. Recommended Alternatives
|
||||||
|
|
||||||
|
If you need brand icons, try:
|
||||||
|
- [Simple Icons](https://simpleicons.org/): offers a huge range of brands, in consistent SVG format, using a 24×24 viewBox, the same as ours.
|
||||||
|
- Official brand asset pages: most major companies provide downloadable SVGs.
|
||||||
|
|
||||||
|
You can use these alongside Lucide without bloating our core library.
|
||||||
|
|
||||||
|
## Final Words
|
||||||
|
|
||||||
|
Lucide is an **icon** set, not a **logo** set.
|
||||||
|
|
||||||
|
Logos belong in dedicated logo resources.
|
||||||
|
|
||||||
|
We're focusing on what Lucide does best: providing a clean, cohesive, and legally safe collection of open-source icons.
|
||||||
@@ -16,10 +16,10 @@ Guidelines for pull requests:
|
|||||||
|
|
||||||
- __Make your commit messages as descriptive as possible.__ Include as much information as you can. Explain anything that the file diffs themselves won’t make apparent.
|
- __Make your commit messages as descriptive as possible.__ Include as much information as you can. Explain anything that the file diffs themselves won’t make apparent.
|
||||||
- __Document your pull request__. Explain your fix, link to the relevant issue, add screenshots when adding new icons.
|
- __Document your pull request__. Explain your fix, link to the relevant issue, add screenshots when adding new icons.
|
||||||
- __Make sure the target of your pull request is the relevant branch__. Most of bugfix or new feature should go to the `main` branch.
|
- __Make sure the target of your pull request is the relevant branch__. Most of bug fixes or new feature should go to the `main` branch.
|
||||||
- __Include only related work__. If your pull request has unrelated commit, it won't be accepted.
|
- __Include only related work__. If your pull request has unrelated commits, it won't be accepted.
|
||||||
|
|
||||||
### Pull Requests Including Icons
|
### Icon Pull Requests
|
||||||
|
|
||||||
#### Guidelines
|
#### Guidelines
|
||||||
|
|
||||||
@@ -27,26 +27,30 @@ Please make sure you follow the icon guidelines, that should be followed to keep
|
|||||||
|
|
||||||
Read it here: [ICON_GUIDELINES](https://lucide.dev/docs/icon-design-guide).
|
Read it here: [ICON_GUIDELINES](https://lucide.dev/docs/icon-design-guide).
|
||||||
|
|
||||||
### Editor guides
|
#### Lucide Studio
|
||||||
|
|
||||||
|
For formatting and adjusting SVG icons, [@jguddas](https://github.com/jguddas) made a great tool called [Lucide Studio](https://studio.lucide.dev/). It is a web-based SVG editor that allows you to edit and adjust icons in the Lucide style. You can use it to create new icons or modify existing ones.
|
||||||
|
|
||||||
|
#### Editor guides
|
||||||
|
|
||||||
Here you can find instructions on how to implement the guidelines with different vector graphics editors:
|
Here you can find instructions on how to implement the guidelines with different vector graphics editors:
|
||||||
|
|
||||||
#### [Adobe Illustrator Guide](https://lucide.dev/docs/illustrator-guide)
|
##### [Adobe Illustrator Guide](https://lucide.dev/docs/illustrator-guide)
|
||||||
|
|
||||||
You can also [download an Adobe Illustrator template](https://github.com/lucide-icons/lucide/blob/main/docs/public/templates/illustrator_template.ai).
|
You can also [download an Adobe Illustrator template](https://github.com/lucide-icons/lucide/blob/main/docs/public/templates/illustrator_template.ai).
|
||||||
|
|
||||||
#### [Inkscape Guide](https://lucide.dev/docs/inkscape-guide)
|
##### [Inkscape Guide](https://lucide.dev/docs/inkscape-guide)
|
||||||
|
|
||||||
#### [Figma Guide](https://lucide.dev/docs/figma-guide)
|
##### [Figma Guide](https://lucide.dev/docs/figma-guide)
|
||||||
|
|
||||||
#### [Affinity Designer Guide](https://lucide.dev/guide/design/affinity-designer-guide)
|
##### [Affinity Designer Guide](https://lucide.dev/guide/design/affinity-designer-guide)
|
||||||
|
|
||||||
### Submitting Multiple Icons
|
#### Submitting Multiple Icons
|
||||||
|
|
||||||
If you want submit multiple icons, please separate the icons and group them. That makes reviewing the icons easier and keep the thread clean and scoped.
|
If you want to submit multiple icons, please separate the icons and group them. That makes reviewing the icons easier and keeps the thread clean and scoped.
|
||||||
So don't submit multiple icons in one PR that have noting to do with each other.
|
So don't submit multiple icons in one PR that have nothing to do with each other.
|
||||||
So for example don't create one PR with icons: `arrow-up`, `bicycle`, `arrow-down`.
|
So for example don't create one PR with icons: `arrow-up`, `bicycle`, `arrow-down`.
|
||||||
Seperate them by two PRs; 'pr-01' `arrow`, `arrow-down` and 'pr-02' `bicycle`.
|
Separate them into two PRs; 'pr-01' `arrow`, `arrow-down` and 'pr-02' `bicycle`.
|
||||||
|
|
||||||
## Icon Requests
|
## Icon Requests
|
||||||
|
|
||||||
@@ -77,7 +81,7 @@ To distribute different packages we use [PNPM workspaces](https://pnpm.io/worksp
|
|||||||
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.
|
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).
|
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.
|
> Note: One package is not managed by pnpm: **lucide-flutter**, this package is written in Dart and uses pub for publishing.
|
||||||
|
|
||||||
### Generated Code
|
### Generated Code
|
||||||
|
|
||||||
@@ -127,7 +131,7 @@ When adding new features to for example the icon component for a framework. It i
|
|||||||
|
|
||||||
### Local Testing
|
### Local Testing
|
||||||
|
|
||||||
To test changes in a local project, you can use `yarn link`, `npm link`, `bun link` or `pnpm link` to link the package. Before you do this make sure you builded the package first.
|
To test changes in a local project, you can use `yarn link`, `npm link`, `bun link` or `pnpm link` to link the package. Before you do this make sure you've built the package first.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# in packages/lucide-react
|
# in packages/lucide-react
|
||||||
@@ -157,6 +161,30 @@ lucide
|
|||||||
|
|
||||||
The lucide.dev website is using [vitepress](https://vitepress.dev/) to generate the static website. The markdown files are located in the docs directory.
|
The lucide.dev website is using [vitepress](https://vitepress.dev/) to generate the static website. The markdown files are located in the docs directory.
|
||||||
|
|
||||||
|
#### Running the Docs Website Locally
|
||||||
|
|
||||||
|
To test the docs website locally, follow these steps:
|
||||||
|
|
||||||
|
1. **Navigate to the docs directory**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd docs
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Start the Local Development Server**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm run docs:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Open the Website Locally**
|
||||||
|
|
||||||
|
Vitepress should open with the following format:
|
||||||
|
|
||||||
|
VitePress dev server is running at:
|
||||||
|
- **Local**: `http://localhost:3000/`
|
||||||
|
- **Network**: `http://192.168.x.x:3000/`
|
||||||
|
|
||||||
### Guides
|
### Guides
|
||||||
|
|
||||||
Detailed documentation about: installation, guides, packages, design guides etc.
|
Detailed documentation about: installation, guides, packages, design guides etc.
|
||||||
@@ -165,15 +193,13 @@ Detailed documentation about: installation, guides, packages, design guides etc.
|
|||||||
|
|
||||||
All the icons of lucide in SVG format. These will be used as source for all the packages and other distributions for the lucide icons.
|
All the icons of lucide in SVG format. These will be used as source for all the packages and other distributions for the lucide icons.
|
||||||
|
|
||||||
### packages
|
### Packages
|
||||||
|
|
||||||
Includes all the (npm) packages of lucide.
|
Includes all the (npm) packages of lucide.
|
||||||
|
|
||||||
> Note: One package is not managed by pnpm: **lucide-flutter**, this package is written in Dart and used pub for publishing.
|
### Scripts
|
||||||
|
|
||||||
### scripts
|
Includes useful 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.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
@@ -190,4 +216,4 @@ If you need any help or have problems with you contribution. Please don't hesita
|
|||||||
Thank you to all the people who already contributed to Lucide!
|
Thank you to all the people who already contributed to Lucide!
|
||||||
|
|
||||||
<a href="https://github.com/lucide-icons/lucide/graphs/contributors">
|
<a href="https://github.com/lucide-icons/lucide/graphs/contributors">
|
||||||
<img src="https://opencollective.com/lucide-icons/contributors.svg?width=890" /></a>
|
<img src="https://opencollective.com/lucide-icons/contributors.svg?width=800" /></a>
|
||||||
|
|||||||
26
LICENSE
26
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
ISC License
|
ISC License
|
||||||
|
|
||||||
Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022.
|
Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2025.
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
@@ -13,3 +13,27 @@ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|||||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
The MIT License (MIT) (for portions derived from Feather)
|
||||||
|
|
||||||
|
Copyright (c) 2013-2023 Cole Bemis
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -40,6 +40,7 @@ Lucide is an open-source icon library that provides 1000+ vector (svg) files for
|
|||||||
| <img src="https://lucide.dev/framework-logos/preact.svg" alt="Preact logo" width="48"> | **`lucide-preact`** | [](https://www.npmjs.com/package/lucide-preact) |  | [Docs](https://lucide.dev/guide/packages/lucide-preact) · [Source](./packages/lucide-preact) |
|
| <img src="https://lucide.dev/framework-logos/preact.svg" alt="Preact logo" width="48"> | **`lucide-preact`** | [](https://www.npmjs.com/package/lucide-preact) |  | [Docs](https://lucide.dev/guide/packages/lucide-preact) · [Source](./packages/lucide-preact) |
|
||||||
| <img src="https://lucide.dev/framework-logos/react-native.svg" alt="React Native logo" width="48"> | **`lucide-react-native`** | [](https://www.npmjs.com/package/lucide-react-native) |  | [Docs](https://lucide.dev/guide/packages/lucide-react-native) · [Source](./packages/lucide-react-native) |
|
| <img src="https://lucide.dev/framework-logos/react-native.svg" alt="React Native logo" width="48"> | **`lucide-react-native`** | [](https://www.npmjs.com/package/lucide-react-native) |  | [Docs](https://lucide.dev/guide/packages/lucide-react-native) · [Source](./packages/lucide-react-native) |
|
||||||
| <img src="https://lucide.dev/framework-logos/angular.svg" alt="Angular logo" width="48"> | **`lucide-angular`** | [](https://www.npmjs.com/package/lucide-angular) |  | [Docs](https://lucide.dev/guide/packages/lucide-angular) · [Source](./packages/lucide-angular) |
|
| <img src="https://lucide.dev/framework-logos/angular.svg" alt="Angular logo" width="48"> | **`lucide-angular`** | [](https://www.npmjs.com/package/lucide-angular) |  | [Docs](https://lucide.dev/guide/packages/lucide-angular) · [Source](./packages/lucide-angular) |
|
||||||
|
| <img src="https://lucide.dev/framework-logos/astro.svg" alt="Astro logo" width="48"> | **`@lucide/astro`** | [](https://www.npmjs.com/package/@lucide/astro) |  | [Docs](https://lucide.dev/guide/packages/lucide-astro) · [Source](./packages/astro) |
|
||||||
| <img src="https://lucide.dev/framework-logos/svg.svg" alt="SVG logo" width="48"> | **`lucide-static`** | [](https://www.npmjs.com/package/lucide-static) |  | [Docs](https://lucide.dev/guide/packages/lucide-static) · [Source](./packages/lucide-static) |
|
| <img src="https://lucide.dev/framework-logos/svg.svg" alt="SVG logo" width="48"> | **`lucide-static`** | [](https://www.npmjs.com/package/lucide-static) |  | [Docs](https://lucide.dev/guide/packages/lucide-static) · [Source](./packages/lucide-static) |
|
||||||
|
|
||||||
### Figma
|
### Figma
|
||||||
@@ -56,6 +57,12 @@ For more info on how to contribute please see the [contribution guidelines](http
|
|||||||
|
|
||||||
Caught a mistake or want to contribute to the documentation? [Edit this page on Github](https://github.com/lucide-icons/lucide/blob/main/README.md)
|
Caught a mistake or want to contribute to the documentation? [Edit this page on Github](https://github.com/lucide-icons/lucide/blob/main/README.md)
|
||||||
|
|
||||||
|
## About brand logos
|
||||||
|
|
||||||
|
Lucide **does not accept** brand logos, and we do not plan to add them in the future. This is due to a combination of **legal restrictions**, **design consistency concerns**, and **practical maintenance reasons**.
|
||||||
|
|
||||||
|
[Click here to read our official statement about brand logos in Lucide.](./BRAND_LOGOS_STATEMENT.md)
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
Join the community on our [Discord](https://discord.gg/EH6nSts) server!
|
Join the community on our [Discord](https://discord.gg/EH6nSts) server!
|
||||||
@@ -69,7 +76,9 @@ Lucide is totally free for commercial use and personal use, this software is lic
|
|||||||
Thank you to all the people who contributed to Lucide!
|
Thank you to all the people who contributed to Lucide!
|
||||||
|
|
||||||
<a href="https://github.com/lucide-icons/lucide/graphs/contributors">
|
<a href="https://github.com/lucide-icons/lucide/graphs/contributors">
|
||||||
<img src="https://opencollective.com/lucide-icons/contributors.svg?width=890" /></a>
|
|
||||||
|
<img src="https://opencollective.com/lucide-icons/contributors.svg?width=800" />
|
||||||
|
</a>
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
@@ -81,5 +90,5 @@ Thank you to all the people who contributed to Lucide!
|
|||||||
|
|
||||||
### Awesome backers 🍺
|
### Awesome backers 🍺
|
||||||
|
|
||||||
<a href="https://www.scipress.io?utm_source=lucide"><img src="docs/public/sponsors/scipress.svg" width="180" alt="Scipress sponsor badge" /></a>
|
|
||||||
<a href="https://github.com/pdfme/pdfme"><img src="docs/public/sponsors/pdfme.svg" width="180" alt="pdfme sponsor badge" /></a>
|
<a href="https://github.com/pdfme/pdfme"><img src="docs/public/sponsors/pdfme.svg" width="180" alt="pdfme sponsor badge" /></a>
|
||||||
|
<a href="https://www.fina.money/"><img src="docs/public/sponsors/fina-money.png" width="180" alt="Fina Money sponsor badge" /></a>
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "../category.schema.json",
|
|
||||||
"title": "Brands",
|
|
||||||
"icon": "facebook"
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../category.schema.json",
|
"$schema": "../category.schema.json",
|
||||||
"title": "Navigation",
|
"title": "Navigation, Maps, and POIs",
|
||||||
"icon": "compass"
|
"icon": "compass"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,12 @@ export default eventHandler((event) => {
|
|||||||
const data = pathData.at(-1).slice(0, -4);
|
const data = pathData.at(-1).slice(0, -4);
|
||||||
const [name] = pathData;
|
const [name] = pathData;
|
||||||
|
|
||||||
const src = Buffer.from(data, 'base64')
|
const src = Buffer.from(data, 'base64').toString('utf8').replaceAll('\n', '');
|
||||||
.toString('utf8')
|
|
||||||
.replaceAll('\n', '')
|
const width = parseInt((src.includes('<svg ') ? src.match(/width="(\d+)"/)?.[1] : null) ?? '24');
|
||||||
.replace(/<svg[^>]*>|<\/svg>/g, '');
|
const height = parseInt(
|
||||||
|
(src.includes('<svg ') ? src.match(/height="(\d+)"/)?.[1] : null) ?? '24',
|
||||||
|
);
|
||||||
|
|
||||||
const children = [];
|
const children = [];
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ export default eventHandler((event) => {
|
|||||||
children.push(
|
children.push(
|
||||||
createElement(Backdrop, {
|
createElement(Backdrop, {
|
||||||
backdropString,
|
backdropString,
|
||||||
src,
|
src: src.replace(/<svg[^>]*>|<\/svg>/g, ''),
|
||||||
color: '#777',
|
color: '#777',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -46,7 +48,18 @@ export default eventHandler((event) => {
|
|||||||
|
|
||||||
const svg = Buffer.from(
|
const svg = Buffer.from(
|
||||||
// We can't use jsx here, is not supported here by nitro.
|
// We can't use jsx here, is not supported here by nitro.
|
||||||
renderToString(createElement(SvgPreview, { src, showGrid: true }, children)),
|
renderToString(
|
||||||
|
createElement(
|
||||||
|
SvgPreview,
|
||||||
|
{
|
||||||
|
src: src.replace(/<svg[^>]*>|<\/svg>/g, ''),
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
showGrid: true,
|
||||||
|
},
|
||||||
|
children,
|
||||||
|
),
|
||||||
|
),
|
||||||
).toString('utf8');
|
).toString('utf8');
|
||||||
|
|
||||||
defaultContentType(event, 'image/svg+xml');
|
defaultContentType(event, 'image/svg+xml');
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ export default eventHandler((event) => {
|
|||||||
.replaceAll('\n', '')
|
.replaceAll('\n', '')
|
||||||
.replace(/<svg[^>]*>|<\/svg>/g, '');
|
.replace(/<svg[^>]*>|<\/svg>/g, '');
|
||||||
|
|
||||||
|
const width = parseInt(
|
||||||
|
(newSrc.includes('<svg ') ? newSrc.match(/width="(\d+)"/)?.[1] : null) ?? '24',
|
||||||
|
);
|
||||||
|
const height = parseInt(
|
||||||
|
(newSrc.includes('<svg ') ? newSrc.match(/height="(\d+)"/)?.[1] : null) ?? '24',
|
||||||
|
);
|
||||||
|
|
||||||
const children = [];
|
const children = [];
|
||||||
|
|
||||||
const oldSrc = iconNodes[name]
|
const oldSrc = iconNodes[name]
|
||||||
@@ -27,7 +34,9 @@ export default eventHandler((event) => {
|
|||||||
|
|
||||||
const svg = Buffer.from(
|
const svg = Buffer.from(
|
||||||
// We can't use jsx here, is not supported here by nitro.
|
// We can't use jsx here, is not supported here by nitro.
|
||||||
renderToString(createElement(Diff, { oldSrc, newSrc, showGrid: true }, children)),
|
renderToString(
|
||||||
|
createElement(Diff, { oldSrc, newSrc, showGrid: true, height, width }, children),
|
||||||
|
),
|
||||||
).toString('utf8');
|
).toString('utf8');
|
||||||
|
|
||||||
defaultContentType(event, 'image/svg+xml');
|
defaultContentType(event, 'image/svg+xml');
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
|
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
|
||||||
import { Resvg, initWasm } from '@resvg/resvg-wasm';
|
import { Resvg, initWasm } from '@resvg/resvg-wasm';
|
||||||
|
import iconNodes from '../../../data/iconNodes';
|
||||||
import wasm from './loadWasm';
|
import wasm from './loadWasm';
|
||||||
|
import { createElement } from 'react';
|
||||||
|
import { renderToStaticMarkup } from 'react-dom/server';
|
||||||
|
import createLucideIcon from 'lucide-react/src/createLucideIcon';
|
||||||
|
|
||||||
var initializedResvg = initWasm(wasm);
|
var initializedResvg = initWasm(wasm);
|
||||||
|
|
||||||
@@ -9,27 +13,37 @@ export default eventHandler(async (event) => {
|
|||||||
await initializedResvg;
|
await initializedResvg;
|
||||||
|
|
||||||
const imageSize = 96;
|
const imageSize = 96;
|
||||||
const [iconSizeString, svgData] = params.data.split('/');
|
const name = params.data.split('/').at(-3);
|
||||||
|
const iconSizeString = params.data.split('/').at(-2);
|
||||||
|
const svgData = params.data.split('/').at(-1);
|
||||||
const iconSize = parseInt(iconSizeString, 10);
|
const iconSize = parseInt(iconSizeString, 10);
|
||||||
const data = svgData.slice(0, -4);
|
const data = svgData.slice(0, -4);
|
||||||
|
|
||||||
|
const prevSvg = iconNodes[name]
|
||||||
|
? renderToStaticMarkup(createElement(createLucideIcon(name, iconNodes[name])))
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const src = Buffer.from(data, 'base64').toString('utf8');
|
const src = Buffer.from(data, 'base64').toString('utf8');
|
||||||
const svg = (src.includes('<svg') ? src : `<svg>${src}</svg>`)
|
const svg = (src.includes('<svg') ? src : `<svg>${src}</svg>`)
|
||||||
.replace(/(\r\n|\n|\r)/gm, '')
|
.replace(/(\r\n|\n|\r)/gm, '')
|
||||||
.replace(
|
.replace(
|
||||||
/<svg[^>]*/,
|
/<svg[^>]*>/,
|
||||||
`<svg
|
`<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="${iconSize}"
|
width="${iconSize}"
|
||||||
height="${iconSize}"
|
height="${prevSvg ? iconSize * 2 : iconSize}"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 ${prevSvg ? 48 : 24}"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="#fff"
|
stroke="#fff"
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
${prevSvg?.replaceAll('\n', '').replace(/<svg[^>]*>|<\/svg>/g, '')}
|
||||||
|
<g transform="translate(0, ${prevSvg ? 24 : 0})">
|
||||||
`,
|
`,
|
||||||
);
|
)
|
||||||
|
.replace(/<\/svg>/, '</g></svg>');
|
||||||
|
|
||||||
const resvg = new Resvg(svg, { background: '#000' });
|
const resvg = new Resvg(svg, { background: '#000' });
|
||||||
const pngData = resvg.render();
|
const pngData = resvg.render();
|
||||||
@@ -39,7 +53,7 @@ export default eventHandler(async (event) => {
|
|||||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
|
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="${imageSize}" height="${imageSize}" viewBox="0 0 ${imageSize} ${imageSize}">
|
<svg xmlns="http://www.w3.org/2000/svg" width="${imageSize}" height="${prevSvg ? imageSize * 2 : imageSize}" viewBox="0 0 ${imageSize} ${prevSvg ? imageSize * 2 : imageSize}">
|
||||||
<style>
|
<style>
|
||||||
@media screen and (prefers-color-scheme: light) {
|
@media screen and (prefers-color-scheme: light) {
|
||||||
#fallback-background { fill: transparent; }
|
#fallback-background { fill: transparent; }
|
||||||
@@ -52,20 +66,20 @@ export default eventHandler(async (event) => {
|
|||||||
<mask id="mask">
|
<mask id="mask">
|
||||||
<image
|
<image
|
||||||
width="${imageSize}"
|
width="${imageSize}"
|
||||||
height="${imageSize}"
|
height="${prevSvg ? imageSize * 2 : imageSize}"
|
||||||
href="data:image/png;base64,${pngBuffer.toString('base64')}"
|
href="data:image/png;base64,${pngBuffer.toString('base64')}"
|
||||||
image-rendering="pixelated"
|
image-rendering="pixelated"
|
||||||
/>
|
/>
|
||||||
</mask>
|
</mask>
|
||||||
<rect
|
<rect
|
||||||
id="fallback-background"
|
id="fallback-background"
|
||||||
width="${imageSize}"
|
width="100%"
|
||||||
height="${imageSize}" ry="${imageSize / 24}"
|
height="100%" ry="${imageSize / 24}"
|
||||||
fill="#fff"
|
fill="#fff"
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
width="${imageSize}"
|
width="100%"
|
||||||
height="${imageSize}"
|
height="100%"
|
||||||
fill="#000"
|
fill="#000"
|
||||||
mask="url(#mask)"
|
mask="url(#mask)"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ if (process.env.NODE_ENV === 'development') {
|
|||||||
|
|
||||||
wasm = fs.readFileSync(require.resolve('@resvg/resvg-wasm/index_bg.wasm'));
|
wasm = fs.readFileSync(require.resolve('@resvg/resvg-wasm/index_bg.wasm'));
|
||||||
} else {
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
wasm = resvg_wasm;
|
wasm = resvg_wasm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { fileURLToPath, URL } from 'node:url';
|
import { fileURLToPath, URL } from 'node:url';
|
||||||
import { defineConfig } from 'vitepress';
|
import { defineConfig } from 'vitepress';
|
||||||
|
import { groupIconMdPlugin, groupIconVitePlugin } from 'vitepress-plugin-group-icons'
|
||||||
|
import container from 'markdown-it-container';
|
||||||
|
import { renderSandbox } from 'vitepress-plugin-sandpack';
|
||||||
import sidebar from './sidebar';
|
import sidebar from './sidebar';
|
||||||
|
import snackPlayer from './plugins/snackPlayer';
|
||||||
|
|
||||||
const title = 'Lucide';
|
const title = 'Lucide';
|
||||||
const socialTitle = 'Lucide Icons';
|
const socialTitle = 'Lucide Icons';
|
||||||
@@ -13,6 +17,19 @@ export default defineConfig({
|
|||||||
cleanUrls: true,
|
cleanUrls: true,
|
||||||
outDir: '.vercel/output/static',
|
outDir: '.vercel/output/static',
|
||||||
srcExclude: ['**/README.md'],
|
srcExclude: ['**/README.md'],
|
||||||
|
markdown: {
|
||||||
|
config(md) {
|
||||||
|
md.use(groupIconMdPlugin);
|
||||||
|
md.use(snackPlayer);
|
||||||
|
md.use(container, 'sandbox', {
|
||||||
|
render (tokens, idx) {
|
||||||
|
console.log(tokens);
|
||||||
|
|
||||||
|
return renderSandbox(tokens, idx, 'sandbox');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
vite: {
|
vite: {
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: [
|
alias: [
|
||||||
@@ -34,8 +51,18 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
plugins: [
|
||||||
|
groupIconVitePlugin()
|
||||||
|
],
|
||||||
},
|
},
|
||||||
head: [
|
head: [
|
||||||
|
[
|
||||||
|
'link',
|
||||||
|
{
|
||||||
|
rel: 'preconnect',
|
||||||
|
href: 'https://analytics.lucide.dev',
|
||||||
|
},
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'script',
|
'script',
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,10 +15,6 @@
|
|||||||
"name": "arrows",
|
"name": "arrows",
|
||||||
"title": "Arrows"
|
"title": "Arrows"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "brands",
|
|
||||||
"title": "Brands"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "buildings",
|
"name": "buildings",
|
||||||
"title": "Buildings"
|
"title": "Buildings"
|
||||||
@@ -101,7 +97,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "navigation",
|
"name": "navigation",
|
||||||
"title": "Navigation"
|
"title": "Navigation, Maps, and POIs"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "notifications",
|
"name": "notifications",
|
||||||
|
|||||||
@@ -7,6 +7,14 @@
|
|||||||
"dark": "/company-logos/vercel-dark.svg"
|
"dark": "/company-logos/vercel-dark.svg"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "MDN Web Docs",
|
||||||
|
"url": "https://developer.mozilla.org/",
|
||||||
|
"image": {
|
||||||
|
"light": "/company-logos/mdn-light.svg",
|
||||||
|
"dark": "/company-logos/mdn-dark.svg"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Supabase",
|
"name": "Supabase",
|
||||||
"url": "https://supabase.com",
|
"url": "https://supabase.com",
|
||||||
@@ -23,6 +31,14 @@
|
|||||||
"dark": "/company-logos/obsidian-dark.svg"
|
"dark": "/company-logos/obsidian-dark.svg"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Nuxt",
|
||||||
|
"url": "https://nuxt.com/",
|
||||||
|
"image": {
|
||||||
|
"light": "/company-logos/nuxt-light.svg",
|
||||||
|
"dark": "/company-logos/nuxt-dark.svg"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Open Collective",
|
"name": "Open Collective",
|
||||||
"url": "https://opencollective.com",
|
"url": "https://opencollective.com",
|
||||||
|
|||||||
@@ -31,20 +31,12 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lucide-vue-next": {
|
"@lucide/vue": {
|
||||||
"order": 2,
|
"order": 2,
|
||||||
"icon": "vue-next",
|
"icon": "vue",
|
||||||
|
"docsAlias": "lucide-vue",
|
||||||
|
"packageDirname": "vue",
|
||||||
"shields": [
|
"shields": [
|
||||||
{
|
|
||||||
"alt": "npm",
|
|
||||||
"src": "https://img.shields.io/npm/v/lucide-vue-next",
|
|
||||||
"href": "https://www.npmjs.com/package/lucide-vue-next"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"alt": "npm",
|
|
||||||
"src": "https://img.shields.io/npm/dw/lucide-vue-next",
|
|
||||||
"href": "https://www.npmjs.com/package/lucide-vue-next"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lucide-svelte": {
|
"lucide-svelte": {
|
||||||
@@ -127,8 +119,27 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lucide-static": {
|
"@lucide/astro": {
|
||||||
|
"docsAlias": "lucide-astro",
|
||||||
|
"packageDirname": "astro",
|
||||||
"order": 8,
|
"order": 8,
|
||||||
|
"icon": "astro",
|
||||||
|
"iconDark": "astro-dark",
|
||||||
|
"shields": [
|
||||||
|
{
|
||||||
|
"alt": "npm",
|
||||||
|
"src": "https://img.shields.io/npm/v/@lucide/astro",
|
||||||
|
"href": "https://www.npmjs.com/package/@lucide/astro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alt": "npm",
|
||||||
|
"src": "https://img.shields.io/npm/dw/@lucide/astro",
|
||||||
|
"href": "https://www.npmjs.com/package/@lucide/astro"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lucide-static": {
|
||||||
|
"order": 9,
|
||||||
"icon": "svg",
|
"icon": "svg",
|
||||||
"shields": [
|
"shields": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -77,9 +77,9 @@
|
|||||||
"source": "https://github.com/swisnl/nuxt-lucide-icons",
|
"source": "https://github.com/swisnl/nuxt-lucide-icons",
|
||||||
"documentation": "https://github.com/swisnl/nuxt-lucide-icons/blob/main/README.md"
|
"documentation": "https://github.com/swisnl/nuxt-lucide-icons/blob/main/README.md"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "lucide-lustre",
|
"name": "lucide_lustre",
|
||||||
"description": "A library providing https://lucide.dev icons to lustre",
|
"description": "A library providing https://lucide.dev icons to lustre.",
|
||||||
"icon": "/framework-logos/lustre.webp",
|
"icon": "/framework-logos/lustre.webp",
|
||||||
"shields": [
|
"shields": [
|
||||||
{
|
{
|
||||||
@@ -95,5 +95,81 @@
|
|||||||
],
|
],
|
||||||
"source": "https://github.com/dinkelspiel/lucide_lustre",
|
"source": "https://github.com/dinkelspiel/lucide_lustre",
|
||||||
"documentation": "https://github.com/dinkelspiel/lucide_lustre/blob/master/README.md"
|
"documentation": "https://github.com/dinkelspiel/lucide_lustre/blob/master/README.md"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lucide_icons_flutter",
|
||||||
|
"description": "A library providing https://lucide.dev icons to Flutter.",
|
||||||
|
"icon": "/framework-logos/flutter.svg",
|
||||||
|
"shields": [
|
||||||
|
{
|
||||||
|
"alt": "Latest Stable Version",
|
||||||
|
"src": "https://img.shields.io/pub/v/lucide_icons_flutter",
|
||||||
|
"href": "https://pub.dev/packages/lucide_icons_flutter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alt": "Total Downloads",
|
||||||
|
"src": "https://img.shields.io/pub/dm/lucide_icons_flutter",
|
||||||
|
"href": "https://pub.dev/packages/lucide_icons_flutter"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": "https://github.com/vqh2602/lucide-flutter-main",
|
||||||
|
"documentation": "https://pub.dev/documentation/lucide_icons_flutter/latest/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lucide-slint",
|
||||||
|
"description": "Implementation of the lucide icon library for Slint.",
|
||||||
|
"icon": "/framework-logos/slint.svg",
|
||||||
|
"shields": [
|
||||||
|
{
|
||||||
|
"alt": "Latest Stable Version",
|
||||||
|
"src": "https://img.shields.io/crates/v/lucide-slint",
|
||||||
|
"href": "https://crates.io/crates/lucide-slint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alt": "Total Downloads",
|
||||||
|
"src": "https://img.shields.io/crates/d/lucide-slint",
|
||||||
|
"href": "https://crates.io/crates/lucide-slint"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": "https://github.com/cnlancehu/lucide-slint",
|
||||||
|
"documentation": "https://github.com/cnlancehu/lucide-slint/blob/main/README.md"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lucide-go",
|
||||||
|
"description": "Implementation of Lucide icons for Go's html/template package.",
|
||||||
|
"icon": "/framework-logos/go.svg",
|
||||||
|
"shields": [
|
||||||
|
{
|
||||||
|
"alt": "Latest Stable Version",
|
||||||
|
"src": "https://img.shields.io/github/v/release/kaugesaar/lucide-go",
|
||||||
|
"href": "https://github.com/kaugesaar/lucide-go/releases"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alt": "Go Reference",
|
||||||
|
"src": "https://pkg.go.dev/badge/github.com/kaugesaar/lucide-go.svg",
|
||||||
|
"href": "https://pkg.go.dev/github.com/kaugesaar/lucide-go"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": "https://github.com/kaugesaar/lucide-go",
|
||||||
|
"documentation": "https://github.com/kaugesaar/lucide-go/blob/master/README.md"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lucide-rails",
|
||||||
|
"description": "Ruby on Rails views helper method for rendering Lucide icons.",
|
||||||
|
"icon": "/framework-logos/rails.svg",
|
||||||
|
"shields": [
|
||||||
|
{
|
||||||
|
"alt": "Latest Stable Version",
|
||||||
|
"src": "https://img.shields.io/gem/v/lucide-rails",
|
||||||
|
"href": "https://rubygems.org/gems/lucide-rails"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alt": "Total Downloads",
|
||||||
|
"src": "https://img.shields.io/gem/dt/lucide-rails",
|
||||||
|
"href": "https://rubygems.org/gems/lucide-rails"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": "https://github.com/heyvito/lucide-rails",
|
||||||
|
"documentation": "https://github.com/heyvito/lucide-rails/blob/master/README.md"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ const Backdrop = ({
|
|||||||
<rect
|
<rect
|
||||||
x="0"
|
x="0"
|
||||||
y="0"
|
y="0"
|
||||||
width="24"
|
width="100%"
|
||||||
height="24"
|
height="100%"
|
||||||
fill="#fff"
|
fill="#fff"
|
||||||
stroke="none"
|
stroke="none"
|
||||||
/>
|
/>
|
||||||
@@ -67,8 +67,8 @@ const Backdrop = ({
|
|||||||
<rect
|
<rect
|
||||||
x="0"
|
x="0"
|
||||||
y="0"
|
y="0"
|
||||||
width="24"
|
width="100%"
|
||||||
height="24"
|
height="100%"
|
||||||
opacity={0.5}
|
opacity={0.5}
|
||||||
fill={`url(#pattern-${id})`}
|
fill={`url(#pattern-${id})`}
|
||||||
stroke="none"
|
stroke="none"
|
||||||
|
|||||||
@@ -7,15 +7,17 @@ const SvgPreview = React.forwardRef<
|
|||||||
{
|
{
|
||||||
oldSrc: string;
|
oldSrc: string;
|
||||||
newSrc: string;
|
newSrc: string;
|
||||||
|
height: number;
|
||||||
|
width: number;
|
||||||
} & React.SVGProps<SVGSVGElement>
|
} & React.SVGProps<SVGSVGElement>
|
||||||
>(({ oldSrc, newSrc, children, ...props }, ref) => {
|
>(({ oldSrc, newSrc, children, height, width, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
ref={ref}
|
ref={ref}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width={24}
|
width={width}
|
||||||
height={24}
|
height={height}
|
||||||
viewBox="0 0 24 24"
|
viewBox={`0 0 ${width} ${height}`}
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
@@ -25,6 +27,8 @@ const SvgPreview = React.forwardRef<
|
|||||||
>
|
>
|
||||||
<style>{darkModeCss}</style>
|
<style>{darkModeCss}</style>
|
||||||
<Grid
|
<Grid
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
strokeWidth={0.1}
|
strokeWidth={0.1}
|
||||||
stroke="#777"
|
stroke="#777"
|
||||||
strokeOpacity={0.3}
|
strokeOpacity={0.3}
|
||||||
@@ -37,8 +41,8 @@ const SvgPreview = React.forwardRef<
|
|||||||
<rect
|
<rect
|
||||||
x="0"
|
x="0"
|
||||||
y="0"
|
y="0"
|
||||||
width="24"
|
width="100%"
|
||||||
height="24"
|
height="100%"
|
||||||
fill="#000"
|
fill="#000"
|
||||||
stroke="none"
|
stroke="none"
|
||||||
/>
|
/>
|
||||||
|
|||||||
137
docs/.vitepress/lib/SvgPreview/GapViolationHighlight.tsx
Normal file
137
docs/.vitepress/lib/SvgPreview/GapViolationHighlight.tsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import pathToPoints from './path-to-points';
|
||||||
|
import { Path, PathProps } from './types';
|
||||||
|
|
||||||
|
export const GapViolationHighlight = ({
|
||||||
|
radius,
|
||||||
|
stroke,
|
||||||
|
strokeWidth,
|
||||||
|
strokeOpacity,
|
||||||
|
paths,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
paths: Path[];
|
||||||
|
} & PathProps<'stroke' | 'strokeOpacity' | 'strokeWidth', 'd'>) => {
|
||||||
|
const id = React.useId();
|
||||||
|
|
||||||
|
const groupedPaths = Object.entries(
|
||||||
|
paths.reduce(
|
||||||
|
(groups, val) => {
|
||||||
|
const key = val.c.id;
|
||||||
|
groups[key] = [...(groups[key] || []), val];
|
||||||
|
return groups;
|
||||||
|
},
|
||||||
|
{} as Record<number, Path[]>,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const groups: Group[] = [];
|
||||||
|
|
||||||
|
for (const [, paths] of groupedPaths) {
|
||||||
|
const d = paths.map((path) => path.d).join(' ');
|
||||||
|
const points = paths.flatMap((path) => pathToPoints(path));
|
||||||
|
groups.push({ id: d, points });
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergedGroups = mergeGroups(groups, 2);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g {...props}>
|
||||||
|
<defs xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<pattern
|
||||||
|
id={`backdrop-pattern-${id}`}
|
||||||
|
width=".1"
|
||||||
|
height=".1"
|
||||||
|
patternUnits="userSpaceOnUse"
|
||||||
|
patternTransform="rotate(45 50 50)"
|
||||||
|
>
|
||||||
|
<line
|
||||||
|
stroke={stroke}
|
||||||
|
strokeWidth={0.1}
|
||||||
|
y2={1}
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
stroke={stroke}
|
||||||
|
strokeWidth={0.1}
|
||||||
|
y2={1}
|
||||||
|
/>
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
{mergedGroups.flatMap((ds, idx, arr) =>
|
||||||
|
arr.slice(0, idx).map((val, i) => (
|
||||||
|
<g
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
key={i}
|
||||||
|
>
|
||||||
|
<mask
|
||||||
|
id={`svg-preview-backdrop-mask-${id}-${i}`}
|
||||||
|
maskUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="white"
|
||||||
|
d={val.join(' ')}
|
||||||
|
/>
|
||||||
|
</mask>
|
||||||
|
<path
|
||||||
|
d={ds.join(' ')}
|
||||||
|
stroke={`url(#backdrop-pattern-${id})`}
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
strokeOpacity={strokeOpacity}
|
||||||
|
mask={`url(#svg-preview-backdrop-mask-${id}-${i})`}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
)),
|
||||||
|
)}
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type Point = { x: number; y: number };
|
||||||
|
type Group = { id: string; points: Point[] };
|
||||||
|
|
||||||
|
// Euclidean distance
|
||||||
|
function distance(a: Point, b: Point): number {
|
||||||
|
return Math.hypot(a.x - b.x, a.y - b.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if two groups should be merged based on minimum distance
|
||||||
|
function shouldMerge(a: Group, b: Group, minDistance: number): boolean {
|
||||||
|
for (const pa of a.points) {
|
||||||
|
for (const pb of b.points) {
|
||||||
|
if (distance(pa, pb) <= minDistance) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge groups and return arrays of merged group IDs
|
||||||
|
function mergeGroups(groups: Group[], minDistance: number): string[][] {
|
||||||
|
const mergedGroups: Group[][] = groups.map((g) => [g]);
|
||||||
|
|
||||||
|
let changed = true;
|
||||||
|
while (changed) {
|
||||||
|
changed = false;
|
||||||
|
|
||||||
|
outer: for (let i = 0; i < mergedGroups.length; i++) {
|
||||||
|
for (let j = i + 1; j < mergedGroups.length; j++) {
|
||||||
|
// Check if any group in mergedGroups[i] should merge with any in mergedGroups[j]
|
||||||
|
if (
|
||||||
|
mergedGroups[i].some((ga) =>
|
||||||
|
mergedGroups[j].some((gb) => shouldMerge(ga, gb, minDistance)),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Merge group j into group i
|
||||||
|
mergedGroups[i] = [...mergedGroups[i], ...mergedGroups[j]];
|
||||||
|
mergedGroups.splice(j, 1);
|
||||||
|
changed = true;
|
||||||
|
break outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return only arrays of IDs
|
||||||
|
return mergedGroups.map((groupList) => groupList.map((g) => g.id));
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { PathProps, Path } from './types';
|
import { PathProps, Path } from './types';
|
||||||
import { getPaths, assert } from './utils';
|
import getPaths, { assert } from './utils';
|
||||||
|
import { GapViolationHighlight } from './GapViolationHighlight.tsx';
|
||||||
|
|
||||||
export const darkModeCss = `
|
export const darkModeCss = `
|
||||||
@media screen and (prefers-color-scheme: light) {
|
@media screen and (prefers-color-scheme: light) {
|
||||||
@@ -20,10 +21,16 @@ export const darkModeCss = `
|
|||||||
|
|
||||||
export const Grid = ({
|
export const Grid = ({
|
||||||
radius,
|
radius,
|
||||||
fill = '#fff',
|
fill,
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
subGridSize = 0,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
|
height: number;
|
||||||
|
width: number;
|
||||||
strokeWidth: number;
|
strokeWidth: number;
|
||||||
|
subGridSize?: number;
|
||||||
radius: number;
|
radius: number;
|
||||||
} & PathProps<'stroke', 'strokeWidth'>) => (
|
} & PathProps<'stroke', 'strokeWidth'>) => (
|
||||||
<g
|
<g
|
||||||
@@ -33,43 +40,53 @@ export const Grid = ({
|
|||||||
>
|
>
|
||||||
<rect
|
<rect
|
||||||
className="svg-preview-grid-rect"
|
className="svg-preview-grid-rect"
|
||||||
width={24 - props.strokeWidth}
|
width={width - props.strokeWidth}
|
||||||
height={24 - props.strokeWidth}
|
height={height - props.strokeWidth}
|
||||||
x={props.strokeWidth / 2}
|
x={props.strokeWidth / 2}
|
||||||
y={props.strokeWidth / 2}
|
y={props.strokeWidth / 2}
|
||||||
rx={radius}
|
rx={radius}
|
||||||
fill={fill}
|
fill={fill}
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
strokeDasharray={'0 0.1 ' + '0.1 0.15 '.repeat(11) + '0 0.15'}
|
strokeDasharray={
|
||||||
|
'0 0.1 ' + '0.1 0.15 '.repeat(subGridSize ? subGridSize * 4 - 1 : 95) + '0 0.15'
|
||||||
|
}
|
||||||
strokeWidth={0.1}
|
strokeWidth={0.1}
|
||||||
d={
|
d={
|
||||||
props.d ||
|
props.d ||
|
||||||
new Array(Math.floor(24 - 1))
|
[
|
||||||
.fill(null)
|
...new Array(Math.floor(width - 1))
|
||||||
.map((_, i) => i)
|
.fill(null)
|
||||||
.filter((i) => i % 3 !== 2)
|
.map((_, i) => i)
|
||||||
.flatMap((i) => [
|
.filter((i) => !subGridSize || i % subGridSize !== subGridSize - 1)
|
||||||
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
|
.flatMap((i) => [`M${i + 1} ${props.strokeWidth}v${height - props.strokeWidth * 2}`]),
|
||||||
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
|
...new Array(Math.floor(height - 1))
|
||||||
])
|
.fill(null)
|
||||||
.join('')
|
.map((_, i) => i)
|
||||||
}
|
.filter((i) => !subGridSize || i % subGridSize !== subGridSize - 1)
|
||||||
/>
|
.flatMap((i) => [`M${props.strokeWidth} ${i + 1}h${width - props.strokeWidth * 2}`]),
|
||||||
<path
|
].join('')
|
||||||
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}`,
|
|
||||||
])
|
|
||||||
.join('')
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
{!!subGridSize && (
|
||||||
|
<path
|
||||||
|
d={
|
||||||
|
props.d ||
|
||||||
|
[
|
||||||
|
...new Array(Math.floor(width - 1))
|
||||||
|
.fill(null)
|
||||||
|
.map((_, i) => i)
|
||||||
|
.filter((i) => i % subGridSize === subGridSize - 1)
|
||||||
|
.flatMap((i) => [`M${i + 1} ${props.strokeWidth}v${height - props.strokeWidth * 2}`]),
|
||||||
|
...new Array(Math.floor(height - 1))
|
||||||
|
.fill(null)
|
||||||
|
.map((_, i) => i)
|
||||||
|
.filter((i) => i % subGridSize === subGridSize - 1)
|
||||||
|
.flatMap((i) => [`M${props.strokeWidth} ${i + 1}h${width - props.strokeWidth * 2}`]),
|
||||||
|
].join('')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -99,6 +116,7 @@ const Shadow = ({
|
|||||||
>
|
>
|
||||||
{groupedPaths.map(([id, paths]) => (
|
{groupedPaths.map(([id, paths]) => (
|
||||||
<mask
|
<mask
|
||||||
|
key={`svg-preview-shadow-mask-${id}`}
|
||||||
id={`svg-preview-shadow-mask-${id}`}
|
id={`svg-preview-shadow-mask-${id}`}
|
||||||
maskUnits="userSpaceOnUse"
|
maskUnits="userSpaceOnUse"
|
||||||
strokeOpacity="1"
|
strokeOpacity="1"
|
||||||
@@ -108,8 +126,8 @@ const Shadow = ({
|
|||||||
<rect
|
<rect
|
||||||
x={0}
|
x={0}
|
||||||
y={0}
|
y={0}
|
||||||
width={24}
|
width="100%"
|
||||||
height={24}
|
height="100%"
|
||||||
fill="#fff"
|
fill="#fff"
|
||||||
stroke="none"
|
stroke="none"
|
||||||
rx={radius}
|
rx={radius}
|
||||||
@@ -152,30 +170,34 @@ const ColoredPath = ({
|
|||||||
colors,
|
colors,
|
||||||
paths,
|
paths,
|
||||||
...props
|
...props
|
||||||
}: { paths: Path[]; colors: string[] } & PathProps<never, 'd' | 'stroke'>) => (
|
}: { paths: Path[]; colors: string[] } & PathProps<never, 'd' | 'stroke'>) => {
|
||||||
<g
|
let idx = 0;
|
||||||
className="svg-preview-colored-path-group"
|
return (
|
||||||
{...props}
|
<g
|
||||||
>
|
className="svg-preview-colored-path-group"
|
||||||
{paths.map(({ d, c }, i) => (
|
{...props}
|
||||||
<path
|
>
|
||||||
key={i}
|
{paths.map(({ d, c }, i) => (
|
||||||
d={d}
|
<path
|
||||||
stroke={colors[(c.name === 'path' ? i : c.id) % colors.length]}
|
key={i}
|
||||||
/>
|
d={d}
|
||||||
))}
|
stroke={colors[(c.name === 'path' ? idx++ : c.id) % colors.length]}
|
||||||
</g>
|
/>
|
||||||
);
|
))}
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const ControlPath = ({
|
const ControlPath = ({
|
||||||
paths,
|
paths,
|
||||||
radius,
|
radius,
|
||||||
pointSize,
|
pointSize,
|
||||||
...props
|
...props
|
||||||
}: { pointSize: number; paths: Path[]; radius: number } & PathProps<
|
}: {
|
||||||
'stroke' | 'strokeWidth',
|
pointSize: number;
|
||||||
'd'
|
paths: Path[];
|
||||||
>) => {
|
radius: number;
|
||||||
|
} & PathProps<'stroke' | 'strokeWidth', 'd'>) => {
|
||||||
const controlPaths = paths.map((path, i) => {
|
const controlPaths = paths.map((path, i) => {
|
||||||
const element = paths.filter((p) => p.c.id === path.c.id);
|
const element = paths.filter((p) => p.c.id === path.c.id);
|
||||||
const lastElement = element.at(-1)?.next;
|
const lastElement = element.at(-1)?.next;
|
||||||
@@ -207,8 +229,8 @@ const ControlPath = ({
|
|||||||
<rect
|
<rect
|
||||||
x="0"
|
x="0"
|
||||||
y="0"
|
y="0"
|
||||||
width="24"
|
width="100%"
|
||||||
height="24"
|
height="100%"
|
||||||
fill="#fff"
|
fill="#fff"
|
||||||
stroke="none"
|
stroke="none"
|
||||||
rx={radius}
|
rx={radius}
|
||||||
@@ -243,7 +265,7 @@ const ControlPath = ({
|
|||||||
)
|
)
|
||||||
.join('')}
|
.join('')}
|
||||||
/>
|
/>
|
||||||
{controlPaths.map(({ d, prev, next, startMarker, endMarker }, i) => (
|
{controlPaths.map(({ prev, next, startMarker, endMarker }, i) => (
|
||||||
<React.Fragment key={i}>
|
<React.Fragment key={i}>
|
||||||
{startMarker && (
|
{startMarker && (
|
||||||
<circle
|
<circle
|
||||||
@@ -279,11 +301,37 @@ const Radii = ({
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{paths.map(
|
{paths.map(
|
||||||
({ c, prev, next, circle }, i) =>
|
({ circle, next, prev, c }, i) =>
|
||||||
circle && (
|
circle && (
|
||||||
<React.Fragment key={i}>
|
<React.Fragment key={i}>
|
||||||
{c.name !== 'circle' && (
|
{circle.tangentIntersection && c.name === 'path' && (
|
||||||
<path d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`} />
|
<>
|
||||||
|
<circle
|
||||||
|
cx={next.x * 2 - circle.tangentIntersection.x}
|
||||||
|
cy={next.y * 2 - circle.tangentIntersection.y}
|
||||||
|
r={0.25}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx={prev.x * 2 - circle.tangentIntersection.x}
|
||||||
|
cy={prev.y * 2 - circle.tangentIntersection.y}
|
||||||
|
r={0.25}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d={`M${next.x * 2 - circle.tangentIntersection.x} ${
|
||||||
|
next.y * 2 - circle.tangentIntersection.y
|
||||||
|
}L${circle.tangentIntersection.x} ${circle.tangentIntersection.y}L${prev.x * 2 - circle.tangentIntersection.x} ${
|
||||||
|
prev.y * 2 - circle.tangentIntersection.y
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx={circle.tangentIntersection.x}
|
||||||
|
cy={circle.tangentIntersection.y}
|
||||||
|
r={0.25}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{c.name === 'path' && (
|
||||||
|
<path d={`M${next.x} ${next.y}L${circle.x} ${circle.y}L${prev.x} ${prev.y}`} />
|
||||||
)}
|
)}
|
||||||
<circle
|
<circle
|
||||||
cy={circle.y}
|
cy={circle.y}
|
||||||
@@ -313,55 +361,60 @@ const Radii = ({
|
|||||||
const Handles = ({
|
const Handles = ({
|
||||||
paths,
|
paths,
|
||||||
...props
|
...props
|
||||||
}: { paths: Path[] } & PathProps<
|
}: { paths: Path[] } & PathProps<'strokeWidth' | 'stroke' | 'strokeOpacity', any>) => (
|
||||||
'strokeWidth' | 'stroke' | 'strokeDasharray' | 'strokeOpacity',
|
<g
|
||||||
any
|
className="svg-preview-handles-group"
|
||||||
>) => {
|
{...props}
|
||||||
return (
|
>
|
||||||
<g
|
{paths.map(({ c, prev, next, cp1, cp2 }, i) => (
|
||||||
className="svg-preview-handles-group"
|
<React.Fragment key={i}>
|
||||||
{...props}
|
{cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />}
|
||||||
>
|
{cp1 && (
|
||||||
{paths.map(({ c, prev, next, cp1, cp2 }) => (
|
<circle
|
||||||
<>
|
cy={cp1.y}
|
||||||
{cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />}
|
cx={cp1.x}
|
||||||
{cp1 && (
|
r={0.25}
|
||||||
<circle
|
/>
|
||||||
cy={cp1.y}
|
)}
|
||||||
cx={cp1.x}
|
{cp2 && <path d={`M${next.x} ${next.y} ${cp2.x} ${cp2.y}`} />}
|
||||||
r={0.25}
|
{cp2 && (
|
||||||
/>
|
<circle
|
||||||
)}
|
cy={cp2.y}
|
||||||
{cp2 && <path d={`M${next.x} ${next.y} ${cp2.x} ${cp2.y}`} />}
|
cx={cp2.x}
|
||||||
{cp2 && (
|
r={0.25}
|
||||||
<circle
|
/>
|
||||||
cy={cp2.y}
|
)}
|
||||||
cx={cp2.x}
|
</React.Fragment>
|
||||||
r={0.25}
|
))}
|
||||||
/>
|
</g>
|
||||||
)}
|
);
|
||||||
</>
|
|
||||||
))}
|
|
||||||
</g>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const SvgPreview = React.forwardRef<
|
const SvgPreview = React.forwardRef<
|
||||||
SVGSVGElement,
|
SVGSVGElement,
|
||||||
{
|
{
|
||||||
|
height?: number;
|
||||||
|
width?: number;
|
||||||
src: string | ReturnType<typeof getPaths>;
|
src: string | ReturnType<typeof getPaths>;
|
||||||
showGrid?: boolean;
|
showGrid?: boolean;
|
||||||
} & React.SVGProps<SVGSVGElement>
|
} & React.SVGProps<SVGSVGElement>
|
||||||
>(({ src, children, showGrid = false, ...props }, ref) => {
|
>(({ src, children, height = 24, width = 24, showGrid = false, ...props }, ref) => {
|
||||||
|
const subGridSize =
|
||||||
|
Math.max(height, width) % 3 === 0
|
||||||
|
? Math.max(height, width) > 24
|
||||||
|
? 12
|
||||||
|
: 3
|
||||||
|
: Math.max(height, width) % 5 === 0
|
||||||
|
? 5
|
||||||
|
: 0;
|
||||||
const paths = typeof src === 'string' ? getPaths(src) : src;
|
const paths = typeof src === 'string' ? getPaths(src) : src;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
ref={ref}
|
ref={ref}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width={24}
|
width={width}
|
||||||
height={24}
|
height={height}
|
||||||
viewBox="0 0 24 24"
|
viewBox={`0 0 ${width} ${height}`}
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
@@ -372,8 +425,12 @@ const SvgPreview = React.forwardRef<
|
|||||||
<style>{darkModeCss}</style>
|
<style>{darkModeCss}</style>
|
||||||
{showGrid && (
|
{showGrid && (
|
||||||
<Grid
|
<Grid
|
||||||
|
height={height}
|
||||||
|
width={width}
|
||||||
|
subGridSize={subGridSize}
|
||||||
strokeWidth={0.1}
|
strokeWidth={0.1}
|
||||||
stroke="#777"
|
stroke="#777"
|
||||||
|
mask="url(#svg-preview-bounding-box-mask)"
|
||||||
strokeOpacity={0.3}
|
strokeOpacity={0.3}
|
||||||
radius={1}
|
radius={1}
|
||||||
/>
|
/>
|
||||||
@@ -385,6 +442,12 @@ const SvgPreview = React.forwardRef<
|
|||||||
radius={1}
|
radius={1}
|
||||||
strokeOpacity={0.15}
|
strokeOpacity={0.15}
|
||||||
/>
|
/>
|
||||||
|
<GapViolationHighlight
|
||||||
|
paths={paths}
|
||||||
|
stroke="red"
|
||||||
|
strokeOpacity={0.75}
|
||||||
|
strokeWidth={4}
|
||||||
|
/>
|
||||||
<Handles
|
<Handles
|
||||||
paths={paths}
|
paths={paths}
|
||||||
strokeWidth={0.12}
|
strokeWidth={0.12}
|
||||||
@@ -394,18 +457,7 @@ const SvgPreview = React.forwardRef<
|
|||||||
<ColoredPath
|
<ColoredPath
|
||||||
paths={paths}
|
paths={paths}
|
||||||
colors={[
|
colors={[
|
||||||
'#1982c4',
|
'##dfdfd6',
|
||||||
'#4267AC',
|
|
||||||
'#6a4c93',
|
|
||||||
'#B55379',
|
|
||||||
'#FF595E',
|
|
||||||
'#FF7655',
|
|
||||||
'#ff924c',
|
|
||||||
'#FFAE43',
|
|
||||||
'#ffca3a',
|
|
||||||
'#C5CA30',
|
|
||||||
'#8ac926',
|
|
||||||
'#52A675',
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Radii
|
<Radii
|
||||||
@@ -433,4 +485,6 @@ const SvgPreview = React.forwardRef<
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
SvgPreview.displayName = 'SvgPreview';
|
||||||
|
|
||||||
export default SvgPreview;
|
export default SvgPreview;
|
||||||
|
|||||||
19
docs/.vitepress/lib/SvgPreview/path-to-points.ts
Normal file
19
docs/.vitepress/lib/SvgPreview/path-to-points.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import memoize from 'lodash/memoize';
|
||||||
|
import SVGPathCommander from 'svg-path-commander';
|
||||||
|
import { Path } from './types';
|
||||||
|
|
||||||
|
function pathToPoints({ d, prev, next }: Path, interval = 1) {
|
||||||
|
const commander = new SVGPathCommander(d);
|
||||||
|
const points = [];
|
||||||
|
try {
|
||||||
|
const totalLength = commander.getTotalLength();
|
||||||
|
points.push(prev);
|
||||||
|
for (let i = interval; i < totalLength - interval; i += interval) {
|
||||||
|
points.push(commander.getPointAtLength(i));
|
||||||
|
}
|
||||||
|
points.push(next);
|
||||||
|
} catch (err) {}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memoize(pathToPoints);
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import { INode, parseSync } from 'svgson';
|
import { INode, parseSync } from 'svgson';
|
||||||
|
// @ts-ignore
|
||||||
import toPath from 'element-to-path';
|
import toPath from 'element-to-path';
|
||||||
|
// @ts-ignore
|
||||||
import { SVGPathData, encodeSVGPath } from 'svg-pathdata';
|
import { SVGPathData, encodeSVGPath } from 'svg-pathdata';
|
||||||
import { Path, Point } from './types';
|
import { Path, Point } from './types';
|
||||||
|
import memoize from 'lodash/memoize';
|
||||||
|
|
||||||
function assertNever(x: never): never {
|
function assertNever(x: never): never {
|
||||||
throw new Error('Unknown type: ' + x['type']);
|
throw new Error('Unknown type: ' + x['type']);
|
||||||
@@ -44,17 +47,21 @@ const extractNodes = (node: INode): INode[] => {
|
|||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getNodes = (src: string) =>
|
export const getNodes = memoize((src: string) =>
|
||||||
extractNodes(parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`));
|
extractNodes(parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`)),
|
||||||
|
);
|
||||||
|
|
||||||
export const getCommands = (src: string) =>
|
export const getCommands = (src: string) =>
|
||||||
getNodes(src)
|
getNodes(src)
|
||||||
.map(convertToPathNode)
|
.map(convertToPathNode)
|
||||||
.flatMap(({ d, name }, idx) =>
|
.flatMap(({ d, name }, idx) =>
|
||||||
new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })),
|
new SVGPathData(d)
|
||||||
|
.toAbs()
|
||||||
|
// @ts-ignore
|
||||||
|
.commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getPaths = (src: string) => {
|
const getPaths = (src: string) => {
|
||||||
const commands = getCommands(src.includes('<svg') ? src : `<svg>${src}</svg>`);
|
const commands = getCommands(src.includes('<svg') ? src : `<svg>${src}</svg>`);
|
||||||
const paths: Path[] = [];
|
const paths: Path[] = [];
|
||||||
let prev: Point | undefined = undefined;
|
let prev: Point | undefined = undefined;
|
||||||
@@ -237,6 +244,7 @@ export const getPaths = (src: string) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
// @ts-ignore
|
||||||
assertNever(c);
|
assertNever(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,7 +252,7 @@ export const getPaths = (src: string) => {
|
|||||||
return paths;
|
return paths;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const arcEllipseCenter = (
|
const arcEllipseCenter = (
|
||||||
x1: number,
|
x1: number,
|
||||||
y1: number,
|
y1: number,
|
||||||
rx: number,
|
rx: number,
|
||||||
@@ -296,5 +304,52 @@ export const arcEllipseCenter = (
|
|||||||
M2[1][0] * Cp[0] + M2[1][1] * Cp[1] + V3[1],
|
M2[1][0] * Cp[0] + M2[1][1] * Cp[1] + V3[1],
|
||||||
];
|
];
|
||||||
|
|
||||||
return { x: C[0], y: C[1] };
|
return {
|
||||||
|
x: C[0],
|
||||||
|
y: C[1],
|
||||||
|
tangentIntersection: intersectTangents(
|
||||||
|
{ x: x1, y: y1 },
|
||||||
|
{ x: x2, y: y2 },
|
||||||
|
{ x: C[0], y: C[1] },
|
||||||
|
),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getTangentDirection(p: Point, center: Point): Point {
|
||||||
|
// Tangent is perpendicular to the radius vector (rotate radius 90°)
|
||||||
|
const dx = p.x - center.x;
|
||||||
|
const dy = p.y - center.y;
|
||||||
|
return { x: -dy, y: dx }; // 90° rotation
|
||||||
|
}
|
||||||
|
|
||||||
|
function intersectTangents(start: Point, end: Point, center: Point): Point | null {
|
||||||
|
const t1 = getTangentDirection(start, center);
|
||||||
|
const t2 = getTangentDirection(end, center);
|
||||||
|
|
||||||
|
// Solve: start + λ * t1 = end + μ * t2
|
||||||
|
const A = [
|
||||||
|
[t1.x, -t2.x],
|
||||||
|
[t1.y, -t2.y],
|
||||||
|
];
|
||||||
|
const b = [end.x - start.x, end.y - start.y];
|
||||||
|
|
||||||
|
// Compute determinant
|
||||||
|
const det = A[0][0] * A[1][1] - A[0][1] * A[1][0];
|
||||||
|
|
||||||
|
if (Math.abs(det) < 1e-10) {
|
||||||
|
// Lines are parallel, no intersection
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const invDet = 1 / det;
|
||||||
|
|
||||||
|
const lambda = (b[0] * A[1][1] - b[1] * A[0][1]) * invDet;
|
||||||
|
|
||||||
|
// Intersection point = start + lambda * t1
|
||||||
|
return {
|
||||||
|
x: start.x + lambda * t1.x,
|
||||||
|
y: start.y + lambda * t1.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memoize(getPaths);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { bundledLanguages, type ThemeRegistration } from 'shikiji';
|
import { bundledLanguages, createHighlighter, type ThemeRegistration } from 'shiki';
|
||||||
import { getHighlighter } from 'shikiji';
|
|
||||||
|
|
||||||
type CodeExampleType = {
|
type CodeExampleType = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -38,7 +37,7 @@ export default App;
|
|||||||
language: 'vue',
|
language: 'vue',
|
||||||
title: 'Vue',
|
title: 'Vue',
|
||||||
code: `<script setup>
|
code: `<script setup>
|
||||||
import { $PascalCase } from 'lucide-vue-next';
|
import { $PascalCase } from '@lucide/vue';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -102,13 +101,8 @@ import { LucideAngularModule, $PascalCase } from 'lucide-angular';
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
language: 'html',
|
language: 'html',
|
||||||
title: 'Icon Font',
|
title: 'Icon font',
|
||||||
code: `<style>
|
code: `<div class="icon-$Name"></div>`,
|
||||||
@import ('~lucide-static/font/Lucide.css');
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="icon-$Name"></div>
|
|
||||||
`,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
@@ -118,7 +112,7 @@ export type ThemeOptions =
|
|||||||
| { light: ThemeRegistration; dark: ThemeRegistration };
|
| { light: ThemeRegistration; dark: ThemeRegistration };
|
||||||
|
|
||||||
const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
||||||
const highlighter = await getHighlighter({
|
const highlighter = await createHighlighter({
|
||||||
themes: ['github-light', 'github-dark'],
|
themes: ['github-light', 'github-dark'],
|
||||||
langs: Object.keys(bundledLanguages),
|
langs: Object.keys(bundledLanguages),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { bundledLanguages, type ThemeRegistration } from 'shikiji';
|
import { bundledLanguages, type ThemeRegistration } from 'shiki';
|
||||||
import { getHighlighter } from 'shikiji';
|
import { createHighlighter } from 'shiki';
|
||||||
|
|
||||||
type CodeExampleType = {
|
type CodeExampleType = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -119,7 +119,7 @@ export type ThemeOptions =
|
|||||||
| { light: ThemeRegistration; dark: ThemeRegistration };
|
| { light: ThemeRegistration; dark: ThemeRegistration };
|
||||||
|
|
||||||
const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
||||||
const highlighter = await getHighlighter({
|
const highlighter = await createHighlighter({
|
||||||
themes: ['github-light', 'github-dark'],
|
themes: ['github-light', 'github-dark'],
|
||||||
langs: Object.keys(bundledLanguages),
|
langs: Object.keys(bundledLanguages),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { bundledLanguages, type ThemeRegistration } from 'shikiji';
|
import { bundledLanguages, type ThemeRegistration } from 'shiki';
|
||||||
import { getHighlighter } from 'shikiji';
|
import { createHighlighter } from 'shiki';
|
||||||
|
|
||||||
export type ThemeOptions =
|
export type ThemeOptions =
|
||||||
| ThemeRegistration
|
| ThemeRegistration
|
||||||
| { light: ThemeRegistration; dark: ThemeRegistration };
|
| { light: ThemeRegistration; dark: ThemeRegistration };
|
||||||
|
|
||||||
const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
||||||
const highlighter = await getHighlighter({
|
const highlighter = await createHighlighter({
|
||||||
themes: ['github-light', 'github-dark'],
|
themes: ['github-light', 'github-dark'],
|
||||||
langs: Object.keys(bundledLanguages),
|
langs: Object.keys(bundledLanguages),
|
||||||
});
|
});
|
||||||
|
|||||||
110
docs/.vitepress/plugins/snackPlayer.ts
Normal file
110
docs/.vitepress/plugins/snackPlayer.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* SnackPlayer markdown-it plugin
|
||||||
|
*
|
||||||
|
* Converts fenced code blocks like:
|
||||||
|
*
|
||||||
|
* ```SnackPlayer name=My%20Example&description=Nice%20demo&ext=tsx
|
||||||
|
* // code here
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* into:
|
||||||
|
*
|
||||||
|
* <div
|
||||||
|
* class="snack-player"
|
||||||
|
* data-snack-name="My Example"
|
||||||
|
* ...
|
||||||
|
* />
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type MarkdownIt from 'markdown-it';
|
||||||
|
|
||||||
|
type SnackParams = Record<string, string>;
|
||||||
|
|
||||||
|
function parseParams(paramString = ''): SnackParams {
|
||||||
|
const params = Object.fromEntries(
|
||||||
|
new URLSearchParams(paramString),
|
||||||
|
) as SnackParams;
|
||||||
|
|
||||||
|
if (!params.platform) {
|
||||||
|
params.platform = 'web';
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function snackPlayerPlugin(md: MarkdownIt) {
|
||||||
|
const escapeHtml = md.utils.escapeHtml;
|
||||||
|
const defaultFence =
|
||||||
|
md.renderer.rules.fence ||
|
||||||
|
((tokens, idx, options, env, self) =>
|
||||||
|
self.renderToken(tokens, idx, options));
|
||||||
|
|
||||||
|
md.renderer.rules.fence = (tokens, idx, options, env, self) => {
|
||||||
|
const token = tokens[idx];
|
||||||
|
const info = (token.info || '').trim();
|
||||||
|
|
||||||
|
if (!info) {
|
||||||
|
return defaultFence(tokens, idx, options, env, self);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [lang, ...rest] = info.split(/\s+/);
|
||||||
|
if (lang !== 'SnackPlayer') {
|
||||||
|
return defaultFence(tokens, idx, options, env, self);
|
||||||
|
}
|
||||||
|
|
||||||
|
const paramString = rest.join(' ');
|
||||||
|
const params = parseParams(paramString);
|
||||||
|
|
||||||
|
// Gather necessary params
|
||||||
|
const name = params.name
|
||||||
|
? decodeURIComponent(params.name)
|
||||||
|
: 'Example';
|
||||||
|
const description = params.description
|
||||||
|
? decodeURIComponent(params.description)
|
||||||
|
: 'Example usage';
|
||||||
|
const ext = params.ext ? decodeURIComponent(params.ext) : 'tsx';
|
||||||
|
const filename = `App.${ext}`;
|
||||||
|
|
||||||
|
const files = encodeURIComponent(
|
||||||
|
JSON.stringify({
|
||||||
|
[filename]: {
|
||||||
|
type: 'CODE',
|
||||||
|
contents: token.content,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const dependencies =
|
||||||
|
'react-native-safe-area-context' +
|
||||||
|
(params.dependencies ? `,${params.dependencies}` : '');
|
||||||
|
const platform = params.platform ?? 'web';
|
||||||
|
const supportedPlatforms =
|
||||||
|
params.supportedPlatforms ?? 'ios,android,web';
|
||||||
|
const theme = params.theme ?? 'light';
|
||||||
|
const preview = params.preview ?? 'true';
|
||||||
|
const loading = params.loading ?? 'lazy';
|
||||||
|
const deviceAppearance = params.deviceAppearance ?? 'dark';
|
||||||
|
|
||||||
|
// Build the HTML output (escaping where appropriate)
|
||||||
|
return (
|
||||||
|
`<SnackPlayer` +
|
||||||
|
` class="snack-player"` +
|
||||||
|
` data-snack-name="${escapeHtml(name)}"` +
|
||||||
|
` data-snack-description="${escapeHtml(description)}"` +
|
||||||
|
` data-snack-files="${files}"` +
|
||||||
|
` data-snack-dependencies="${escapeHtml(dependencies)}"` +
|
||||||
|
` data-snack-platform="${escapeHtml(platform)}"` +
|
||||||
|
` data-snack-supported-platforms="${escapeHtml(
|
||||||
|
supportedPlatforms,
|
||||||
|
)}"` +
|
||||||
|
// ` data-snack-theme="${escapeHtml(theme)}"` +
|
||||||
|
` data-snack-preview="${escapeHtml(preview)}"` +
|
||||||
|
` data-snack-loading="${escapeHtml(loading)}"` +
|
||||||
|
` data-snack-device-appearance="${escapeHtml(
|
||||||
|
deviceAppearance,
|
||||||
|
)}"` +
|
||||||
|
` data-snack-device-frame="false"` +
|
||||||
|
`></SnackPlayer>`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
import { DefaultTheme, UserConfig } from 'vitepress';
|
|
||||||
|
|
||||||
const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
|
|
||||||
guide: [
|
|
||||||
{
|
|
||||||
text: 'Introduction',
|
|
||||||
items: [
|
|
||||||
{ text: 'What is lucide?', link: '/guide/' },
|
|
||||||
{ text: 'Installation', link: '/guide/installation' },
|
|
||||||
{ text: 'Comparison', link: '/guide/comparison' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Basics',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
text: 'Color',
|
|
||||||
link: '/guide/basics/color',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Sizing',
|
|
||||||
link: '/guide/basics/sizing',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Stroke width',
|
|
||||||
link: '/guide/basics/stroke-width',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
// TODO: Add this section
|
|
||||||
{
|
|
||||||
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: 'Aliased Names',
|
|
||||||
link: '/guide/advanced/aliased-names',
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// text: 'Combining icons',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// text: 'Dynamic imports'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// text: 'Auto importing'
|
|
||||||
// },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Packages',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
text: 'Lucide',
|
|
||||||
link: '/guide/packages/lucide',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Lucide React',
|
|
||||||
link: '/guide/packages/lucide-react',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Lucide Vue',
|
|
||||||
link: '/guide/packages/lucide-vue-next',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Lucide Svelte',
|
|
||||||
link: '/guide/packages/lucide-svelte',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Lucide Solid',
|
|
||||||
link: '/guide/packages/lucide-solid',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Lucide React Native',
|
|
||||||
link: '/guide/packages/lucide-react-native',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Lucide Angular',
|
|
||||||
link: '/guide/packages/lucide-angular',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Lucide Preact',
|
|
||||||
link: '/guide/packages/lucide-preact',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Lucide Static',
|
|
||||||
link: '/guide/packages/lucide-static',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Contributing',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
text: 'Icon Design Principles',
|
|
||||||
link: '/guide/design/icon-design-guide',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Designing in Illustrator',
|
|
||||||
link: '/guide/design/illustrator-guide',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Designing in InkScape',
|
|
||||||
link: '/guide/design/inkscape-guide',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Designing in Figma',
|
|
||||||
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: '/' }],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default sidebar;
|
|
||||||
66
docs/.vitepress/sidebar/index.ts
Normal file
66
docs/.vitepress/sidebar/index.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { DefaultTheme, UserConfig } from 'vitepress';
|
||||||
|
import { reactSidebar } from './react';
|
||||||
|
import { vueSidebar } from './vue';
|
||||||
|
import { svelteSidebar } from './svelte';
|
||||||
|
import { lucideSidebar } from './lucide';
|
||||||
|
import { solidSidebar } from './solid';
|
||||||
|
import { preactSidebar } from './preact';
|
||||||
|
import { reactNativeSidebar } from './react-native';
|
||||||
|
|
||||||
|
type Sidebar = UserConfig<DefaultTheme.Config>['themeConfig']['sidebar']
|
||||||
|
|
||||||
|
export const guideSidebarTop: DefaultTheme.SidebarItem[] = [
|
||||||
|
{
|
||||||
|
text: 'Introduction',
|
||||||
|
items: [
|
||||||
|
{ text: 'What is lucide?', link: '/guide/' },
|
||||||
|
{ text: 'Installation', link: '/guide/installation' },
|
||||||
|
{ text: 'Comparison', link: '/guide/comparison' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const sidebar: Sidebar = {
|
||||||
|
'/guide': [{ text: '', link: '/' }],
|
||||||
|
'/guide/lucide': lucideSidebar,
|
||||||
|
'/guide/react': reactSidebar,
|
||||||
|
'/guide/vue': vueSidebar,
|
||||||
|
'/guide/svelte': svelteSidebar,
|
||||||
|
'/guide/solid': solidSidebar,
|
||||||
|
'/guide/preact': preactSidebar,
|
||||||
|
'/guide/react-native/': reactNativeSidebar,
|
||||||
|
'/resources': [
|
||||||
|
{
|
||||||
|
text: "Community",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Designing icons',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Icon Design Principles',
|
||||||
|
link: '/guide/design/icon-design-guide',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Designing in Illustrator',
|
||||||
|
link: '/guide/design/illustrator-guide',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Designing in InkScape',
|
||||||
|
link: '/guide/design/inkscape-guide',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Designing in Figma',
|
||||||
|
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: '/' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default sidebar;
|
||||||
68
docs/.vitepress/sidebar/lucide.ts
Normal file
68
docs/.vitepress/sidebar/lucide.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { DefaultTheme } from "vitepress";
|
||||||
|
|
||||||
|
export const lucideSidebar = [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Overview',
|
||||||
|
link: '/guide/lucide/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Getting started',
|
||||||
|
link: '/guide/lucide/getting-started',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Basics',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Color',
|
||||||
|
desc: 'Adjust the color of your icons',
|
||||||
|
link: '/guide/lucide/basics/color',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Sizing',
|
||||||
|
desc: 'Adjust the size of your icons',
|
||||||
|
link: '/guide/lucide/basics/sizing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Stroke width',
|
||||||
|
desc: 'Adjust the stroke width of your icons',
|
||||||
|
link: '/guide/lucide/basics/stroke-width',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// TODO: Add this section
|
||||||
|
{
|
||||||
|
text: 'Advanced',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Typescript',
|
||||||
|
link: '/guide/lucide/advanced/typescript',
|
||||||
|
desc: 'All exported types and how to use them',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'With shadow DOM',
|
||||||
|
link: '/guide/lucide/advanced/shadow-dom',
|
||||||
|
desc: 'All exported types and how to use them',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Accessibility',
|
||||||
|
link: '/guide/lucide/advanced/accessibility',
|
||||||
|
desc: 'Making your icons accessible',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'With lucide lab',
|
||||||
|
link: '/guide/lucide/advanced/with-lucide-lab',
|
||||||
|
desc: 'Using lucide-lab with lucide',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Filled icons',
|
||||||
|
link: '/guide/lucide/advanced/filled-icons',
|
||||||
|
desc: 'Using filled icons in lucide',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];
|
||||||
|
|
||||||
97
docs/.vitepress/sidebar/preact.ts
Normal file
97
docs/.vitepress/sidebar/preact.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { DefaultTheme } from "vitepress";
|
||||||
|
|
||||||
|
export const preactSidebar = [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Overview',
|
||||||
|
link: '/guide/preact/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Getting started',
|
||||||
|
link: '/guide/preact/getting-started',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Basics',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Color',
|
||||||
|
desc: 'Adjust the color of your icons',
|
||||||
|
link: '/guide/preact/basics/color',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Sizing',
|
||||||
|
desc: 'Adjust the size of your icons',
|
||||||
|
link: '/guide/preact/basics/sizing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Stroke width',
|
||||||
|
desc: 'Adjust the stroke width of your icons',
|
||||||
|
link: '/guide/preact/basics/stroke-width',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Advanced',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Typescript',
|
||||||
|
link: '/guide/preact/advanced/typescript',
|
||||||
|
desc: 'All exported types and how to use them',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Accessibility',
|
||||||
|
link: '/guide/preact/advanced/accessibility',
|
||||||
|
desc: 'Making your icons accessible',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Global styling',
|
||||||
|
link: '/guide/preact/advanced/global-styling',
|
||||||
|
desc: 'Apply global styles to all icons',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'With lucide lab',
|
||||||
|
link: '/guide/preact/advanced/with-lucide-lab',
|
||||||
|
desc: 'Using lucide-lab with lucide-preact',
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// text: 'Animations',
|
||||||
|
// link: '/guide/preact/advanced/animations',
|
||||||
|
// desc: 'Add animations to your icons',
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
text: 'Filled icons',
|
||||||
|
link: '/guide/preact/advanced/filled-icons',
|
||||||
|
desc: 'Using filled icons in lucide-preact',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Aliased Names',
|
||||||
|
link: '/guide/preact/advanced/aliased-names',
|
||||||
|
desc: 'Using aliased icon names',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Combining icons',
|
||||||
|
link: '/guide/preact/advanced/combining-icons',
|
||||||
|
desc: 'Combine multiple icons into one',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Resources',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Accessibility in depth',
|
||||||
|
link: '/guide/accessibility',
|
||||||
|
desc: 'Accessibility best practices',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'VSCode',
|
||||||
|
link: '/guide/vscode',
|
||||||
|
desc: 'VSCode and Lucide',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];
|
||||||
|
|
||||||
82
docs/.vitepress/sidebar/react-native.ts
Normal file
82
docs/.vitepress/sidebar/react-native.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { DefaultTheme } from "vitepress";
|
||||||
|
|
||||||
|
export const reactNativeSidebar = [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Overview',
|
||||||
|
link: '/guide/react-native/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Getting started',
|
||||||
|
link: '/guide/react-native/getting-started',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Basics',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Color',
|
||||||
|
desc: 'Adjust the color of your icons',
|
||||||
|
link: '/guide/react-native/basics/color',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Sizing',
|
||||||
|
desc: 'Adjust the size of your icons',
|
||||||
|
link: '/guide/react-native/basics/sizing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Stroke width',
|
||||||
|
desc: 'Adjust the stroke width of your icons',
|
||||||
|
link: '/guide/react-native/basics/stroke-width',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Advanced',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Typescript',
|
||||||
|
link: '/guide/react-native/advanced/typescript',
|
||||||
|
desc: 'All exported types and how to use them',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Global styling',
|
||||||
|
link: '/guide/react-native/advanced/global-styling',
|
||||||
|
desc: 'Apply global styles to all icons',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'With lucide lab',
|
||||||
|
link: '/guide/react-native/advanced/with-lucide-lab',
|
||||||
|
desc: 'Using lucide-lab with lucide-react-native',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Filled icons',
|
||||||
|
link: '/guide/react-native/advanced/filled-icons',
|
||||||
|
desc: 'Using filled icons in lucide-react-native',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Aliased Names',
|
||||||
|
link: '/guide/react-native/advanced/aliased-names',
|
||||||
|
desc: 'Using aliased icon names',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Combining icons',
|
||||||
|
link: '/guide/react-native/advanced/combining-icons',
|
||||||
|
desc: 'Combine multiple icons into one',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Resources',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'VSCode',
|
||||||
|
link: '/guide/vscode',
|
||||||
|
desc: 'VSCode and Lucide',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];
|
||||||
|
|
||||||
103
docs/.vitepress/sidebar/react.ts
Normal file
103
docs/.vitepress/sidebar/react.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { DefaultTheme } from "vitepress";
|
||||||
|
|
||||||
|
export const reactSidebar = [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Overview',
|
||||||
|
link: '/guide/react/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Getting started',
|
||||||
|
link: '/guide/react/getting-started',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Basics',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Color',
|
||||||
|
desc: 'Adjust the color of your icons',
|
||||||
|
link: '/guide/react/basics/color',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Sizing',
|
||||||
|
desc: 'Adjust the size of your icons',
|
||||||
|
link: '/guide/react/basics/sizing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Stroke width',
|
||||||
|
desc: 'Adjust the stroke width of your icons',
|
||||||
|
link: '/guide/react/basics/stroke-width',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Advanced',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Typescript',
|
||||||
|
link: '/guide/react/advanced/typescript',
|
||||||
|
desc: 'All exported types and how to use them',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Accessibility',
|
||||||
|
link: '/guide/react/advanced/accessibility',
|
||||||
|
desc: 'Making your icons accessible',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Global styling',
|
||||||
|
link: '/guide/react/advanced/global-styling',
|
||||||
|
desc: 'Apply global styles to all icons',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'With lucide lab',
|
||||||
|
link: '/guide/react/advanced/with-lucide-lab',
|
||||||
|
desc: 'Using lucide-lab with lucide-react',
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// text: 'Animations',
|
||||||
|
// link: '/guide/react/advanced/animations',
|
||||||
|
// desc: 'Add animations to your icons',
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
text: 'Filled icons',
|
||||||
|
link: '/guide/react/advanced/filled-icons',
|
||||||
|
desc: 'Using filled icons in lucide-react',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Aliased Names',
|
||||||
|
link: '/guide/react/advanced/aliased-names',
|
||||||
|
desc: 'Using aliased icon names',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
text: 'Combining icons',
|
||||||
|
link: '/guide/react/advanced/combining-icons',
|
||||||
|
desc: 'Combine multiple icons into one',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Dynamic icon component',
|
||||||
|
link: '/guide/react/advanced/dynamic-icon-component.md',
|
||||||
|
desc: 'Dynamically import icons as needed',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Resources',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Accessibility in depth',
|
||||||
|
link: '/guide/accessibility',
|
||||||
|
desc: 'Accessibility best practices',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'VSCode',
|
||||||
|
link: '/guide/vscode',
|
||||||
|
desc: 'VSCode and Lucide',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];
|
||||||
|
|
||||||
98
docs/.vitepress/sidebar/solid.ts
Normal file
98
docs/.vitepress/sidebar/solid.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import { DefaultTheme } from "vitepress";
|
||||||
|
|
||||||
|
export const solidSidebar = [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Overview',
|
||||||
|
link: '/guide/solid/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Getting started',
|
||||||
|
link: '/guide/solid/getting-started',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Basics',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Color',
|
||||||
|
desc: 'Adjust the color of your icons',
|
||||||
|
link: '/guide/solid/basics/color',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Sizing',
|
||||||
|
desc: 'Adjust the size of your icons',
|
||||||
|
link: '/guide/solid/basics/sizing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Stroke width',
|
||||||
|
desc: 'Adjust the stroke width of your icons',
|
||||||
|
link: '/guide/solid/basics/stroke-width',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Advanced',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Typescript',
|
||||||
|
link: '/guide/solid/advanced/typescript',
|
||||||
|
desc: 'All exported types and how to use them',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Accessibility',
|
||||||
|
link: '/guide/solid/advanced/accessibility',
|
||||||
|
desc: 'Making your icons accessible',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Global styling',
|
||||||
|
link: '/guide/solid/advanced/global-styling',
|
||||||
|
desc: 'Apply global styles to all icons',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'With lucide lab',
|
||||||
|
link: '/guide/solid/advanced/with-lucide-lab',
|
||||||
|
desc: 'Using lucide-lab with lucide-solid',
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// text: 'Animations',
|
||||||
|
// link: '/guide/solid/advanced/animations',
|
||||||
|
// desc: 'Add animations to your icons',
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
text: 'Filled icons',
|
||||||
|
link: '/guide/solid/advanced/filled-icons',
|
||||||
|
desc: 'Using filled icons in lucide-solid',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Aliased Names',
|
||||||
|
link: '/guide/solid/advanced/aliased-names',
|
||||||
|
desc: 'Using aliased icon names',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
text: 'Combining icons',
|
||||||
|
link: '/guide/solid/advanced/combining-icons',
|
||||||
|
desc: 'Combine multiple icons into one',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Resources',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Accessibility in depth',
|
||||||
|
link: '/guide/accessibility',
|
||||||
|
desc: 'Accessibility best practices',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'VSCode',
|
||||||
|
link: '/guide/vscode',
|
||||||
|
desc: 'VSCode and Lucide',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];
|
||||||
|
|
||||||
87
docs/.vitepress/sidebar/svelte.ts
Normal file
87
docs/.vitepress/sidebar/svelte.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { DefaultTheme } from "vitepress";
|
||||||
|
|
||||||
|
export const svelteSidebar = [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Overview',
|
||||||
|
link: '/guide/svelte/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Getting started',
|
||||||
|
link: '/guide/svelte/getting-started',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Basics',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Color',
|
||||||
|
desc: 'Adjust the color of your icons',
|
||||||
|
link: '/guide/svelte/basics/color',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Sizing',
|
||||||
|
desc: 'Adjust the size of your icons',
|
||||||
|
link: '/guide/svelte/basics/sizing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Stroke width',
|
||||||
|
desc: 'Adjust the stroke width of your icons',
|
||||||
|
link: '/guide/svelte/basics/stroke-width',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Advanced',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Typescript',
|
||||||
|
link: '/guide/svelte/advanced/typescript',
|
||||||
|
desc: 'All exported types and how to use them',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Accessibility',
|
||||||
|
link: '/guide/svelte/advanced/accessibility',
|
||||||
|
desc: 'Making your icons accessible',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Global styling',
|
||||||
|
link: '/guide/svelte/advanced/global-styling',
|
||||||
|
desc: 'Apply global styles to all icons',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'With lucide lab',
|
||||||
|
link: '/guide/svelte/advanced/with-lucide-lab',
|
||||||
|
desc: 'Using lucide-lab with @lucide/svelte',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Filled icons',
|
||||||
|
link: '/guide/svelte/advanced/filled-icons',
|
||||||
|
desc: 'Using filled icons in @lucide/svelte',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Combining icons',
|
||||||
|
link: '/guide/svelte/advanced/combining-icons',
|
||||||
|
desc: 'Combine multiple icons into one',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Resources',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Accessibility in depth',
|
||||||
|
link: '/guide/accessibility',
|
||||||
|
desc: 'Accessibility best practices',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'VSCode',
|
||||||
|
link: '/guide/vscode',
|
||||||
|
desc: 'VSCode and Lucide',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];
|
||||||
|
|
||||||
92
docs/.vitepress/sidebar/vue.ts
Normal file
92
docs/.vitepress/sidebar/vue.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { DefaultTheme } from "vitepress";
|
||||||
|
|
||||||
|
export const vueSidebar = [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Overview',
|
||||||
|
link: '/guide/vue/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Getting started',
|
||||||
|
link: '/guide/vue/getting-started',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Basics',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Color',
|
||||||
|
desc: 'Adjust the color of your icons',
|
||||||
|
link: '/guide/vue/basics/color',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Sizing',
|
||||||
|
desc: 'Adjust the size of your icons',
|
||||||
|
link: '/guide/vue/basics/sizing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Stroke width',
|
||||||
|
desc: 'Adjust the stroke width of your icons',
|
||||||
|
link: '/guide/vue/basics/stroke-width',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Advanced',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Typescript',
|
||||||
|
link: '/guide/vue/advanced/typescript',
|
||||||
|
desc: 'All exported types and how to use them',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Accessibility',
|
||||||
|
link: '/guide/vue/advanced/accessibility',
|
||||||
|
desc: 'Making your icons accessible',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Global styling',
|
||||||
|
link: '/guide/vue/advanced/global-styling',
|
||||||
|
desc: 'Apply global styles to all icons',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'With lucide lab',
|
||||||
|
link: '/guide/vue/advanced/with-lucide-lab',
|
||||||
|
desc: 'Using lucide-lab with @lucide/vue',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Filled icons',
|
||||||
|
link: '/guide/vue/advanced/filled-icons',
|
||||||
|
desc: 'Using filled icons in @lucide/vue',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Aliased Names',
|
||||||
|
link: '/guide/vue/advanced/aliased-names',
|
||||||
|
desc: 'Using aliased icon names',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
text: 'Combining icons',
|
||||||
|
link: '/guide/vue/advanced/combining-icons',
|
||||||
|
desc: 'Combine multiple icons into one',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Resources',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Accessibility in depth',
|
||||||
|
link: '/guide/accessibility',
|
||||||
|
desc: 'Accessibility best practices',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'VSCode',
|
||||||
|
link: '/guide/vscode',
|
||||||
|
desc: 'VSCode and Lucide',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];
|
||||||
@@ -3,12 +3,20 @@ import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
|||||||
import { search } from '../../../data/iconNodes'
|
import { search } from '../../../data/iconNodes'
|
||||||
|
|
||||||
const SearchIcon = createLucideIcon('search', search)
|
const SearchIcon = createLucideIcon('search', search)
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
shortcut: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button class="fake-input">
|
<button class="fake-input">
|
||||||
<component :is="SearchIcon" class="search-icon"/>
|
<component :is="SearchIcon" class="search-icon"/>
|
||||||
<slot/>
|
<slot/>
|
||||||
|
<kbd v-if="shortcut" class="shortcut">{{ shortcut }}</kbd>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -33,4 +41,23 @@ const SearchIcon = createLucideIcon('search', search)
|
|||||||
border-color: var(--vp-c-brand);
|
border-color: var(--vp-c-brand);
|
||||||
background: var(--vp-c-bg-alt);
|
background: var(--vp-c-bg-alt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shortcut {
|
||||||
|
margin-left: auto;
|
||||||
|
padding: 2px 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--vp-c-text-3);
|
||||||
|
background: var(--vp-c-default-soft);
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: none) {
|
||||||
|
.shortcut {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -6,20 +6,35 @@ export default {
|
|||||||
export interface InputProps {
|
export interface InputProps {
|
||||||
type: string
|
type: string
|
||||||
modelValue: string
|
modelValue: string
|
||||||
|
shortcut?: string
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted, nextTick, watch } from 'vue'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<InputProps>(), {
|
const props = withDefaults(defineProps<InputProps>(), {
|
||||||
type: 'text'
|
type: 'text'
|
||||||
})
|
})
|
||||||
|
|
||||||
const input = ref()
|
const input = ref()
|
||||||
|
const wrapperEl = ref()
|
||||||
|
const shortcutEl = ref()
|
||||||
|
|
||||||
defineEmits(['change', 'input', 'update:modelValue'])
|
defineEmits(['change', 'input', 'update:modelValue'])
|
||||||
|
|
||||||
|
const updateShortcutSpacing = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
if (shortcutEl.value && wrapperEl.value) {
|
||||||
|
const shortcutWidth = shortcutEl.value.offsetWidth
|
||||||
|
wrapperEl.value.style.setProperty('--shortcut-width', `${shortcutWidth}px`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(updateShortcutSpacing)
|
||||||
|
watch(() => props.shortcut, updateShortcutSpacing)
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
focus: () => {
|
focus: () => {
|
||||||
input.value.focus()
|
input.value.focus()
|
||||||
@@ -28,17 +43,18 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper" ref="wrapperEl">
|
||||||
<slot name="icon" class="icon" />
|
<slot name="icon" class="icon" />
|
||||||
<input
|
<input
|
||||||
:type="type"
|
:type="type"
|
||||||
class="input"
|
class="input"
|
||||||
:class="{'has-icon': $slots.icon}"
|
:class="{'has-icon': $slots.icon, 'has-shortcut': shortcut}"
|
||||||
ref="input"
|
ref="input"
|
||||||
:value="modelValue"
|
:value="modelValue"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
@input="$emit('update:modelValue', $event.target.value)"
|
@input="$emit('update:modelValue', $event.target.value)"
|
||||||
/>
|
/>
|
||||||
|
<kbd v-if="shortcut" class="shortcut" ref="shortcutEl">{{ shortcut }}</kbd>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -57,6 +73,10 @@ defineExpose({
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input.has-shortcut {
|
||||||
|
padding-right: calc(var(--shortcut-width, 40px) + 22px);
|
||||||
|
}
|
||||||
|
|
||||||
.input:hover, .input:focus {
|
.input:hover, .input:focus {
|
||||||
border-color: var(--vp-c-brand);
|
border-color: var(--vp-c-brand);
|
||||||
background: var(--vp-c-bg-alt);
|
background: var(--vp-c-bg-alt);
|
||||||
@@ -66,7 +86,28 @@ defineExpose({
|
|||||||
padding-left: 52px;
|
padding-left: 52px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shortcut {
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
padding: 2px 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--vp-c-text-3);
|
||||||
|
background: var(--vp-c-default-soft);
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
border-radius: 4px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: none) {
|
||||||
|
.shortcut {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const SearchIcon = createLucideIcon('search', search)
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue: string
|
modelValue: string
|
||||||
|
shortcut?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
@@ -38,6 +39,8 @@ const value = computed({
|
|||||||
<Input
|
<Input
|
||||||
ref="input"
|
ref="input"
|
||||||
type="search"
|
type="search"
|
||||||
|
autofocus
|
||||||
|
:shortcut="shortcut"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
v-model="value"
|
v-model="value"
|
||||||
class="input-wrapper"
|
class="input-wrapper"
|
||||||
@@ -70,5 +73,4 @@ const value = computed({
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ export type IconNode = [elementName: string, attrs: Record<string, string>][]
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
name: string;
|
name: string;
|
||||||
tags: string[];
|
tags?: string[];
|
||||||
categories: string[];
|
categories?: string[];
|
||||||
// contributors: Contributor[];
|
// contributors: Contributor[];
|
||||||
iconNode: IconNode;
|
iconNode: IconNode;
|
||||||
}>()
|
}>()
|
||||||
|
|||||||
61
docs/.vitepress/theme/components/base/OverviewLink.vue
Normal file
61
docs/.vitepress/theme/components/base/OverviewLink.vue
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
href?: string
|
||||||
|
title?: string
|
||||||
|
desc?: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VPLink
|
||||||
|
class="overview-link"
|
||||||
|
:href="href"
|
||||||
|
:aria-label="`${title} - ${desc}`"
|
||||||
|
>
|
||||||
|
<span class="title">
|
||||||
|
{{ title }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="desc">
|
||||||
|
{{ desc }}
|
||||||
|
</span>
|
||||||
|
</VPLink>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.overview-link {
|
||||||
|
display: block;
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 11px 16px 13px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transition: border-color 0.25s;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-link:hover {
|
||||||
|
border-color: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
display: block;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: block;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
transition: color 0.25s;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
31
docs/.vitepress/theme/components/base/OverviewLinkGrid.vue
Normal file
31
docs/.vitepress/theme/components/base/OverviewLinkGrid.vue
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<template>
|
||||||
|
<div class="overview-link-grid">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.overview-link-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-link-grid > * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.overview-link-grid {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
.overview-link-grid {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
226
docs/.vitepress/theme/components/base/Select.vue
Normal file
226
docs/.vitepress/theme/components/base/Select.vue
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import {
|
||||||
|
Listbox,
|
||||||
|
ListboxLabel,
|
||||||
|
ListboxButton,
|
||||||
|
ListboxOptions,
|
||||||
|
ListboxOption,
|
||||||
|
} from '@headlessui/vue'
|
||||||
|
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
id?: string
|
||||||
|
items?: { name: string, icon: string }[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const selected = defineModel<{ name: string, icon: string }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Listbox v-model="selected">
|
||||||
|
<div class="select-wrapper">
|
||||||
|
<ListboxButton class="select-button" :id="id">
|
||||||
|
<img
|
||||||
|
:src="selected.icon"
|
||||||
|
:class="{ 'select-item-icon': true }"
|
||||||
|
:alt="`${selected.name} logo`"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<span class="select-text">{{ selected.name }}</span>
|
||||||
|
<span class="select-icon">
|
||||||
|
<ChevronsUpDownIcon class="chevron-icon" aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
</ListboxButton>
|
||||||
|
|
||||||
|
<transition
|
||||||
|
leave-active-class="transition-leave"
|
||||||
|
leave-from-class="transition-leave-from"
|
||||||
|
leave-to-class="transition-leave-to"
|
||||||
|
>
|
||||||
|
<ListboxOptions class="select-options">
|
||||||
|
<ListboxOption
|
||||||
|
v-slot="{ active, selected }"
|
||||||
|
v-for="item in items"
|
||||||
|
:key="item.name"
|
||||||
|
:value="item"
|
||||||
|
as="template"
|
||||||
|
>
|
||||||
|
<li :class="['select-option', { active, selected }]">
|
||||||
|
<img
|
||||||
|
:src="item.icon"
|
||||||
|
:class="{ 'select-item-icon': true }"
|
||||||
|
:alt="`${item.name} logo`"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<span :class="['option-text', { selected }]">{{ item.name }}</span>
|
||||||
|
<span v-if="selected" class="check-icon">
|
||||||
|
<CheckIcon class="check" aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ListboxOption>
|
||||||
|
</ListboxOptions>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</Listbox>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.select-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.select-button {
|
||||||
|
background: var(--vp-sidebar-input);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
padding: 7px 14px;
|
||||||
|
height: auto;
|
||||||
|
font-size: 14px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
cursor: text;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-button:focus {
|
||||||
|
border-color: var(--vp-c-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-text {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-icon {
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-item-icon {
|
||||||
|
object-fit: contain;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chevron-icon {
|
||||||
|
height: 1.25rem;
|
||||||
|
width: 1.25rem;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transition-leave {
|
||||||
|
transition: opacity 100ms ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transition-leave-from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transition-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-options {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
min-width: 128px;
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
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));
|
||||||
|
overflow-y: auto;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 90;
|
||||||
|
right: 0;
|
||||||
|
top: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-option {
|
||||||
|
position: relative;
|
||||||
|
cursor: default;
|
||||||
|
user-select: none;
|
||||||
|
padding: 0px 4px;
|
||||||
|
text-align: left;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
line-height: 32px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: background-color .25s,color .25s;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-option:hover, .select-option.active {
|
||||||
|
color: var(--vp-c-brand);
|
||||||
|
background-color: var(--vp-c-default-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-option:active {
|
||||||
|
color: var(--vp-c-brand);
|
||||||
|
background-color: var(--vp-c-bg-elv);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-text {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-text.selected {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
color: var(--vp-c-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
.check {
|
||||||
|
height: 1.25rem;
|
||||||
|
width: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.select-button,
|
||||||
|
.select-options {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
@@ -1,15 +1,22 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
|
||||||
import { Switch } from '@headlessui/vue'
|
import { Switch } from '@headlessui/vue'
|
||||||
|
|
||||||
const enabled = ref(false)
|
defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Switch
|
<Switch
|
||||||
v-model="enabled"
|
:model-value="modelValue"
|
||||||
|
@update:model-value="emit('update:modelValue', $event)"
|
||||||
class="switch"
|
class="switch"
|
||||||
:class="{ enabled }"
|
:class="{ enabled: modelValue }"
|
||||||
>
|
>
|
||||||
<span class="thumb" />
|
<span class="thumb" />
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|||||||
136
docs/.vitepress/theme/components/editors/Sandbox.vue
Normal file
136
docs/.vitepress/theme/components/editors/Sandbox.vue
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { getSandpackFiles, getCustomSetupFromProps, parsedBoolean, getSandpackOptions } from 'vitepress-plugin-sandpack';
|
||||||
|
import { Sandpack, type SandpackFiles } from 'sandpack-vue3';
|
||||||
|
import { computed, nextTick, onBeforeMount, onMounted, ref, useSlots, watch } from 'vue';
|
||||||
|
import styles from './styles.css?raw'
|
||||||
|
|
||||||
|
import sandpackTheme from '../../sandpackTheme.json'
|
||||||
|
import { sandboxProps } from 'vitepress-plugin-sandpack';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
...sandboxProps,
|
||||||
|
editorHeight: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
editorWidthPercentage: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const files = ref<SandpackFiles>({});
|
||||||
|
|
||||||
|
const getOpt = (propName: string) => props[propName] ?? props?.options?.[propName];
|
||||||
|
|
||||||
|
const editorVisible = computed(() => parsedBoolean(getOpt('hideEditor')) ? 'none' : 'flex');
|
||||||
|
|
||||||
|
const previewHeight = computed(() => isNaN(Number(getOpt('previewHeight'))) ? undefined : Number(getOpt('previewHeight')));
|
||||||
|
const previewHeightStyle =
|
||||||
|
computed(() => previewHeight.value ? `${previewHeight.value}px` : 'var(--sp-layout-height)');
|
||||||
|
|
||||||
|
const coderHeight = computed(() => isNaN(Number(getOpt('coderHeight'))) ? undefined : Number(getOpt('coderHeight')));
|
||||||
|
const coderHeightStyle =
|
||||||
|
computed(() => coderHeight.value ? `${coderHeight.value}px` : 'var(--sp-layout-height)');
|
||||||
|
|
||||||
|
const slots = useSlots();
|
||||||
|
const isDark = ref(false);
|
||||||
|
|
||||||
|
const resolveFiles = async () => {
|
||||||
|
files.value = {
|
||||||
|
...await getSandpackFiles(props, slots),
|
||||||
|
'styles.css': {
|
||||||
|
code: styles,
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(props, resolveFiles);
|
||||||
|
|
||||||
|
onBeforeMount(resolveFiles);
|
||||||
|
|
||||||
|
const dependencies = computed(() => {
|
||||||
|
if (props.dependencies) {
|
||||||
|
return props.dependencies.split(',').reduce((acc, dep) => {
|
||||||
|
const [name, version] = dep.split(':').map(s => s.trim());
|
||||||
|
acc[name] = version || 'latest';
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, string>);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Sandpack :theme="sandpackTheme" :template="template" :rtl="parsedBoolean(rtl)" :files="files" :options="{
|
||||||
|
...(getSandpackOptions(props) as any),
|
||||||
|
editorWidthPercentage: getOpt('editorWidthPercentage') ? Number(getOpt('editorWidthPercentage')) : undefined,
|
||||||
|
showConsoleButton: false,
|
||||||
|
}" :customSetup='{
|
||||||
|
dependencies: dependencies
|
||||||
|
}' />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.sp-wrapper+* {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp-wrapper .sp-layout {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp-wrapper .sp-tabs-scrollable-container {
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
box-shadow: inset 0 -1px var(--vp-code-tab-divider);
|
||||||
|
margin-bottom: 0px;
|
||||||
|
margin-top: -1px;
|
||||||
|
height: 48px;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp-wrapper .sp-preview-container {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp-wrapper .sp-tabs .sp-tab-button {
|
||||||
|
padding: 0 12px;
|
||||||
|
line-height: 48px;
|
||||||
|
height: 48px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp-wrapper .sp-tabs .sp-tab-button:after {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
left: 8px;
|
||||||
|
bottom: 0px;
|
||||||
|
z-index: 1;
|
||||||
|
height: 1px;
|
||||||
|
content: '';
|
||||||
|
background-color: transparent;
|
||||||
|
transition: background-color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp-wrapper .sp-tabs .sp-tab-button[data-active='true'] {
|
||||||
|
color: var(--vp-code-tab-active-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp-wrapper .sp-tabs .sp-tab-button[data-active='true']:after {
|
||||||
|
background-color: var(--vp-code-tab-active-bar-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp-wrapper .sp-button {
|
||||||
|
color: var(--sp-colors-clickable);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
48
docs/.vitepress/theme/components/editors/SnackPlayer.vue
Normal file
48
docs/.vitepress/theme/components/editors/SnackPlayer.vue
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useMutationObserver, useScriptTag } from '@vueuse/core';
|
||||||
|
import { useData } from 'vitepress';
|
||||||
|
import { onMounted, useTemplateRef, watchEffect } from 'vue';
|
||||||
|
|
||||||
|
const { isDark } = useData()
|
||||||
|
const el = useTemplateRef('el')
|
||||||
|
|
||||||
|
useScriptTag('https://snack.expo.dev/embed.js')
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
console.log(isDark.value);
|
||||||
|
})
|
||||||
|
|
||||||
|
useMutationObserver(el, (mutations) => {
|
||||||
|
const container = el.value;
|
||||||
|
if (mutations[0]) {
|
||||||
|
if ('ExpoSnack' in window) {
|
||||||
|
window?.ExpoSnack?.remove(container);
|
||||||
|
window?.ExpoSnack?.append(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
attributes: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const container = el.value;
|
||||||
|
if ('ExpoSnack' in window) {
|
||||||
|
window?.ExpoSnack?.append(container);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-bind="$attrs" class="snack-player" ref="el" :data-snack-theme="isDark ? 'dark' : 'light'" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.snack-player {
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 100%;
|
||||||
|
height: 635px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
50
docs/.vitepress/theme/components/editors/styles.css
Normal file
50
docs/.vitepress/theme/components/editors/styles.css
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
-webkit-font-smoothing: auto;
|
||||||
|
-moz-font-smoothing: auto;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
font-smoothing: auto;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
font-smooth: always;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
background: #202127;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
line-height: 24px;
|
||||||
|
gap: 8px;
|
||||||
|
border-radius: 24px;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
background: #111;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: #f56565;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 32px;
|
||||||
|
}
|
||||||
88
docs/.vitepress/theme/components/guide/FrameworkSelect.vue
Normal file
88
docs/.vitepress/theme/components/guide/FrameworkSelect.vue
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<script setup lang="tsx">
|
||||||
|
import VPSidebarGroup from 'vitepress/dist/client/theme-default/components/VPSidebarGroup.vue';
|
||||||
|
import sidebar, { guideSidebarTop } from '../../../sidebar';
|
||||||
|
import { useData, useRouter } from 'vitepress';
|
||||||
|
import Select from '../base/Select.vue';
|
||||||
|
import { computed, ref, watch, watchEffect } from 'vue';
|
||||||
|
import { link, route } from '~/.vitepress/data/iconNodes';
|
||||||
|
import { useLocalStorage } from '@vueuse/core';
|
||||||
|
|
||||||
|
const { page } = useData()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const frameworks = [
|
||||||
|
{ name: 'Vanilla', icon: '/framework-logos/js.svg', route: '/guide/lucide' },
|
||||||
|
{ name: 'React', icon: '/framework-logos/react.svg', route: '/guide/react' },
|
||||||
|
{ name: 'Vue', icon: '/framework-logos/vue.svg', route: '/guide/vue' },
|
||||||
|
{ name: 'Svelte', icon: '/framework-logos/svelte.svg', route: '/guide/svelte' },
|
||||||
|
{ name: 'Solid', icon: '/framework-logos/solid.svg', route: '/guide/solid' },
|
||||||
|
{ name: 'Angular', icon: '/framework-logos/angular.svg', route: '/guide/angular' },
|
||||||
|
{ name: 'Preact', icon: '/framework-logos/preact.svg', route: '/guide/preact' },
|
||||||
|
{ name: 'React Native', icon: '/framework-logos/react-native.svg', route: '/guide/react-native' },
|
||||||
|
{ name: 'Astro', icon: '/framework-logos/astro-dark.svg', route: '/guide/astro' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const fallbackFramework = useLocalStorage('lucide-docs-fallback-framework', frameworks[1])
|
||||||
|
|
||||||
|
const selected = computed(() => {
|
||||||
|
const current = frameworks.find(({ route }) => {
|
||||||
|
return router.route.path.split('/').slice(0, 3).join('/') === route
|
||||||
|
})
|
||||||
|
|
||||||
|
return current || fallbackFramework.value
|
||||||
|
})
|
||||||
|
|
||||||
|
function onSelectFramework(item: { name: string, icon: string, route: string }) {
|
||||||
|
fallbackFramework.value = item
|
||||||
|
if (item.route !== router.route.path) {
|
||||||
|
const likeRoute = router.route.path.replace(selected.value.route, item.route);
|
||||||
|
|
||||||
|
const hasRoute = sidebar[item.route]?.some(section =>
|
||||||
|
section?.items?.some(({ link }) => link === likeRoute)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasRoute) {
|
||||||
|
router.go(likeRoute)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
router.go(item.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VPSidebarGroup :items="guideSidebarTop" v-if="page?.relativePath?.startsWith?.('guide')" />
|
||||||
|
<div class="framework-select" v-if="page?.relativePath?.startsWith?.('guide')">
|
||||||
|
<label for="framework-select">Framework</label>
|
||||||
|
<Select id="framework-select" :items="frameworks" @update:model-value="onSelectFramework" v-model="selected" />
|
||||||
|
</div>
|
||||||
|
<VPSidebarGroup :key="selected.route"
|
||||||
|
v-if="page?.relativePath?.startsWith?.('guide') && !page?.relativePath?.startsWith?.(selected.route.substring(1))"
|
||||||
|
:items="sidebar[selected.route]" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.framework-select {
|
||||||
|
font-size: 12px;
|
||||||
|
transition: border-color 0.5s, background-color 0.5s ease;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
position: sticky;
|
||||||
|
top: -0.5px;
|
||||||
|
z-index: 10;
|
||||||
|
border-top: 1px solid var(--vp-c-divider);
|
||||||
|
padding-top: 10px;
|
||||||
|
margin-top: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
padding: 4px 0;
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: color 0.25s;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
51
docs/.vitepress/theme/components/home/HomeHeroAfter.vue
Normal file
51
docs/.vitepress/theme/components/home/HomeHeroAfter.vue
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from 'vitepress';
|
||||||
|
import Badge from '../base/Badge.vue';
|
||||||
|
import HomeContainer from './HomeContainer.vue';
|
||||||
|
import { data } from './HomeHeroIconsCard.data'
|
||||||
|
import FakeInput from '../base/FakeInput.vue'
|
||||||
|
import { nextTick, provide } from 'vue'
|
||||||
|
import useSearchShortcut from '../../utils/useSearchShortcut';
|
||||||
|
|
||||||
|
const { go } = useRouter()
|
||||||
|
|
||||||
|
const { shortcutText: kbdSearchShortcut } = useSearchShortcut(() => {
|
||||||
|
go('/icons/?focus')
|
||||||
|
})
|
||||||
|
|
||||||
|
const enableTransitions = () =>
|
||||||
|
'startViewTransition' in document &&
|
||||||
|
window.matchMedia('(prefers-reduced-motion: no-preference)').matches
|
||||||
|
|
||||||
|
|
||||||
|
async function handleClick(event: MouseEvent) {
|
||||||
|
if (!enableTransitions()) {
|
||||||
|
go('/icons/?focus')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
await document.startViewTransition(async () => {
|
||||||
|
await go('/icons/?focus');
|
||||||
|
await nextTick()
|
||||||
|
}).ready
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<FakeInput
|
||||||
|
@click="go('/icons/?focus')"
|
||||||
|
:shortcut="kbdSearchShortcut"
|
||||||
|
class="search-box"
|
||||||
|
>
|
||||||
|
Search {{ data.iconsCount }} icons...
|
||||||
|
</FakeInput>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.search-box {
|
||||||
|
view-transition-name: icons-search-box;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
428
docs/.vitepress/theme/components/home/HomeHeroIconsAnimation.vue
Normal file
428
docs/.vitepress/theme/components/home/HomeHeroIconsAnimation.vue
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, shallowRef, onBeforeUnmount, watchEffect, computed } from 'vue';
|
||||||
|
import { data } from './HomeHeroIconsCard.data'
|
||||||
|
import { useRouter } from 'vitepress';
|
||||||
|
import { random } from 'lodash-es'
|
||||||
|
import FakeInput from '../base/FakeInput.vue'
|
||||||
|
import { motion, Variants, useScroll, useSpring, useTransform } from "motion-v"
|
||||||
|
import LucideIcon from '../base/LucideIcon.vue'
|
||||||
|
import { shrink } from '~/.vitepress/data/iconNodes';
|
||||||
|
|
||||||
|
const emit = defineEmits(['animation-complete'])
|
||||||
|
|
||||||
|
const MotionLucideIcon = motion.create(LucideIcon)
|
||||||
|
|
||||||
|
const COLUMNS = 8;
|
||||||
|
const SIZE = 2;
|
||||||
|
const GAP = 1;
|
||||||
|
|
||||||
|
const { scrollYProgress } = useScroll()
|
||||||
|
const opacity = useTransform(() => (1 - scrollYProgress.get() * 8))
|
||||||
|
|
||||||
|
const icons = ref(data.icons.slice(0, 64).map((icon, index) => {
|
||||||
|
const x = index % COLUMNS;
|
||||||
|
const y = Math.floor(index / COLUMNS);
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
return {
|
||||||
|
...icon,
|
||||||
|
x: 9999,
|
||||||
|
y: 9999,
|
||||||
|
opacity: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...icon,
|
||||||
|
x: x * (SIZE + GAP) + 0.5,
|
||||||
|
y: y * (SIZE + GAP) + 0.5
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { go } = useRouter()
|
||||||
|
const intervalTime = shallowRef()
|
||||||
|
const showHandles = ref(true)
|
||||||
|
const scaleDownVariants: Variants = {
|
||||||
|
fullSize: {
|
||||||
|
scale: 1
|
||||||
|
},
|
||||||
|
riseUp: {
|
||||||
|
x: 0.5,
|
||||||
|
y: -0.5,
|
||||||
|
animationName: 'riseUp',
|
||||||
|
scale: 1,
|
||||||
|
transition: {
|
||||||
|
delay: 0.5,
|
||||||
|
duration: 1.5,
|
||||||
|
ease: [0.22, 1, 0.36, 1]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
small: {
|
||||||
|
x: -10.5,
|
||||||
|
y: -10.5,
|
||||||
|
scale: 0.1,
|
||||||
|
animationName: 'small',
|
||||||
|
transition: {
|
||||||
|
delay: 1,
|
||||||
|
duration: 1,
|
||||||
|
ease: [0.22, 1, 0.36, 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const scaleDownAnimation = ref('fullSize')
|
||||||
|
|
||||||
|
const iconGridAnimation = ref('initial')
|
||||||
|
|
||||||
|
const drawAnimation = ref('visible')
|
||||||
|
const draw: Variants = {
|
||||||
|
hidden: { pathLength: 0, opacity: 0 },
|
||||||
|
visible: {
|
||||||
|
animationName: 'visible',
|
||||||
|
pathLength: 1,
|
||||||
|
opacity: 1,
|
||||||
|
transition: {
|
||||||
|
pathLength: { delay: 2.4, type: "spring", duration: 2.8, bounce: 0 },
|
||||||
|
opacity: { delay: 2.4, duration: 0.1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
exit: (path) => ({
|
||||||
|
animationName: 'exit',
|
||||||
|
stroke: path ? 'var(--vp-c-text-1)' : 'var(--vp-c-brand)',
|
||||||
|
pathLength: 1,
|
||||||
|
opacity: 1,
|
||||||
|
transition: {
|
||||||
|
duration: 0.8,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAnimationComplete = (item) => {
|
||||||
|
if (item.animationName === 'visible') {
|
||||||
|
drawAnimation.value = 'exit'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (item.animationName === 'exit') {
|
||||||
|
showHandles.value = false
|
||||||
|
scaleDownAnimation.value = 'small'
|
||||||
|
}
|
||||||
|
if (item.animationName === 'small') {
|
||||||
|
iconGridAnimation.value = 'showIcons'
|
||||||
|
}
|
||||||
|
if (item.animationName === 'riseUp') {
|
||||||
|
scaleDownAnimation.value = 'small'
|
||||||
|
}
|
||||||
|
if (item.animationName === 'showIcons') {
|
||||||
|
shrinkIconAnimation.value = 'shrinkIcons'
|
||||||
|
}
|
||||||
|
if (item.animationName === 'shrinkIcons') {
|
||||||
|
iconGridAnimation.value = 'initial'
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
emit('animation-complete')
|
||||||
|
}, 2800)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const randomIndex = ref(
|
||||||
|
Math.floor(Math.random() * 64)
|
||||||
|
)
|
||||||
|
|
||||||
|
const iconAnimationVariants = {
|
||||||
|
initial: {
|
||||||
|
animationName: 'end',
|
||||||
|
opacity: 0,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
transition: { duration: 1, delay: 1, ease: 'easeInOut' }
|
||||||
|
},
|
||||||
|
showIcons: (index) => ({
|
||||||
|
animationName: 'showIcons',
|
||||||
|
opacity: [0, 1, 1],
|
||||||
|
x: [0.5, 0, 0],
|
||||||
|
y: [-0.5, 0, 0],
|
||||||
|
strokeWidth: randomIndex.value === index ? [0, 2, 2] : undefined,
|
||||||
|
transition: { delay: index * 0.023, duration: 1.6, ease: 'easeInOut' }
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const shrinkIconAnimation = ref('initial')
|
||||||
|
|
||||||
|
const shrinkIconVariants = {
|
||||||
|
initial: { strokeWidth: 2 },
|
||||||
|
shrinkIcons: (index) => ({
|
||||||
|
animationName: 'shrinkIcons',
|
||||||
|
opacity: 1,
|
||||||
|
strokeWidth: 0,
|
||||||
|
transition: { delay: 1.8, duration: 1.5, ease: 'easeInOut' }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="home-hero-animation-container">
|
||||||
|
<div class="home-hero-animation">
|
||||||
|
<motion.svg xmlns="http://www.w3.org/2000/svg" viewBox="-12 -12 48 48" fill="none" overflow="auto"
|
||||||
|
stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="hero-background"
|
||||||
|
:style="{ opacity }">
|
||||||
|
|
||||||
|
<g class="svg-preview-grid-group" stroke-linecap="butt" stroke-width="0.1" stroke="#777"
|
||||||
|
mask="url(#svg-preview-bounding-box-mask)" stroke-opacity="0.3">
|
||||||
|
<path
|
||||||
|
stroke-dasharray="0 0.1 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0 0.15"
|
||||||
|
stroke-width="0.1"
|
||||||
|
d="M1 0.1v23.8M2 0.1v23.8M4 0.1v23.8M5 0.1v23.8M7 0.1v23.8M8 0.1v23.8M10 0.1v23.8M11 0.1v23.8M13 0.1v23.8M14 0.1v23.8M16 0.1v23.8M17 0.1v23.8M19 0.1v23.8M20 0.1v23.8M22 0.1v23.8M23 0.1v23.8M0.1 1h23.8M0.1 2h23.8M0.1 4h23.8M0.1 5h23.8M0.1 7h23.8M0.1 8h23.8M0.1 10h23.8M0.1 11h23.8M0.1 13h23.8M0.1 14h23.8M0.1 16h23.8M0.1 17h23.8M0.1 19h23.8M0.1 20h23.8M0.1 22h23.8M0.1 23h23.8">
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
d="M3 0.1v23.8M6 0.1v23.8M9 0.1v23.8M12 0.1v23.8M15 0.1v23.8M18 0.1v23.8M21 0.1v23.8M0.1 3h23.8M0.1 6h23.8M0.1 9h23.8M0.1 12h23.8M0.1 15h23.8M0.1 18h23.8M0.1 21h23.8">
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
<!-- <rect fill="red" x="0" y="0" width="24" height="24" fill-opacity="0.1" stroke="none" /> -->
|
||||||
|
<motion.g initial="initial" :variants="shrinkIconVariants" :animate="shrinkIconAnimation"
|
||||||
|
@animation-complete="onAnimationComplete">
|
||||||
|
<MotionLucideIcon v-for="(icon, index) in icons" size="2" class="animated-icon" initial="initial"
|
||||||
|
:key="icon.name" :variants="iconAnimationVariants" :animate="iconGridAnimation" :custom="index"
|
||||||
|
strokeWidth="inherit" v-bind="icon" @animation-complete="onAnimationComplete" />
|
||||||
|
<motion.g class="svg-preview-colored-path-group" :variants="scaleDownVariants" :animate="scaleDownAnimation"
|
||||||
|
initial="hidden" @animation-complete="onAnimationComplete">
|
||||||
|
<motion.path
|
||||||
|
d="M14 12C14 9.79086 12.2091 8 10 8C7.79086 8 6 9.79086 6 12C6 16.4183 9.58172 20 14 20C18.4183 20 22 16.4183 22 12C22 8.446 20.455 5.25285 18 3.05557"
|
||||||
|
:style="{ stroke: 'var(--vp-c-gray-1)' }" :animate="drawAnimation" initial="hidden" :variants="draw"
|
||||||
|
:custom="1" @animation-complete="onAnimationComplete" />
|
||||||
|
<motion.path
|
||||||
|
d="M10 12C10 14.2091 11.7909 16 14 16C16.2091 16 18 14.2091 18 12C18 7.58172 14.4183 4 10 4C5.58172 4 2 7.58172 2 12C2 15.5841 3.57127 18.8012 6.06253 21"
|
||||||
|
:style="{ stroke: 'var(--vp-c-gray-1)' }" :animate="drawAnimation" initial="hidden" :variants="draw"
|
||||||
|
:custom="0" />
|
||||||
|
</motion.g>
|
||||||
|
</motion.g>
|
||||||
|
<motion.g class="svg-preview-control-path-marker-group" stroke="#fff" stroke-width="0.125"
|
||||||
|
:initial="{ opacity: 1 }" :animate="showHandles ? { opacity: 1 } : { opacity: 0 }"
|
||||||
|
:transition="{ delay: 0, duration: 0.2 }">
|
||||||
|
<motion.path
|
||||||
|
d="M14 12C14 9.79086 12.2091 8 10 8C7.79086 8 6 9.79086 6 12C6 16.4183 9.58172 20 14 20C18.4183 20 22 16.4183 22 12C22 8.446 20.455 5.25285 18 3.05557"
|
||||||
|
:initial="{ opacity: 0 }" :animate="{ opacity: 1 }" :transition="{ delay: 1.6, duration: 1.5 }" />
|
||||||
|
<motion.path
|
||||||
|
d="M10 12C10 14.2091 11.7909 16 14 16C16.2091 16 18 14.2091 18 12C18 7.58172 14.4183 4 10 4C5.58172 4 2 7.58172 2 12C2 15.5841 3.57127 18.8012 6.06253 21"
|
||||||
|
:initial="{ opacity: 0 }" :animate="{ opacity: 1 }" :transition="{ delay: 1.6, duration: 1.5 }" />
|
||||||
|
<motion.g :initial="{ opacity: 0 }" :animate="{ opacity: 1 }" :transition="{ delay: 0.2, duration: 0.3 }">
|
||||||
|
<path
|
||||||
|
d="M14 12h.01M10 8h.01M10 8h.01M6 12h.01M6 12h.01M14 20h.01M14 20h.01M22 12h.01M22 12h.01M18 3.05557h.01M10 12h.01M14 16h.01M14 16h.01M18 12h.01M18 12h.01M10 4h.01M10 4h.01M2 12h.01M2 12h.01M6.06253 21h.01">
|
||||||
|
</path>
|
||||||
|
</motion.g>
|
||||||
|
|
||||||
|
<motion.circle :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||||
|
:transition="{ delay: 0, duration: 0.8 }" cx="14" cy="12" r="0.5" />
|
||||||
|
<motion.circle :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||||
|
:transition="{ delay: 0, duration: 0.8 }" cx="14" cy="12" r="0.5" />
|
||||||
|
<motion.circle :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||||
|
:transition="{ delay: 0, duration: 0.8 }" cx="18" cy="3.05557" r="0.5" />
|
||||||
|
<motion.circle :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||||
|
:transition="{ delay: 0, duration: 0.8 }" cx="10" cy="12" r="0.5" />
|
||||||
|
<motion.circle :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||||
|
:transition="{ delay: 0, duration: 0.8 }" cx="6.06253" cy="21" r="0.5" />
|
||||||
|
</motion.g>
|
||||||
|
<motion.g class="svg-preview-handles-group" stroke-width="0.12" stroke="#FFF" stroke-opacity="0.3"
|
||||||
|
:initial="{ opacity: 1 }" :animate="showHandles ? { opacity: 1 } : { opacity: 0 }"
|
||||||
|
:transition="{ delay: 0, duration: 0.6 }">
|
||||||
|
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||||
|
:transition="{ delay: 0.2, duration: 0.3 }">
|
||||||
|
<path d="M14 12 14 9.79086"></path>
|
||||||
|
<circle cy="9.79086" cx="14" r="0.25"></circle>
|
||||||
|
</motion.g>
|
||||||
|
|
||||||
|
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||||
|
:transition="{ delay: 0.4, duration: 0.3 }">
|
||||||
|
<path d="M10 8 12.2091 8"></path>
|
||||||
|
<circle cy="8" cx="12.2091" r="0.25"></circle>
|
||||||
|
<path d="M10 8 7.79086 8"></path>
|
||||||
|
<circle cy="8" cx="7.79086" r="0.25"></circle>
|
||||||
|
</motion.g>
|
||||||
|
|
||||||
|
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||||
|
:transition="{ delay: 0.6, duration: 0.3 }">
|
||||||
|
<path d="M6 12 6 9.79086"></path>
|
||||||
|
<circle cy="9.79086" cx="6" r="0.25"></circle>
|
||||||
|
<path d="M6 12 6 16.4183"></path>
|
||||||
|
<circle cy="16.4183" cx="6" r="0.25"></circle>
|
||||||
|
</motion.g>
|
||||||
|
|
||||||
|
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||||
|
:transition="{ delay: 0.8, duration: 0.3 }">
|
||||||
|
<path d="M14 20 9.58172 20"></path>
|
||||||
|
<circle cy="20" cx="9.58172" r="0.25"></circle>
|
||||||
|
<path d="M14 20 18.4183 20"></path>
|
||||||
|
<circle cy="20" cx="18.4183" r="0.25"></circle>
|
||||||
|
</motion.g>
|
||||||
|
|
||||||
|
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||||
|
:transition="{ delay: 1, duration: 0.3 }">
|
||||||
|
<path d="M22 12 22 16.4183"></path>
|
||||||
|
<circle cy="16.4183" cx="22" r="0.25"></circle>
|
||||||
|
<path d="M22 12 22 8.446"></path>
|
||||||
|
<circle cy="8.446" cx="22" r="0.25"></circle>
|
||||||
|
</motion.g>
|
||||||
|
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||||
|
:transition="{ delay: 1.2, duration: 0.3 }">
|
||||||
|
<path d="M18 3.05557 20.455 5.25285"></path>
|
||||||
|
<circle cy="5.25285" cx="20.455" r="0.25"></circle>
|
||||||
|
</motion.g>
|
||||||
|
|
||||||
|
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||||
|
:transition="{ delay: 0.2, duration: 0.3 }">
|
||||||
|
<path d="M10 12 10 14.2091"></path>
|
||||||
|
<circle cy="14.2091" cx="10" r="0.25"></circle>
|
||||||
|
</motion.g>
|
||||||
|
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||||
|
:transition="{ delay: 0.4, duration: 0.3 }">
|
||||||
|
<path d="M14 16 11.7909 16"></path>
|
||||||
|
<circle cy="16" cx="11.7909" r="0.25"></circle>
|
||||||
|
<path d="M14 16 16.2091 16"></path>
|
||||||
|
<circle cy="16" cx="16.2091" r="0.25"></circle>
|
||||||
|
</motion.g>
|
||||||
|
|
||||||
|
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||||
|
:transition="{ delay: 0.6, duration: 0.3 }">
|
||||||
|
<path d="M18 12 18 14.2091"></path>
|
||||||
|
<circle cy="14.2091" cx="18" r="0.25"></circle>
|
||||||
|
<path d="M18 12 18 7.58172"></path>
|
||||||
|
<circle cy="7.58172" cx="18" r="0.25"></circle>
|
||||||
|
</motion.g>
|
||||||
|
|
||||||
|
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||||
|
:transition="{ delay: 0.8, duration: 0.3 }">
|
||||||
|
<path d="M10 4 14.4183 4"></path>
|
||||||
|
<circle cy="4" cx="14.4183" r="0.25"></circle>
|
||||||
|
<path d="M10 4 5.58172 4"></path>
|
||||||
|
<circle cy="4" cx="5.58172" r="0.25"></circle>
|
||||||
|
</motion.g>
|
||||||
|
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||||
|
:transition="{ delay: 1, duration: 0.3 }">
|
||||||
|
<path d="M2 12 2 7.58172"></path>
|
||||||
|
<circle cy="7.58172" cx="2" r="0.25"></circle>
|
||||||
|
<path d="M2 12 2 15.5841"></path>
|
||||||
|
<circle cy="15.5841" cx="2" r="0.25"></circle>
|
||||||
|
</motion.g>
|
||||||
|
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||||
|
:transition="{ delay: 1.2, duration: 0.3 }">
|
||||||
|
<path d="M6.06253 21 3.57127 18.8012"></path>
|
||||||
|
<circle cy="18.8012" cx="3.57127" r="0.25"></circle>
|
||||||
|
</motion.g>
|
||||||
|
</motion.g>
|
||||||
|
</motion.svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.home-hero-animation-container {
|
||||||
|
margin: -48px -24px 0;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-hero-animation {
|
||||||
|
height: 250px;
|
||||||
|
width: 396px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: auto;
|
||||||
|
margin-left: calc(((396px - 100vw) / 2)* -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 396px) {
|
||||||
|
.home-hero-animation {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-background {
|
||||||
|
transform: rotateX(-51deg) rotateZ(-43deg);
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
position: relative;
|
||||||
|
top: -155px;
|
||||||
|
left: -82px;
|
||||||
|
width: 560px;
|
||||||
|
height: 560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.hero-background {
|
||||||
|
width: 680px;
|
||||||
|
height: 680px;
|
||||||
|
|
||||||
|
left: -100px;
|
||||||
|
top: -188px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-hero-animation {
|
||||||
|
height: 305px;
|
||||||
|
width: 480px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.hero-background {
|
||||||
|
width: 760px;
|
||||||
|
height: 760px;
|
||||||
|
|
||||||
|
left: -110px;
|
||||||
|
top: -200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-hero-animation {
|
||||||
|
height: 360px;
|
||||||
|
width: 540px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-hero-animation-container {
|
||||||
|
margin-top: -60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.hero-background {
|
||||||
|
top: -20vw;
|
||||||
|
right: 20vw;
|
||||||
|
width: 80vw;
|
||||||
|
height: 80vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-hero-animation {
|
||||||
|
height: 415px;
|
||||||
|
width: 620px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-hero-animation-container {
|
||||||
|
margin: -48px -48px 0 -64px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1160px) {
|
||||||
|
.home-hero-animation-container {
|
||||||
|
margin-right: -64px;
|
||||||
|
margin-bottom: -180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-hero-animation {
|
||||||
|
width: auto;
|
||||||
|
height: calc(((1152px/2)));
|
||||||
|
top: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-background {
|
||||||
|
top: -20vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
.home-hero-animation-container {
|
||||||
|
margin-right: calc(((((100vw - 1152px) / 2)) * -1) + 24px);
|
||||||
|
margin-left: -128px
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-background {
|
||||||
|
width: 1024px;
|
||||||
|
height: 1024px;
|
||||||
|
top: -280px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,153 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, shallowRef, onBeforeUnmount} from 'vue';
|
import { ref } from 'vue';
|
||||||
import { data } from './HomeHeroIconsCard.data'
|
import HomeHeroIconsAnimation from './HomeHeroIconsAnimation.vue'
|
||||||
import LucideIcon from '../base/LucideIcon.vue'
|
|
||||||
import { useRouter } from 'vitepress';
|
|
||||||
import { random } from 'lodash-es'
|
|
||||||
import FakeInput from '../base/FakeInput.vue'
|
|
||||||
|
|
||||||
const { go } = useRouter()
|
|
||||||
const intervalTime = shallowRef()
|
|
||||||
|
|
||||||
const getInitialItems = () => data.icons.slice(0, 48)
|
|
||||||
const items = ref(getInitialItems())
|
|
||||||
let id = items.value.length + 1
|
|
||||||
|
|
||||||
function getRandomNewIcon() {
|
|
||||||
const randomIndex = random(0, 200)
|
|
||||||
const newRandomIcon = data.icons[randomIndex]
|
|
||||||
|
|
||||||
if (items.value.some((item) => item.name === newRandomIcon.name)) {
|
|
||||||
return getRandomNewIcon()
|
|
||||||
}
|
|
||||||
|
|
||||||
return newRandomIcon
|
|
||||||
}
|
|
||||||
|
|
||||||
function insert() {
|
|
||||||
const replaceIndex = random(0, 48)
|
|
||||||
const newIcon = getRandomNewIcon()
|
|
||||||
|
|
||||||
items.value[replaceIndex] = newIcon
|
|
||||||
}
|
|
||||||
|
|
||||||
function startInterval() {
|
|
||||||
intervalTime.value = setInterval(() => {
|
|
||||||
insert()
|
|
||||||
}, 2000)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Try maybe something else for better pref performance
|
|
||||||
onMounted(() => {
|
|
||||||
window.addEventListener('mousemove', startInterval, { once: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
clearInterval(intervalTime.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
const animationRun = ref(1)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="card-wrapper">
|
<HomeHeroIconsAnimation
|
||||||
<div class="icons-card">
|
:key="animationRun"
|
||||||
<div class="card-grid">
|
@animation-complete="animationRun++"
|
||||||
<TransitionGroup name="list" mode="out-in">
|
/>
|
||||||
<div
|
|
||||||
v-for="icon in items"
|
|
||||||
:key="icon.name"
|
|
||||||
class="random-icon"
|
|
||||||
>
|
|
||||||
<LucideIcon
|
|
||||||
v-bind="icon"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TransitionGroup>
|
|
||||||
</div>
|
|
||||||
<FakeInput @click="go('/icons/?focus')" class="search-box">
|
|
||||||
Search {{ data.iconsCount }} icons...
|
|
||||||
</FakeInput>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.card-wrapper {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-bottom: auto;
|
|
||||||
margin-top: 48px;
|
|
||||||
}
|
|
||||||
.icons-card {
|
|
||||||
background: var(--vp-c-bg-alt);
|
|
||||||
padding: 24px;
|
|
||||||
border-radius: 8px;
|
|
||||||
width: 100%;
|
|
||||||
height:100%;
|
|
||||||
max-height: 220px;
|
|
||||||
max-width: 560px;
|
|
||||||
margin: 0 auto;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-grid {
|
|
||||||
display: grid;
|
|
||||||
gap: 8px;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(36px, 1fr));
|
|
||||||
grid-template-rows: repeat(auto-fill, minmax(36px, 1fr));
|
|
||||||
width: 100%;
|
|
||||||
height:100%;
|
|
||||||
max-height: 168px;
|
|
||||||
max-width: 512px;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-enter-active {
|
|
||||||
transition: all 0.5s cubic-bezier(.85,.85,.25,1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-enter-from,
|
|
||||||
.list-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.01);
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-leave-active {
|
|
||||||
position: absolute;
|
|
||||||
opacity: 0;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
left: 0;
|
|
||||||
top: -64px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.random-icon {
|
|
||||||
display: inline-flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 960px) {
|
|
||||||
.search-box {
|
|
||||||
top: unset;
|
|
||||||
bottom: -24px;
|
|
||||||
left: -24px;
|
|
||||||
|
|
||||||
box-shadow: var(--vp-shadow-3);
|
|
||||||
background: var(--vp-c-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .search-box {
|
|
||||||
background: var(--vp-c-bg-soft);
|
|
||||||
}
|
|
||||||
.card-wrapper {
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,25 +1,22 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Badge from '../base/Badge.vue';
|
import Badge from '../base/Badge.vue';
|
||||||
import HomeContainer from './HomeContainer.vue';
|
import HomeContainer from './HomeContainer.vue';
|
||||||
import { data } from './HomeHeroBefore.data'
|
import { data } from './HomeHeroInfoBefore.data'
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<HomeContainer class="container">
|
<Badge :href="`https://github.com/lucide-icons/lucide/releases/tag/${data.version}`">v{{ data.version }}</Badge>
|
||||||
<Badge
|
|
||||||
:href="`https://github.com/lucide-icons/lucide/releases/tag/${data.version}`"
|
|
||||||
>v{{ data.version }}</Badge>
|
|
||||||
</HomeContainer>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.container {
|
.container {
|
||||||
margin-block: 0;;
|
margin-block: 0;
|
||||||
margin-top: 37px;
|
margin-top: 37px;
|
||||||
margin-bottom: -96px;
|
margin-bottom: -96px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
@@ -34,9 +31,9 @@ import { data } from './HomeHeroBefore.data'
|
|||||||
.container {
|
.container {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@@ -48,6 +48,7 @@ function resetStyle () {
|
|||||||
color.value = 'currentColor'
|
color.value = 'currentColor'
|
||||||
strokeWidth.value = 2
|
strokeWidth.value = 2
|
||||||
size.value = 24
|
size.value = 24
|
||||||
|
absoluteStrokeWidth.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(absoluteStrokeWidth, (enabled) => {
|
watch(absoluteStrokeWidth, (enabled) => {
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ export default {
|
|||||||
label: 'Lucide documentation for React',
|
label: 'Lucide documentation for React',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'lucide-vue-next',
|
name: 'lucide-vue',
|
||||||
logo: '/framework-logos/vue.svg',
|
logo: '/framework-logos/vue.svg',
|
||||||
label: 'Lucide documentation for Vue 3',
|
label: 'Lucide documentation for Vue',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'lucide-svelte',
|
name: 'lucide-svelte',
|
||||||
@@ -37,6 +37,12 @@ export default {
|
|||||||
logo: '/framework-logos/angular.svg',
|
logo: '/framework-logos/angular.svg',
|
||||||
label: 'Lucide documentation for Angular',
|
label: 'Lucide documentation for Angular',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'lucide-astro',
|
||||||
|
logo: '/framework-logos/astro.svg',
|
||||||
|
logoDark: '/framework-logos/astro-dark.svg',
|
||||||
|
label: 'Lucide documentation for Astro',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'lucide-react-native',
|
name: 'lucide-react-native',
|
||||||
logo: '/framework-logos/react-native.svg',
|
logo: '/framework-logos/react-native.svg',
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const { go } = useRouter()
|
|||||||
<HomeSectionTitle>Available For:</HomeSectionTitle>
|
<HomeSectionTitle>Available For:</HomeSectionTitle>
|
||||||
<div class="packages-list">
|
<div class="packages-list">
|
||||||
<a
|
<a
|
||||||
v-for="{ name, logo } in data.packages"
|
v-for="{ name, logo, logoDark } in data.packages"
|
||||||
:href="`/guide/packages/${name}`"
|
:href="`/guide/packages/${name}`"
|
||||||
class="package-logo"
|
class="package-logo"
|
||||||
:aria-label="`Read more about: ${name} package`"
|
:aria-label="`Read more about: ${name} package`"
|
||||||
@@ -21,10 +21,17 @@ const { go } = useRouter()
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="logo"
|
:src="logo"
|
||||||
height="36"
|
:class="{ light: logoDark, 'image-logo': true }"
|
||||||
width="36"
|
|
||||||
loading="lazy"
|
|
||||||
:alt="`${name} logo`"
|
:alt="`${name} logo`"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="logoDark"
|
||||||
|
:src="logoDark"
|
||||||
|
:alt="`${name} logo`"
|
||||||
|
class="image-logo dark"
|
||||||
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,6 +42,13 @@ const { go } = useRouter()
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
|
.image-logo {
|
||||||
|
object-fit: contain;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
.packages-list {
|
.packages-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -57,4 +71,11 @@ const { go } = useRouter()
|
|||||||
.package-logo:hover {
|
.package-logo:hover {
|
||||||
opacity: .6;
|
opacity: .6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html.dark .image-logo.light {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
html:not(.dark) .image-logo.dark {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import {useData, useRouter} from 'vitepress';
|
|||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||||
import { diamond } from '../../../data/iconNodes'
|
import { diamond } from '../../../data/iconNodes'
|
||||||
|
import deprecationReasonTemplate from '../../../../../tools/build-icons/utils/deprecationReasonTemplate.ts';
|
||||||
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
icon: IconEntity
|
icon: IconEntity
|
||||||
@@ -24,6 +26,15 @@ const tags = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const DiamondIcon = createLucideIcon('Diamond', diamond)
|
const DiamondIcon = createLucideIcon('Diamond', diamond)
|
||||||
|
|
||||||
|
const deprecatedTitle = computed(() => {
|
||||||
|
if (!props.icon.deprecationReason) return '';
|
||||||
|
return deprecationReasonTemplate(props.icon.deprecationReason, {
|
||||||
|
componentName: props.icon.name,
|
||||||
|
iconName: props.icon.name,
|
||||||
|
toBeRemovedInVersion: props.icon.toBeRemovedInVersion,
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -36,6 +47,13 @@ const DiamondIcon = createLucideIcon('Diamond', diamond)
|
|||||||
<DiamondIcon fill="currentColor" :size="12"/>
|
<DiamondIcon fill="currentColor" :size="12"/>
|
||||||
{{ icon.externalLibrary }}
|
{{ icon.externalLibrary }}
|
||||||
</div>
|
</div>
|
||||||
|
<Badge
|
||||||
|
v-if="icon.deprecated"
|
||||||
|
class="deprecated-badge"
|
||||||
|
:title="deprecatedTitle"
|
||||||
|
>
|
||||||
|
Deprecated
|
||||||
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div class="tags-scroller" v-if="tags.length">
|
<div class="tags-scroller" v-if="tags.length">
|
||||||
<p class="icon-tags horizontal-scroller">
|
<p class="icon-tags horizontal-scroller">
|
||||||
@@ -98,6 +116,16 @@ const DiamondIcon = createLucideIcon('Diamond', diamond)
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.deprecated-badge {
|
||||||
|
background-color: var(--vp-c-brand-5);
|
||||||
|
margin-left: 40px;
|
||||||
|
opacity: .8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deprecated-badge:hover {
|
||||||
|
background-color: var(--vp-c-brand-2);
|
||||||
|
}
|
||||||
|
|
||||||
.icon-tags {
|
.icon-tags {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: var(--vp-c-text-2);
|
color: var(--vp-c-text-2);
|
||||||
|
|||||||
@@ -178,6 +178,8 @@ const DiamondIcon = createLucideIcon('Diamond', diamond)
|
|||||||
stroke-width: var(--customize-strokeWidth, 2);
|
stroke-width: var(--customize-strokeWidth, 2);
|
||||||
width: calc(var(--customize-size, 24) * 1px);
|
width: calc(var(--customize-size, 24) * 1px);
|
||||||
height: calc(var(--customize-size, 24) * 1px);
|
height: calc(var(--customize-size, 24) * 1px);
|
||||||
|
max-width: 3rem;
|
||||||
|
max-height: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.absolute-stroke-width .lucide-icon.customizable {
|
html.absolute-stroke-width .lucide-icon.customizable {
|
||||||
|
|||||||
@@ -26,10 +26,9 @@ const iconComponent = computed(() => {
|
|||||||
<component
|
<component
|
||||||
ref="previewIcon"
|
ref="previewIcon"
|
||||||
:is="iconComponent"
|
:is="iconComponent"
|
||||||
:width="size"
|
:size="size"
|
||||||
:height="size"
|
:color="color"
|
||||||
:stroke="color"
|
:strokeWidth="absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth"
|
||||||
:stroke-width="absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth"
|
|
||||||
/>
|
/>
|
||||||
<svg class="icon-grid" :viewBox="`0 0 ${size} ${size}`" fill="none" stroke-width="0.1" xmlns="http://www.w3.org/2000/svg">
|
<svg class="icon-grid" :viewBox="`0 0 ${size} ${size}`" fill="none" stroke-width="0.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g :key="`grid-${i}`" v-for="(_, i) in gridLines">
|
<g :key="`grid-${i}`" v-for="(_, i) in gridLines">
|
||||||
|
|||||||
@@ -29,7 +29,12 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const iconComponent = computed(() => {
|
const iconComponent = computed(() => {
|
||||||
if (!props.name || !props.iconNode) return null;
|
if (!props.name || !props.iconNode) return null;
|
||||||
return createLucideIcon(props.name, props.iconNode);
|
try {
|
||||||
|
return createLucideIcon(props.name, props.iconNode);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Icon ${props.name} not found, using fallback`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const CalendarIcon = createLucideIcon('calendar', Calendar.iconNode);
|
const CalendarIcon = createLucideIcon('calendar', Calendar.iconNode);
|
||||||
@@ -61,7 +66,7 @@ const prettyName = props.name
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="showcase">
|
<section class="showcase" v-if="iconComponent">
|
||||||
<h2 class="title">See this icon in action</h2>
|
<h2 class="title">See this icon in action</h2>
|
||||||
<div class="showcase-grid">
|
<div class="showcase-grid">
|
||||||
<div class="showcase-item column">
|
<div class="showcase-item column">
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { IconEntity, Category } from '../../types';
|
|||||||
import useSearch from '../../composables/useSearch';
|
import useSearch from '../../composables/useSearch';
|
||||||
import InputSearch from '../base/InputSearch.vue';
|
import InputSearch from '../base/InputSearch.vue';
|
||||||
import useSearchInput from '../../composables/useSearchInput';
|
import useSearchInput from '../../composables/useSearchInput';
|
||||||
|
import useSearchShortcut from '../../utils/useSearchShortcut';
|
||||||
import StickyBar from './StickyBar.vue';
|
import StickyBar from './StickyBar.vue';
|
||||||
import IconsCategory from './IconsCategory.vue';
|
import IconsCategory from './IconsCategory.vue';
|
||||||
import useFetchTags from '../../composables/useFetchTags';
|
import useFetchTags from '../../composables/useFetchTags';
|
||||||
@@ -27,6 +28,10 @@ const activeIconName = ref(null);
|
|||||||
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
||||||
const isSearching = computed(() => !!searchQuery.value);
|
const isSearching = computed(() => !!searchQuery.value);
|
||||||
|
|
||||||
|
const { shortcutText: kbdSearchShortcut } = useSearchShortcut(() => {
|
||||||
|
searchInput.value?.focus();
|
||||||
|
});
|
||||||
|
|
||||||
function setActiveIconName(name: string) {
|
function setActiveIconName(name: string) {
|
||||||
activeIconName.value = name;
|
activeIconName.value = name;
|
||||||
}
|
}
|
||||||
@@ -154,6 +159,7 @@ watchEffect(() => {
|
|||||||
<InputSearch
|
<InputSearch
|
||||||
:placeholder="`Search ${icons.length} icons ...`"
|
:placeholder="`Search ${icons.length} icons ...`"
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
|
:shortcut="kbdSearchShortcut"
|
||||||
class="input-wrapper"
|
class="input-wrapper"
|
||||||
ref="searchInput"
|
ref="searchInput"
|
||||||
@focus="onFocusSearchInput"
|
@focus="onFocusSearchInput"
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, defineAsyncComponent, onMounted, watch } from 'vue';
|
import { ref, computed, defineAsyncComponent, onMounted, onBeforeUnmount, watch } from 'vue';
|
||||||
import type { IconEntity } from '../../types';
|
import type { IconEntity } from '../../types';
|
||||||
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
||||||
|
import { useRoute } from 'vitepress';
|
||||||
import IconGrid from './IconGrid.vue';
|
import IconGrid from './IconGrid.vue';
|
||||||
import InputSearch from '../base/InputSearch.vue';
|
import InputSearch from '../base/InputSearch.vue';
|
||||||
import useSearch from '../../composables/useSearch';
|
import useSearch from '../../composables/useSearch';
|
||||||
import useSearchInput from '../../composables/useSearchInput';
|
import useSearchInput from '../../composables/useSearchInput';
|
||||||
|
import useSearchShortcut from '../../utils/useSearchShortcut';
|
||||||
import StickyBar from './StickyBar.vue';
|
import StickyBar from './StickyBar.vue';
|
||||||
import useFetchTags from '../../composables/useFetchTags';
|
import useFetchTags from '../../composables/useFetchTags';
|
||||||
import useFetchCategories from '../../composables/useFetchCategories';
|
import useFetchCategories from '../../composables/useFetchCategories';
|
||||||
@@ -14,7 +16,15 @@ import CarbonAdOverlay from './CarbonAdOverlay.vue';
|
|||||||
|
|
||||||
const ICON_SIZE = 56;
|
const ICON_SIZE = 56;
|
||||||
const ICON_GRID_GAP = 8;
|
const ICON_GRID_GAP = 8;
|
||||||
const DEFAULT_GRID_ITEMS = 10 * 160;
|
|
||||||
|
const initialGridItems = computed(() => {
|
||||||
|
if (containerWidth.value === 0) return 120;
|
||||||
|
|
||||||
|
const itemsPerRow = columnSize.value || 10;
|
||||||
|
const visibleRows = Math.ceil(window.innerHeight / (ICON_SIZE + ICON_GRID_GAP));
|
||||||
|
|
||||||
|
return Math.min(itemsPerRow * (visibleRows + 2), 200);
|
||||||
|
});
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
icons: IconEntity[];
|
icons: IconEntity[];
|
||||||
@@ -50,6 +60,11 @@ const mappedIcons = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
||||||
|
|
||||||
|
const { shortcutText: kbdSearchShortcut } = useSearchShortcut(() => {
|
||||||
|
searchInput.value?.focus();
|
||||||
|
});
|
||||||
|
|
||||||
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
|
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
|
||||||
{ name: 'name', weight: 3 },
|
{ name: 'name', weight: 3 },
|
||||||
{ name: 'aliases', weight: 3 },
|
{ name: 'aliases', weight: 3 },
|
||||||
@@ -72,8 +87,13 @@ const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
containerProps.ref.value = document.documentElement;
|
containerProps.ref.value = document.documentElement;
|
||||||
useEventListener(window, 'scroll', containerProps.onScroll)
|
useEventListener(window, 'scroll', containerProps.onScroll)
|
||||||
})
|
|
||||||
|
|
||||||
|
// Check if we should focus the search input from URL parameter
|
||||||
|
const route = useRoute()
|
||||||
|
if (route.data?.relativePath && window.location.search.includes('focus')) {
|
||||||
|
searchInput.value?.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function setActiveIconName(name: string) {
|
function setActiveIconName(name: string) {
|
||||||
activeIconName.value = name;
|
activeIconName.value = name;
|
||||||
@@ -110,20 +130,20 @@ function handleCloseDrawer() {
|
|||||||
:placeholder="`Search ${icons.length} icons ...`"
|
:placeholder="`Search ${icons.length} icons ...`"
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
ref="searchInput"
|
ref="searchInput"
|
||||||
|
:shortcut="kbdSearchShortcut"
|
||||||
class="input-wrapper"
|
class="input-wrapper"
|
||||||
@focus="onFocusSearchInput"
|
@focus="onFocusSearchInput"
|
||||||
/>
|
/>
|
||||||
</StickyBar>
|
</StickyBar>
|
||||||
<NoResults
|
<NoResults
|
||||||
v-if="list.length === 0 && searchQuery !== ''"
|
v-if="searchResults.length === 0 && searchQuery !== ''"
|
||||||
:searchQuery="searchQuery"
|
:searchQuery="searchQuery"
|
||||||
@clear="searchQuery = ''"
|
@clear="searchQuery = ''"
|
||||||
/>
|
/>
|
||||||
<IconGrid
|
<IconGrid
|
||||||
v-else-if="list.length === 0"
|
v-else-if="list.length === 0"
|
||||||
:key="index"
|
|
||||||
overlayMode
|
overlayMode
|
||||||
:icons="[...searchResults].splice(0, DEFAULT_GRID_ITEMS)"
|
:icons="searchResults.slice(0, initialGridItems)"
|
||||||
:activeIcon="activeIconName"
|
:activeIcon="activeIconName"
|
||||||
@setActiveIcon="setActiveIconName"
|
@setActiveIcon="setActiveIconName"
|
||||||
/>
|
/>
|
||||||
@@ -162,6 +182,7 @@ function handleCloseDrawer() {
|
|||||||
|
|
||||||
.input-wrapper {
|
.input-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
view-transition-name: icons-search-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overview-container {
|
.overview-container {
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ function resetStyle () {
|
|||||||
color.value = STYLE_DEFAULTS.color
|
color.value = STYLE_DEFAULTS.color
|
||||||
strokeWidth.value = STYLE_DEFAULTS.strokeWidth
|
strokeWidth.value = STYLE_DEFAULTS.strokeWidth
|
||||||
size.value = STYLE_DEFAULTS.size
|
size.value = STYLE_DEFAULTS.size
|
||||||
|
absoluteStrokeWidth.value = STYLE_DEFAULTS.absoluteStrokeWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(absoluteStrokeWidth, (enabled) => {
|
watch(absoluteStrokeWidth, (enabled) => {
|
||||||
@@ -59,9 +60,8 @@ const customizingActive = computed(() => {
|
|||||||
return color.value !== STYLE_DEFAULTS.color
|
return color.value !== STYLE_DEFAULTS.color
|
||||||
|| strokeWidth.value !== STYLE_DEFAULTS.strokeWidth
|
|| strokeWidth.value !== STYLE_DEFAULTS.strokeWidth
|
||||||
|| size.value !== STYLE_DEFAULTS.size
|
|| size.value !== STYLE_DEFAULTS.size
|
||||||
|
|| absoluteStrokeWidth.value !== STYLE_DEFAULTS.absoluteStrokeWidth
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -110,7 +110,7 @@ const customizingActive = computed(() => {
|
|||||||
name="size"
|
name="size"
|
||||||
v-model="size"
|
v-model="size"
|
||||||
:min="16"
|
:min="16"
|
||||||
:max="48"
|
:max="256"
|
||||||
:step="4"
|
:step="4"
|
||||||
/>
|
/>
|
||||||
</InputField>
|
</InputField>
|
||||||
@@ -120,8 +120,8 @@ const customizingActive = computed(() => {
|
|||||||
label="Absolute Stroke width"
|
label="Absolute Stroke width"
|
||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
id="size"
|
id="absolute-stroke-width"
|
||||||
name="size"
|
name="absolute-stroke-width"
|
||||||
v-model="absoluteStrokeWidth"
|
v-model="absoluteStrokeWidth"
|
||||||
/>
|
/>
|
||||||
</InputField>
|
</InputField>
|
||||||
@@ -161,9 +161,4 @@ const customizingActive = computed(() => {
|
|||||||
.color-picker {
|
.color-picker {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#absolute-stroke-width {
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
379
docs/.vitepress/theme/components/overrides/Hero.vue
Normal file
379
docs/.vitepress/theme/components/overrides/Hero.vue
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import VPHero from 'vitepress/dist/client/theme-default/components/VPHero.vue'
|
||||||
|
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||||
|
import FakeInput from '../base/FakeInput.vue';
|
||||||
|
import { useRouter } from 'vitepress';
|
||||||
|
import { data } from '../home/HomeHeroIconsCard.data'
|
||||||
|
import { useScroll } from '@vueuse/core';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
const { go } = useRouter()
|
||||||
|
|
||||||
|
const { frontmatter: fm } = useData()
|
||||||
|
const { x, y, isScrolling, arrivedState, directions } = useScroll(window)
|
||||||
|
|
||||||
|
const opacity = computed(() => {
|
||||||
|
if (y.value < 0) return 1
|
||||||
|
if (y.value > 300) return 0
|
||||||
|
return 1 - (y.value / 300)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VPHero v-if="fm.hero" class="VPHomeHero" :name="fm.hero.name" :text="fm.hero.text" :tagline="fm.hero.tagline"
|
||||||
|
:image="undefined" :actions="fm.hero.actions">
|
||||||
|
<template #home-hero-image></template>
|
||||||
|
<template #home-hero-actions-after>
|
||||||
|
<FakeInput @click="go('/icons/?focus')" class="search-box">
|
||||||
|
Search {{ data.iconsCount }} icons...
|
||||||
|
</FakeInput>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</VPHero>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-12 -12 48 48" fill="none" overflow="auto"
|
||||||
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="hero-background" :style="{ opacity: opacity }">
|
||||||
|
<g class="svg-preview-grid-group" stroke-linecap="butt" stroke-width="0.1" stroke="#777"
|
||||||
|
stroke-opacity="0.3">
|
||||||
|
<!-- <rect class="svg-preview-grid-rect" width="23.9" height="23.9" x="0.05" y="0.05" rx="1"></rect> -->
|
||||||
|
<path
|
||||||
|
stroke-dasharray="0 0.1 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0 0.15"
|
||||||
|
stroke-width="0.1"
|
||||||
|
d="M1 0.1v23.8M2 0.1v23.8M4 0.1v23.8M5 0.1v23.8M7 0.1v23.8M8 0.1v23.8M10 0.1v23.8M11 0.1v23.8M13 0.1v23.8M14 0.1v23.8M16 0.1v23.8M17 0.1v23.8M19 0.1v23.8M20 0.1v23.8M22 0.1v23.8M23 0.1v23.8M0.1 1h23.8M0.1 2h23.8M0.1 4h23.8M0.1 5h23.8M0.1 7h23.8M0.1 8h23.8M0.1 10h23.8M0.1 11h23.8M0.1 13h23.8M0.1 14h23.8M0.1 16h23.8M0.1 17h23.8M0.1 19h23.8M0.1 20h23.8M0.1 22h23.8M0.1 23h23.8">
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
d="M3 0.1v23.8M6 0.1v23.8M9 0.1v23.8M12 0.1v23.8M15 0.1v23.8M18 0.1v23.8M21 0.1v23.8M0.1 3h23.8M0.1 6h23.8M0.1 9h23.8M0.1 12h23.8M0.1 15h23.8M0.1 18h23.8M0.1 21h23.8">
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
<g class="svg-preview-shadow-mask-group" stroke-width="4" stroke="#777" stroke-opacity="0.15">
|
||||||
|
<mask id="svg-preview-shadow-mask-0" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
|
||||||
|
<path d="M4.9 16.1h.01M4.9 1.9h.01"></path>
|
||||||
|
</mask>
|
||||||
|
<mask id="svg-preview-shadow-mask-1" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
|
||||||
|
<path d="M7.8 4.7h.01M7 12.2h.01"></path>
|
||||||
|
</mask>
|
||||||
|
<mask id="svg-preview-shadow-mask-2" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
|
||||||
|
<path d="M12 7h.01M14 9h.01M12 11h.01M10 9h.01"></path>
|
||||||
|
</mask>
|
||||||
|
<mask id="svg-preview-shadow-mask-3" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
|
||||||
|
<path d="M16.2 4.8h.01M17 12.27h.01"></path>
|
||||||
|
</mask>
|
||||||
|
<mask id="svg-preview-shadow-mask-4" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
|
||||||
|
<path d="M19.1 1.9h.01M19.1 16h.01"></path>
|
||||||
|
</mask>
|
||||||
|
<mask id="svg-preview-shadow-mask-5" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
|
||||||
|
<path d="M9.5 18h.01M14.5 18h.01"></path>
|
||||||
|
</mask>
|
||||||
|
<mask id="svg-preview-shadow-mask-6" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
|
||||||
|
<path d="M8 22h.01M12 11h.01M16 22h.01"></path>
|
||||||
|
</mask>
|
||||||
|
</g>
|
||||||
|
<!-- <g class="svg-preview-shadow-group" stroke-width="4" stroke="#777" stroke-opacity="0.15">
|
||||||
|
<path mask="url(#svg-preview-shadow-mask-0)" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
|
||||||
|
<path mask="url(#svg-preview-shadow-mask-1)" d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2"></path>
|
||||||
|
<path mask="url(#svg-preview-shadow-mask-2)" d="M 12 7 A2 2 0 0 1 14 9"></path>
|
||||||
|
<path mask="url(#svg-preview-shadow-mask-2)" d="M 14 9 A2 2 0 0 1 12 11"></path>
|
||||||
|
<path mask="url(#svg-preview-shadow-mask-2)" d="M 12 11 A2 2 0 0 1 10 9"></path>
|
||||||
|
<path mask="url(#svg-preview-shadow-mask-2)" d="M 10 9 A2 2 0 0 1 12 7"></path>
|
||||||
|
<path mask="url(#svg-preview-shadow-mask-3)" d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27"></path>
|
||||||
|
<path mask="url(#svg-preview-shadow-mask-4)" d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16"></path>
|
||||||
|
<path mask="url(#svg-preview-shadow-mask-5)" d="M 9.5 18 L 14.5 18"></path>
|
||||||
|
<path mask="url(#svg-preview-shadow-mask-6)" d="M 8 22 L 12 11"></path>
|
||||||
|
<path mask="url(#svg-preview-shadow-mask-6)" d="M 12 11 L 16 22"></path>
|
||||||
|
<path
|
||||||
|
d="M4.9 16.1h.01M4.9 1.9h.01M7.8 4.7h.01M7 12.2h.01M12 7h.01M14 9h.01M12 11h.01M10 9h.01M16.2 4.8h.01M17 12.27h.01M19.1 1.9h.01M19.1 16h.01M9.5 18h.01M14.5 18h.01M8 22h.01M16 22h.01">
|
||||||
|
</path>
|
||||||
|
</g> -->
|
||||||
|
<g>
|
||||||
|
<defs xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<pattern id="backdrop-pattern-:R4:" width=".1" height=".1" patternUnits="userSpaceOnUse"
|
||||||
|
patternTransform="rotate(45 50 50)">
|
||||||
|
<line stroke="red" stroke-width="0.1" y2="1"></line>
|
||||||
|
<line stroke="red" stroke-width="0.1" y2="1"></line>
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
<g stroke-width="4">
|
||||||
|
<mask id="svg-preview-backdrop-mask-:R4:-0" maskUnits="userSpaceOnUse">
|
||||||
|
<path stroke="white" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
|
||||||
|
</mask>
|
||||||
|
<path d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
|
||||||
|
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-0)"></path>
|
||||||
|
</g>
|
||||||
|
<g stroke-width="4">
|
||||||
|
<mask id="svg-preview-backdrop-mask-:R4:-0" maskUnits="userSpaceOnUse">
|
||||||
|
<path stroke="white" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
|
||||||
|
</mask>
|
||||||
|
<path
|
||||||
|
d="M 12 7 A2 2 0 0 1 14 9 M 14 9 A2 2 0 0 1 12 11 M 12 11 A2 2 0 0 1 10 9 M 10 9 A2 2 0 0 1 12 7 M 8 22 L 12 11 M 12 11 L 16 22 M 9.5 18 L 14.5 18"
|
||||||
|
stroke="url(#backdrop-pattern-:R4:)" stroke-width="4" stroke-opacity="0.75"
|
||||||
|
mask="url(#svg-preview-backdrop-mask-:R4:-0)"></path>
|
||||||
|
</g>
|
||||||
|
<g stroke-width="4">
|
||||||
|
<mask id="svg-preview-backdrop-mask-:R4:-1" maskUnits="userSpaceOnUse">
|
||||||
|
<path stroke="white" d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2"></path>
|
||||||
|
</mask>
|
||||||
|
<path
|
||||||
|
d="M 12 7 A2 2 0 0 1 14 9 M 14 9 A2 2 0 0 1 12 11 M 12 11 A2 2 0 0 1 10 9 M 10 9 A2 2 0 0 1 12 7 M 8 22 L 12 11 M 12 11 L 16 22 M 9.5 18 L 14.5 18"
|
||||||
|
stroke="url(#backdrop-pattern-:R4:)" stroke-width="4" stroke-opacity="0.75"
|
||||||
|
mask="url(#svg-preview-backdrop-mask-:R4:-1)"></path>
|
||||||
|
</g>
|
||||||
|
<g stroke-width="4">
|
||||||
|
<mask id="svg-preview-backdrop-mask-:R4:-0" maskUnits="userSpaceOnUse">
|
||||||
|
<path stroke="white" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
|
||||||
|
</mask>
|
||||||
|
<path d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
|
||||||
|
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-0)"></path>
|
||||||
|
</g>
|
||||||
|
<g stroke-width="4">
|
||||||
|
<mask id="svg-preview-backdrop-mask-:R4:-1" maskUnits="userSpaceOnUse">
|
||||||
|
<path stroke="white" d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2"></path>
|
||||||
|
</mask>
|
||||||
|
<path d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
|
||||||
|
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-1)"></path>
|
||||||
|
</g>
|
||||||
|
<g stroke-width="4">
|
||||||
|
<mask id="svg-preview-backdrop-mask-:R4:-2" maskUnits="userSpaceOnUse">
|
||||||
|
<path stroke="white"
|
||||||
|
d="M 12 7 A2 2 0 0 1 14 9 M 14 9 A2 2 0 0 1 12 11 M 12 11 A2 2 0 0 1 10 9 M 10 9 A2 2 0 0 1 12 7 M 8 22 L 12 11 M 12 11 L 16 22 M 9.5 18 L 14.5 18">
|
||||||
|
</path>
|
||||||
|
</mask>
|
||||||
|
<path d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
|
||||||
|
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-2)"></path>
|
||||||
|
</g>
|
||||||
|
<g stroke-width="4">
|
||||||
|
<mask id="svg-preview-backdrop-mask-:R4:-0" maskUnits="userSpaceOnUse">
|
||||||
|
<path stroke="white" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
|
||||||
|
</mask>
|
||||||
|
<path d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
|
||||||
|
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-0)"></path>
|
||||||
|
</g>
|
||||||
|
<g stroke-width="4">
|
||||||
|
<mask id="svg-preview-backdrop-mask-:R4:-1" maskUnits="userSpaceOnUse">
|
||||||
|
<path stroke="white" d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2"></path>
|
||||||
|
</mask>
|
||||||
|
<path d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
|
||||||
|
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-1)"></path>
|
||||||
|
</g>
|
||||||
|
<g stroke-width="4">
|
||||||
|
<mask id="svg-preview-backdrop-mask-:R4:-2" maskUnits="userSpaceOnUse">
|
||||||
|
<path stroke="white"
|
||||||
|
d="M 12 7 A2 2 0 0 1 14 9 M 14 9 A2 2 0 0 1 12 11 M 12 11 A2 2 0 0 1 10 9 M 10 9 A2 2 0 0 1 12 7 M 8 22 L 12 11 M 12 11 L 16 22 M 9.5 18 L 14.5 18">
|
||||||
|
</path>
|
||||||
|
</mask>
|
||||||
|
<path d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
|
||||||
|
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-2)"></path>
|
||||||
|
</g>
|
||||||
|
<g stroke-width="4">
|
||||||
|
<mask id="svg-preview-backdrop-mask-:R4:-3" maskUnits="userSpaceOnUse">
|
||||||
|
<path stroke="white" d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27"></path>
|
||||||
|
</mask>
|
||||||
|
<path d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
|
||||||
|
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-3)"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g class="svg-preview-handles-group" stroke-width="0.12" stroke="#777" stroke-opacity="0.6">
|
||||||
|
<path d="M4.9 16.1 1 12.2"></path>
|
||||||
|
<circle cy="12.2" cx="1" r="0.25"></circle>
|
||||||
|
<path d="M4.9 1.9 1 5.8"></path>
|
||||||
|
<circle cy="5.8" cx="1" r="0.25"></circle>
|
||||||
|
<path d="M16.2 4.8 18.2 6.8"></path>
|
||||||
|
<circle cy="6.8" cx="18.2" r="0.25"></circle>
|
||||||
|
<path d="M17 12.27 18.46 9.91"></path>
|
||||||
|
<circle cy="9.91" cx="18.46" r="0.25"></circle>
|
||||||
|
</g>
|
||||||
|
<g class="svg-preview-colored-path-group">
|
||||||
|
<path d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9" stroke="##dfdfd6"></path>
|
||||||
|
<path d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2" stroke="##dfdfd6"></path>
|
||||||
|
<path d="M 12 7 A2 2 0 0 1 14 9" stroke="##dfdfd6"></path>
|
||||||
|
<path d="M 14 9 A2 2 0 0 1 12 11" stroke="##dfdfd6"></path>
|
||||||
|
<path d="M 12 11 A2 2 0 0 1 10 9" stroke="##dfdfd6"></path>
|
||||||
|
<path d="M 10 9 A2 2 0 0 1 12 7" stroke="##dfdfd6"></path>
|
||||||
|
<path d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27" stroke="##dfdfd6"></path>
|
||||||
|
<path d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16" stroke="##dfdfd6"></path>
|
||||||
|
<path d="M 9.5 18 L 14.5 18" stroke="##dfdfd6"></path>
|
||||||
|
<path d="M 8 22 L 12 11" stroke="##dfdfd6"></path>
|
||||||
|
<path d="M 12 11 L 16 22" stroke="##dfdfd6"></path>
|
||||||
|
</g>
|
||||||
|
<g class="svg-preview-radii-group" stroke-width="0.12" stroke-dasharray="0 0.25 0.25" stroke="#777"
|
||||||
|
stroke-opacity="0.3">
|
||||||
|
<circle cx="9.518750780437157" cy="16.261333416579962" r="0.25"></circle>
|
||||||
|
<circle cx="11.118750780437157" cy="1.261333416579964" r="0.25"></circle>
|
||||||
|
<path
|
||||||
|
d="M9.518750780437157 16.261333416579962L4.481249219562843 8.138666583420036L11.118750780437157 1.261333416579964">
|
||||||
|
</path>
|
||||||
|
<circle cx="4.481249219562843" cy="8.138666583420036" r="0.25"></circle>
|
||||||
|
<path d="M7 12.2L12.217985863765243 8.963918492134958L7.8 4.7"></path>
|
||||||
|
<circle cy="8.963918492134958" cx="12.217985863765243" r="0.25" stroke-dasharray="0" stroke="red"></circle>
|
||||||
|
<circle cy="8.963918492134958" cx="12.217985863765243" r="6.14" stroke="red"></circle>
|
||||||
|
<circle cy="9" cx="12" r="0.25" stroke-dasharray="0"></circle>
|
||||||
|
<circle cy="9" cx="12" r="2"></circle>
|
||||||
|
<circle cy="9" cx="12" r="0.25" stroke-dasharray="0"></circle>
|
||||||
|
<circle cy="9" cx="12" r="2"></circle>
|
||||||
|
<circle cy="9" cx="12" r="0.25" stroke-dasharray="0"></circle>
|
||||||
|
<circle cy="9" cx="12" r="2"></circle>
|
||||||
|
<circle cy="9" cx="12" r="0.25" stroke-dasharray="0"></circle>
|
||||||
|
<circle cy="9" cx="12" r="2"></circle>
|
||||||
|
<circle cx="12.035530040810755" cy="23.05" r="0.25"></circle>
|
||||||
|
<circle cx="12.035530040810755" cy="-5.1499999999999995" r="0.25"></circle>
|
||||||
|
<path d="M12.035530040810755 23.05L26.164469959189248 8.95L12.035530040810755 -5.1499999999999995"></path>
|
||||||
|
<circle cx="26.164469959189248" cy="8.95" r="0.25"></circle>
|
||||||
|
<path d="M19.1 16L12.064440320770494 8.95L19.1 1.9"></path>
|
||||||
|
<circle cy="8.95" cx="12.064440320770494" r="0.25" stroke-dasharray="0" stroke="red"></circle>
|
||||||
|
<circle cy="8.95" cx="12.064440320770494" r="9.96" stroke="red"></circle>
|
||||||
|
</g>
|
||||||
|
<g class="svg-preview-control-path-marker-mask-group" stroke-width="1" stroke="#000">
|
||||||
|
<mask id="svg-preview-control-path-marker-mask-0" maskUnits="userSpaceOnUse">
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
|
||||||
|
<path d="M4.9 16.1h.01"></path>
|
||||||
|
<path d="M4.9 1.9h.01"></path>
|
||||||
|
</mask>
|
||||||
|
<mask id="svg-preview-control-path-marker-mask-1" maskUnits="userSpaceOnUse">
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
|
||||||
|
<path d="M7.8 4.7h.01"></path>
|
||||||
|
<path d="M7 12.2h.01"></path>
|
||||||
|
</mask>
|
||||||
|
<mask id="svg-preview-control-path-marker-mask-6" maskUnits="userSpaceOnUse">
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
|
||||||
|
<path d="M16.2 4.8h.01"></path>
|
||||||
|
<path d="M17 12.27h.01"></path>
|
||||||
|
</mask>
|
||||||
|
<mask id="svg-preview-control-path-marker-mask-7" maskUnits="userSpaceOnUse">
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
|
||||||
|
<path d="M19.1 1.9h.01"></path>
|
||||||
|
<path d="M19.1 16h.01"></path>
|
||||||
|
</mask>
|
||||||
|
<mask id="svg-preview-control-path-marker-mask-8" maskUnits="userSpaceOnUse">
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
|
||||||
|
<path d="M9.5 18h.01"></path>
|
||||||
|
<path d="M14.5 18h.01"></path>
|
||||||
|
</mask>
|
||||||
|
<mask id="svg-preview-control-path-marker-mask-9" maskUnits="userSpaceOnUse">
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
|
||||||
|
<path d="M8 22h.01"></path>
|
||||||
|
<path d="M12 11h.01"></path>
|
||||||
|
</mask>
|
||||||
|
<mask id="svg-preview-control-path-marker-mask-10" maskUnits="userSpaceOnUse">
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
|
||||||
|
<path d="M12 11h.01"></path>
|
||||||
|
<path d="M16 22h.01"></path>
|
||||||
|
</mask>
|
||||||
|
</g>
|
||||||
|
<g class="svg-preview-control-path-group" stroke="#fff" stroke-width="0.125">
|
||||||
|
<path mask="url(#svg-preview-control-path-marker-mask-0)" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
|
||||||
|
<path mask="url(#svg-preview-control-path-marker-mask-1)" d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2"></path>
|
||||||
|
<path d="M 12 7 A2 2 0 0 1 14 9"></path>
|
||||||
|
<path d="M 14 9 A2 2 0 0 1 12 11"></path>
|
||||||
|
<path d="M 12 11 A2 2 0 0 1 10 9"></path>
|
||||||
|
<path d="M 10 9 A2 2 0 0 1 12 7"></path>
|
||||||
|
<path mask="url(#svg-preview-control-path-marker-mask-6)" d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27"></path>
|
||||||
|
<path mask="url(#svg-preview-control-path-marker-mask-7)" d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16"></path>
|
||||||
|
<path mask="url(#svg-preview-control-path-marker-mask-8)" d="M 9.5 18 L 14.5 18"></path>
|
||||||
|
<path mask="url(#svg-preview-control-path-marker-mask-9)" d="M 8 22 L 12 11"></path>
|
||||||
|
<path mask="url(#svg-preview-control-path-marker-mask-10)" d="M 12 11 L 16 22"></path>
|
||||||
|
</g>
|
||||||
|
<g class="svg-preview-control-path-marker-group" stroke="#fff" stroke-width="0.125">
|
||||||
|
<path
|
||||||
|
d="M4.9 16.1h.01M4.9 1.9h.01M7.8 4.7h.01M7 12.2h.01M16.2 4.8h.01M17 12.27h.01M19.1 1.9h.01M19.1 16h.01M9.5 18h.01M14.5 18h.01M8 22h.01M12 11h.01M12 11h.01M16 22h.01">
|
||||||
|
</path>
|
||||||
|
<circle cx="4.9" cy="16.1" r="0.5"></circle>
|
||||||
|
<circle cx="4.9" cy="1.9" r="0.5"></circle>
|
||||||
|
<circle cx="7.8" cy="4.7" r="0.5"></circle>
|
||||||
|
<circle cx="7" cy="12.2" r="0.5"></circle>
|
||||||
|
<circle cx="16.2" cy="4.8" r="0.5"></circle>
|
||||||
|
<circle cx="17" cy="12.27" r="0.5"></circle>
|
||||||
|
<circle cx="19.1" cy="1.9" r="0.5"></circle>
|
||||||
|
<circle cx="19.1" cy="16" r="0.5"></circle>
|
||||||
|
<circle cx="9.5" cy="18" r="0.5"></circle>
|
||||||
|
<circle cx="14.5" cy="18" r="0.5"></circle>
|
||||||
|
<circle cx="8" cy="22" r="0.5"></circle>
|
||||||
|
<circle cx="16" cy="22" r="0.5"></circle>
|
||||||
|
</g>
|
||||||
|
<g class="svg-preview-handles-group" stroke-width="0.12" style="stroke: var(--vp-c-brand)" stroke-opacity="0.3">
|
||||||
|
<path d="M4.9 16.1 1 12.2"></path>
|
||||||
|
<circle cy="12.2" cx="1" r="0.25"></circle>
|
||||||
|
<path d="M4.9 1.9 1 5.8"></path>
|
||||||
|
<circle cy="5.8" cx="1" r="0.25"></circle>
|
||||||
|
<path d="M16.2 4.8 18.2 6.8"></path>
|
||||||
|
<circle cy="6.8" cx="18.2" r="0.25"></circle>
|
||||||
|
<path d="M17 12.27 18.46 9.91"></path>
|
||||||
|
<circle cy="9.91" cx="18.46" r="0.25"></circle>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.hero {
|
||||||
|
overflow: hidden;
|
||||||
|
height: 60vh;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
/* align-items: center;
|
||||||
|
justify-content: center; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-background {
|
||||||
|
transform: rotateX(-51deg) rotateZ(-43deg);
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
position: fixed;
|
||||||
|
top: -240px;
|
||||||
|
right: -320px;
|
||||||
|
width: 112vw;
|
||||||
|
height: 112vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 3.2rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .VPHomeHero .image {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPHomeHero .container {
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPHomeHero .main {
|
||||||
|
text-align: center;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* .VPHomeHero .container .actions {
|
||||||
|
justify-content: center;
|
||||||
|
} */
|
||||||
|
/*
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.VPHomeHero :deep(.actions) {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
|
@media screen and (prefers-color-scheme: light) {
|
||||||
|
.svg-preview-grid-rect { fill: none }
|
||||||
|
}
|
||||||
|
@media screen and (prefers-color-scheme: dark) {
|
||||||
|
.svg-preview-grid-rect { fill: none }
|
||||||
|
.svg
|
||||||
|
.svg-preview-grid-group,
|
||||||
|
.svg-preview-radii-group,
|
||||||
|
.svg-preview-shadow-mask-group,
|
||||||
|
.svg-preview-shadow-group {
|
||||||
|
stroke: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
/* width: calc(100vw - 272px); */
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
248
docs/.vitepress/theme/components/overrides/HeroMatrix.vue
Normal file
248
docs/.vitepress/theme/components/overrides/HeroMatrix.vue
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onBeforeUnmount, type Component } from 'vue';
|
||||||
|
import {
|
||||||
|
heart,
|
||||||
|
star,
|
||||||
|
zap,
|
||||||
|
code,
|
||||||
|
feather,
|
||||||
|
cloud,
|
||||||
|
sun,
|
||||||
|
moon,
|
||||||
|
camera,
|
||||||
|
music,
|
||||||
|
video,
|
||||||
|
globe,
|
||||||
|
layers,
|
||||||
|
package as packageIcon,
|
||||||
|
compass,
|
||||||
|
command,
|
||||||
|
terminal,
|
||||||
|
database,
|
||||||
|
server,
|
||||||
|
cpu,
|
||||||
|
lock,
|
||||||
|
key,
|
||||||
|
shield,
|
||||||
|
wifi,
|
||||||
|
download,
|
||||||
|
upload,
|
||||||
|
search,
|
||||||
|
settings,
|
||||||
|
users,
|
||||||
|
mail,
|
||||||
|
bell,
|
||||||
|
calendar,
|
||||||
|
clock,
|
||||||
|
gitBranch,
|
||||||
|
funnel,
|
||||||
|
bookmark,
|
||||||
|
tag,
|
||||||
|
sparkles,
|
||||||
|
} from '../../../data/iconNodes';
|
||||||
|
import createElement from 'lucide/src/createElement';
|
||||||
|
import { IconNode } from 'lucide';
|
||||||
|
|
||||||
|
const icons: Component[] = [
|
||||||
|
heart, star, zap, code, feather, cloud, sun, moon, camera, music,
|
||||||
|
video, globe, layers, packageIcon, compass, command, terminal, database,
|
||||||
|
server, cpu, lock, key, shield, wifi, download, upload, search,
|
||||||
|
settings, users, mail, bell, calendar, clock, gitBranch, funnel,
|
||||||
|
bookmark, tag, sparkles,
|
||||||
|
];
|
||||||
|
|
||||||
|
const svgs = icons.map(icon => {
|
||||||
|
const element = createElement(icon as IconNode, {
|
||||||
|
stroke: 'white',
|
||||||
|
opacity: '0.2',
|
||||||
|
})
|
||||||
|
|
||||||
|
return element.outerHTML;
|
||||||
|
});
|
||||||
|
|
||||||
|
const highlightedSvgs = icons.map(icon => {
|
||||||
|
const element = createElement(icon as IconNode, {
|
||||||
|
stroke: 'white',
|
||||||
|
})
|
||||||
|
|
||||||
|
return element.outerHTML;
|
||||||
|
});
|
||||||
|
|
||||||
|
const images = svgs.map(svg => {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = `data:image/svg+xml;base64,${btoa(svg)}`;
|
||||||
|
return img;
|
||||||
|
});
|
||||||
|
|
||||||
|
const highlightedImages = highlightedSvgs.map(svg => {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = `data:image/svg+xml;base64,${btoa(svg)}`;
|
||||||
|
return img;
|
||||||
|
});
|
||||||
|
|
||||||
|
const canvas = ref<HTMLCanvasElement | null>(null);
|
||||||
|
|
||||||
|
// Setting up the columns
|
||||||
|
const fontSize = 16;
|
||||||
|
const gap = 40;
|
||||||
|
const gapY = 8;
|
||||||
|
const intervalTime = ref<number | null>(null);
|
||||||
|
|
||||||
|
// Store individual drops with their positions
|
||||||
|
let individualDrops: Array<{x: number, y: number, active: boolean}> = [];
|
||||||
|
|
||||||
|
// Random integer between min and max
|
||||||
|
function randInt(min, max) {
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (!canvas.value) return;
|
||||||
|
|
||||||
|
const ctx = canvas.value.getContext('2d');
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
const width = window.innerWidth;
|
||||||
|
const height = window.innerHeight * 0.6;
|
||||||
|
canvas.value.width = width;
|
||||||
|
canvas.value.height = height;
|
||||||
|
|
||||||
|
let columns = Math.floor(width / 120);
|
||||||
|
|
||||||
|
var rows = Math.floor(canvas.value.height / fontSize);
|
||||||
|
var midStart = Math.floor(rows * 0.25);
|
||||||
|
var midEnd = Math.floor(rows * 0.85);
|
||||||
|
|
||||||
|
let drops = Array.from({ length: columns }, () => randInt(midStart, midEnd));
|
||||||
|
let fps, fpsInterval, startTime, now, then, elapsed;
|
||||||
|
|
||||||
|
// Add click event listener
|
||||||
|
function handleCanvasClick(event: MouseEvent) {
|
||||||
|
const rect = canvas.value?.getBoundingClientRect();
|
||||||
|
if (!rect) return;
|
||||||
|
|
||||||
|
const x = event.clientX - rect.left;
|
||||||
|
const y = event.clientY - rect.top;
|
||||||
|
|
||||||
|
individualDrops.push({
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
active: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.value.addEventListener('click', handleCanvasClick);
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
ctx.fillStyle = 'rgba(27, 27, 31, 0.50)';
|
||||||
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
|
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
|
||||||
|
|
||||||
|
// Draw regular drops
|
||||||
|
for (var i = drops.length; i > 0; i--) {
|
||||||
|
var img = images[Math.floor(Math.random() * images.length)];
|
||||||
|
ctx.drawImage(img, i * fontSize * 2 + gap, drops[i] * fontSize, (fontSize / 2) + gapY, (fontSize / 2) + gapY);
|
||||||
|
|
||||||
|
drops[i]++;
|
||||||
|
if (Math.random() > .98) {
|
||||||
|
drops[i] = randInt(midStart, midEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw and update individual drops
|
||||||
|
individualDrops = individualDrops.filter(drop => {
|
||||||
|
if (!drop.active) return false;
|
||||||
|
|
||||||
|
var img = highlightedImages[Math.floor(Math.random() * images.length)];
|
||||||
|
ctx.drawImage(img, drop.x - fontSize/2, drop.y, fontSize, fontSize);
|
||||||
|
|
||||||
|
drop.y += fontSize
|
||||||
|
|
||||||
|
// Remove if off screen
|
||||||
|
if (drop.y > height) {
|
||||||
|
drop.active = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startAnimating(fps) {
|
||||||
|
fpsInterval = 1000 / fps;
|
||||||
|
then = Date.now();
|
||||||
|
startTime = then;
|
||||||
|
animate();
|
||||||
|
}
|
||||||
|
function animate() {
|
||||||
|
|
||||||
|
// request another frame
|
||||||
|
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
|
||||||
|
// calc elapsed time since last loop
|
||||||
|
|
||||||
|
now = Date.now();
|
||||||
|
elapsed = now - then;
|
||||||
|
|
||||||
|
// if enough time has elapsed, draw the next frame
|
||||||
|
|
||||||
|
if (elapsed > fpsInterval) {
|
||||||
|
|
||||||
|
// Get ready for next frame by setting then=now, but also adjust for your
|
||||||
|
// specified fpsInterval not being a multiple of RAF's interval (16.7ms)
|
||||||
|
then = now - (elapsed % fpsInterval);
|
||||||
|
|
||||||
|
// Put your drawing code here
|
||||||
|
draw();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startAnimating(12);
|
||||||
|
|
||||||
|
window.addEventListener('resize', function() {
|
||||||
|
canvas.value.width = window.innerWidth;
|
||||||
|
canvas.value.height = window.innerHeight;
|
||||||
|
rows = Math.floor(canvas.value.height / fontSize);
|
||||||
|
columns = Math.floor(canvas.value.width / fontSize);
|
||||||
|
|
||||||
|
var oldDrops = drops.slice();
|
||||||
|
drops = [];
|
||||||
|
for (var i = 0; i < columns; i++) {
|
||||||
|
drops[i] = oldDrops[i % oldDrops.length] ?? randInt(midStart, midEnd);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cleanup event listener
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
canvas.value?.removeEventListener('click', handleCanvasClick);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="hero">
|
||||||
|
<canvas ref="canvas" class="hero-canvas" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.hero {
|
||||||
|
overflow: hidden;
|
||||||
|
height: 60vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-canvas {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 60vh;
|
||||||
|
filter: blur(0px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user