Compare commits

..

87 Commits

Author SHA1 Message Date
Karsa
78182c3573 fix(icons): updates crown to look more Lucide-y (#1947)
Co-authored-by: Karsa <karsa@sztaki.hu>
2024-03-11 14:59:06 +01:00
Eric Fennis
02f8f5a1c1 Update sandpack (#1968) 2024-03-11 14:21:03 +01:00
Jakob Guddas
16ee591f49 fix(icons): optimized hotel icon (#1951)
* Updated icons/hotel.svg

* Updated icons/hotel.json
2024-03-09 15:21:39 +01:00
Han Yeong-woo
5378156833 fix(lucide-solid): use jsx for exports too (#1970) 2024-03-09 14:11:25 +01:00
Karsa
7b76078792 feat(icons): added ham icon (#1949)
Co-authored-by: Karsa <karsa@sztaki.hu>
2024-03-09 10:42:01 +01:00
Karsa
e2af1af4f9 fix(packages/lucide-solid): use jsx extension for solid build (#1964)
Co-authored-by: Karsa <karsa@sztaki.hu>
2024-03-09 10:41:06 +01:00
Jakob Guddas
c9513d0bf4 Update 02_bug_report.yml (#1967) 2024-03-09 10:40:22 +01:00
Karsa
6fbd5ee06a chore(icons): update icon names to match naming guidelines no. 7 to 9 (#1906)
* Update icon names to match naming guidelines no. 7 to 9
Update and extend rename icon helper script.

* Ran prettier

* Refactor rename scripts so that simplegit import doesn't cause other helper script usages to crash.

* Revert renaming key-square

* Revert renaming message-circle|square

* Fix square-dashed-kanban

* Optimize circle power

* pnpm rename square-dashed-bottom-scissors square-bottom-dashed-scissors

* Fix linting

* Fix file name linting issues via cspell configuration

* Rename unlock => lock-open

* Rename (square|circle)-check-2 => (square|circle)-check-small

* pnpm rename code-2 code-xml
pnpm rename contact-2 contact-round

* rename test-tube-2 test-tube-diagonal
rename mic-2 mic-vocal
rename loader-2 loader-circle
rename test-tube-2 test-tube-diagonal
rename school-2 university

* fix icons linting

* rename ice-cream => ice-cream-cone
rename ice-cream-2 => ice-cream-bowl
rename laptop-2 => laptop-abstract

* renamePattern "^(square|circle)-arrow-([a-z-]+)-from$" "\\1-arrow-out-\\2"

* rename wand-2 => wand-sparkles

* Update university.json tags.

* fix(icons): renamePattern '^(.+)-abstract$' '\1-minimal' --add-alias=false
feat(scripts): added yargs option parsing to renamePattern

* fix(packages): Remove protractor and puppeteer from lucide-angular, they are no longer in use but cause pnpm install to fail

* Lint fix, this is starting to get ridiculous.

* fix(packages): fix lucide-angular karma config.

* chore(icons): renamePattern '^(square|circle)-check$' '\1-check-big'
chore(icons): renamePattern '^(square|circle)-check-small$' '\1-check'

---------

Co-authored-by: Karsa <karsa@sztaki.hu>
2024-03-08 16:55:46 +01:00
Karsa
d8773827fb hotfix(icons): remove circle-ellipsis alias from circle-ellipsis (#1961) 2024-03-08 16:46:56 +01:00
Daniel Bayley
bc1843f767 chore(icons): sort out more/ellipsis… icons/metadata (#1459)
* Add `more-horizontal-rectangle` icon

* Refine `circle-ellipsis` icon

* Rename `circle-ellipsis` to `more-circle`

* Improve `more` icons metadata

* Revert "Refine `circle-ellipsis` icon"

This reverts commit 973db1e140.

* Revert "Add `more-horizontal-rectangle` icon"

This reverts commit 3960c6ee0c.

* Rename `more-circle` to `more-horizontal-circle`

* Rename `form-input` to `more-horizontal-rectangle`

* Improve metadata

* Rename `more-horizontal-circle` to `ellipsis-circle`

* Rename `more-horizontal-rectangle` to `ellipsis-rectangle`

* Rename `more-horizontal` to `ellipsis`

* Rename `more-vertical` to `ellipsis-vertical`

* Fix lint issues

* Rename vertical ellipsis

* formatting

* Rename circle-ellipsis

* Rename rectangle ellipsis

* Revert "Rename vertical ellipsis"

This reverts commit acb80b75f8.

* Add EOFs

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-03-08 15:32:35 +01:00
Karsa
07aefe3b88 feat(icons): add briefcase-medical & briefcase-business (#1954)
* feat(icons): add briefcase-medical & briefcase-business

* chore(icons): add extra tag to briefcase-business.json

---------

Co-authored-by: Karsa <karsa@sztaki.hu>
2024-03-08 15:28:45 +01:00
Eric Fennis
76d449a974 Aliases redirect on site (#1959)
* test aliases

* test

* Add redirect routes

* Formatting
2024-03-08 15:20:15 +01:00
Karsa
dd39fa328c Update cannabis.json (#1946) 2024-03-07 10:40:20 +01:00
Bernardo Ferrari
0cf568c38b fix(icons): play icon uncentered (#1834)
* Fix play.svg

* Update play.svg

---------

Co-authored-by: Karsa <contact@karsa.org>
2024-03-07 08:21:30 +01:00
Jakob Guddas
a8b8dfc58e fix(icons): optimized and closed gaps in file-pie-chart icon (#1939)
* Updated icons/file-pie-chart.svg

* Updated icons/file-pie-chart.json

* Updated icons/file-pie-chart.svg

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-03-06 22:16:52 +01:00
Jakob Guddas
41dde6b574 fix(icons): added round corner to file-volume icon (#1940)
* Updated icons/file-volume.svg

* Updated icons/file-volume.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-03-06 22:16:47 +01:00
Jakob Guddas
ae35ca256c fix(icons): added round corner to file-search icon (#1941)
* Updated icons/file-search.svg

* Updated icons/file-search.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-03-06 22:16:41 +01:00
Eric Fennis
38142f24a5 Update metadata (#1943) 2024-03-06 22:16:23 +01:00
Eric Fennis
b16f70993a workflow: Update stale workflow 2024-03-06 21:39:39 +01:00
Eric Fennis
ad1accb2e3 refactor: Adds repo shared package @lucide/shared (#1904)
* Fixed import of toKebabCase helper function

* Added utils package

* utils

* Make utils package work in build

* Add lucide-shared

* Transpile solid with esbuild

* Fix resolve modules

* Cleanup

* Format files

* Fix properties plugins function

* Fix properties plugins in lucide package

* Revert remove resolve plugin and cleanup

* Update snapshots

* Revert icon changes

---------

Co-authored-by: Rohan <rohancrrm@gmail.com>
2024-03-06 21:03:12 +01:00
Jakob Guddas
d255c6ac4e fix: readded gap between x-ray preview comment icons. (#1915)
* fix: readded gap between x-ray preview comment icons.

* feat: keep size 400px

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-03-06 21:00:26 +01:00
Jakob Guddas
d67ef7b0ca feat(icons): add cannabis icon (#1901)
* Added icons/cannabis.svg

* Added icons/cannabis.json

* Updated icons/cannabis.svg

* Updated icons/cannabis.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-03-06 20:59:25 +01:00
Karsa
198ccb8430 fix(icons): increased rounding of zap icons (#1936)
* fix(icons): add more Lucide-ish rounding to zap icons

* chore(icons): optimise zap-off manually

* chore(icons): fix linting

* Update icons/zap-off.svg

Co-authored-by: Jakob Guddas <github@jguddas.de>

* Update icons/zap.svg

Co-authored-by: Jakob Guddas <github@jguddas.de>

---------

Co-authored-by: Karsa <karsa@sztaki.hu>
Co-authored-by: Jakob Guddas <github@jguddas.de>
2024-03-06 20:58:17 +01:00
Eric Fennis
ce1b5bdefa style: Fix formatting 2024-03-06 20:54:55 +01:00
Daniel Bayley
45aa928369 Optimise/refine radio[-tower]/add airdrop/Apple/mac/iOS/app related icons (#1514)
* Add `airdrop` icon

* Optimise `radio` icon

* Refine `radio-tower` icon

* Add `airplay` icon

* Add `dock` (desktop) icon

* Improve metadata

* Optimise `podcast` icon

* Add `app-window-mac` icon

* Update icons/radio.svg

Co-authored-by: Karsa <contact@karsa.org>

* Update icons/radio-tower.svg

Co-authored-by: Karsa <contact@karsa.org>

* Update icons/podcast.svg

Co-authored-by: Karsa <contact@karsa.org>

* Update icons/radio-tower.svg

Co-authored-by: Karsa <contact@karsa.org>

* Update icons/radio.svg

Co-authored-by: Karsa <contact@karsa.org>

* Revert "Add `airdrop` icon"

This reverts commit af60a17784.

* Revert radio and radio-tower changes

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Karsa <contact@karsa.org>
2024-03-06 20:29:21 +01:00
Daniel Bayley
93dc356fa1 Add rotate/resize element icons (#1516)
* Add `proportions` icon

* Improve metadata

* Add `rotate-cw-square` icon

* Add `rotate-ccw-square` icon

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-03-06 20:13:53 +01:00
Jakob Guddas
a0aa132682 Updated icons/keyboard.svg (#1930)
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-03-01 11:40:56 +01:00
Karsa
1a4dd64862 feat(icons): redesign brain and related icons (#1902)
* Update brain and related icons

* Update central line and increase in size

---------

Co-authored-by: Karsa <karsa@sztaki.hu>
2024-02-28 16:53:48 +01:00
Jakob Guddas
3481f70ad7 feat: added 16x16 sub grid to svg preview component (#1914)
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-02-28 16:52:12 +01:00
Daniel Bayley
024f21e896 Add bot-message-square (chatbot) icon (#1709) 2024-02-28 16:35:25 +01:00
Jakob Guddas
c30ccd472e fix(icons): update goal icon (#1924)
* Updated icons/goal.svg

* Updated icons/goal.json
2024-02-27 10:17:59 +01:00
Wojciech Maj
28ec03ebc8 Add support for react-native-svg ^15.0.0 (#1929) 2024-02-27 10:13:42 +01:00
Eric Fennis
ab3a31367a feat(site): adds virtual scrolling to icons grid and categories (#1911)
* Add virtual scrolling icons page

* improve scrolling

* Small fixes in the routing

* Cleanup

* Fix routing issue

* Remove log

* Cleanup

* Cleanup

* Show search results in sidebar

* Improve alphabetical list

* Add whitespace

* Extract logic into a composable and fix icon selection

* Formatting
2024-02-27 10:09:52 +01:00
Karsa
148bae88d8 feat(icons): added globe-lock icon (#1587)
* Added globe-lock icon

* feat(icons): Added globe-lock, renamed globe-2 to earth, and added earth-lock

* feat(icons): Added globe as a tag to earth

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Karsa <karsa@sztaki.hu>
2024-02-26 09:53:04 +01:00
Chaoying
ca4a38ba85 Fix import statements (#1922) 2024-02-25 19:04:23 +01:00
Karsa
f297765a13 Update shield to have better centre of gravity (#1905)
Co-authored-by: Karsa <karsa@sztaki.hu>
2024-02-25 11:28:31 +01:00
Karsa
4657ccbfff fix(icons/concierge-bell): Adds missing rounding from bottom part (#1923)
Co-authored-by: Karsa <karsa@sztaki.hu>
2024-02-25 10:27:22 +01:00
Jakob Guddas
db222d4591 Optimized and fixed alignment issues in package-open icon (#1648)
* Optimized and fixed alignment issues in `package-open` icon

* Update icons/package-open.svg

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-02-24 18:31:04 +01:00
Jakob Guddas
bafad1c625 Add image-up icon (#1909)
* Added icons/image-up.svg

* Added icons/image-up.json
2024-02-23 11:25:17 +01:00
Jakob Guddas
7f1e95c6e7 Update image-down icon (#1910)
* Updated icons/image-down.svg

* Updated icons/image-down.json
2024-02-23 11:20:42 +01:00
Jakob Guddas
ca7a87112c Fixed gap issues in flower icon (#1650)
* Fixed gap issues in `flower` icon

* Update flower.json

* Update flower.json

* Update icons/flower.svg

Co-authored-by: Karsa <contact@karsa.org>

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Karsa <contact@karsa.org>
2024-02-23 09:58:13 +01:00
Kyle Angelo Galendez
d542da0a1c Add table-cells-merge, table-cells-split, table-rows-split, and table-columns-split icons (#1856)
* Add split-table icon

* Add split-table.json

* Add split-rows icon

* Add split-rows.json

* Add merge-rows icon

* Add merge-rows.json

* Update icons/merge-rows.svg

Co-authored-by: Karsa <contact@karsa.org>

* Update split-rows.svg

* Update split-table.svg

* Fix by add padding to split-table icon

- Accidentally exported the whole icon from Inkscape, but not the entire page, which creates no padding

* Update split-table icon

- Add 1px to the row borders down below

* Rename split-table.svg to table-row-split.svg

* Update and rename split-table.json to table-row-split.json

* Rename merge-rows to table-cells-merge

* Rename split-rows to table-cells-split

* Rename table-row-split to table-split

* Add `table-columns-split` icon

* Add table-columns-split.json

* Delete table-split icon and json

* Add `table-rows-split` json and icon

* Update icons/table-cells-split.json

Co-authored-by: Karsa <contact@karsa.org>

* Update icons/table-rows-split.json

Co-authored-by: Karsa <contact@karsa.org>

* Update icons/table-cells-merge.json

Co-authored-by: Karsa <contact@karsa.org>

* Update icons/table-columns-split.json

Co-authored-by: Karsa <contact@karsa.org>

---------

Co-authored-by: Karsa <contact@karsa.org>
2024-02-21 22:47:20 +01:00
Karsa
6fa51d2a22 Optimise hop (#1897)
Co-authored-by: Karsa <karsa@sztaki.hu>
2024-02-20 15:32:43 +01:00
Karsa
ca53b06af2 Adds pickaxe icon (#1890)
* Adds pickaxe icon

* Replace missing icon, haha 😅

---------

Co-authored-by: Karsa <karsa@sztaki.hu>
2024-02-19 09:01:32 +01:00
Karsa
b5dc96d2c2 Optimise hammer (#1893)
* Optimize and fix guideline violation in hammer

* Further adjustments to pixel perfection

* Nudge things around a bit more

* Elongate front part of head

---------

Co-authored-by: Karsa <karsa@sztaki.hu>
2024-02-19 09:00:51 +01:00
Karsa
5e5fe0085f Update tractor.svg 2024-02-19 08:12:47 +01:00
Karsa
0c2a8d774f Fix anvil rounding (#1895)
* Fix anvil rounding

* Add rounding to spike

---------

Co-authored-by: Karsa <karsa@sztaki.hu>
2024-02-19 08:11:29 +01:00
Jakob Guddas
804906dcd8 Update tractor icon (#1894)
* Updated icons/tractor.svg

* Updated icons/tractor.json
2024-02-19 08:11:24 +01:00
Karsa
d575743d3a Fix siren rounding (#1896)
Co-authored-by: Karsa <karsa@sztaki.hu>
2024-02-19 08:11:15 +01:00
Karsa
42c1faed75 Adds telescope icon (#1889)
Co-authored-by: Karsa <karsa@sztaki.hu>
2024-02-15 13:34:42 +01:00
Karsa
42b494f853 Updated naming guidelines with element order and modifier naming scheme (#1874)
* Update icon-design-guide.md

* Update docs/guide/design/icon-design-guide.md

---------

Co-authored-by: Karsa <karsa@sztaki.hu>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-02-12 19:04:03 +01:00
Karsa
9bf8a653a3 Ran prettier on updateContributors.mjs (#1887)
Co-authored-by: Karsa <karsa@sztaki.hu>
2024-02-12 18:59:06 +01:00
Jakob Guddas
01cff578e5 Update file-output icon (#1879)
* Updated icons/file-output.svg

* Updated icons/file-output.json
2024-02-12 16:18:48 +01:00
Jakob Guddas
a0b1305258 feat: folder output (#1619) 2024-02-12 16:18:39 +01:00
Karsa
0cfdfa2181 Adds images and (updates file-image to match) (#1852)
* Adds images and updates file-image to match

* Improve pixel perfection

---------

Co-authored-by: Karsa <karsa@sztaki.hu>
2024-02-12 16:17:22 +01:00
Jakob Guddas
a2e8ea32d2 feat: folder symlink (#1618)
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-02-12 16:16:03 +01:00
Jakob Guddas
8a7e6ba343 Update file-symlink icon (#1878)
* Updated icons/file-symlink.svg

* Updated icons/file-symlink.json
2024-02-12 16:15:05 +01:00
Jakob Guddas
7a9233f4a7 Update clover icon (#1883)
* Updated icons/clover.svg

* Updated icons/clover.json

* Updated icons/clover.svg

* Update icons/clover.svg

Co-authored-by: Karsa <contact@karsa.org>

---------

Co-authored-by: Karsa <contact@karsa.org>
2024-02-12 16:14:07 +01:00
Jakob Guddas
71aef25812 Update circle-dashed icon (#1884)
* Updated icons/circle-dashed.svg

* Updated icons/circle-dashed.json

* Update icons/circle-dashed.svg

Co-authored-by: Karsa <contact@karsa.org>

---------

Co-authored-by: Karsa <contact@karsa.org>
2024-02-12 16:13:45 +01:00
Jakob Guddas
33189a81ac fix: updateContributors adds trailing line to icon.json (#1885) 2024-02-12 14:33:04 +01:00
Simon
e3923f87c2 Add radical icon (square root) (#1847)
* Add `square-root`icon

* Update square-root.svg

* Fix square icon SVG

Co-authored-by: Jakob Guddas <github@jguddas.de>

* Rename `square-root` `radical`

* Rename radical-square.json to square-radical.json

* Rename radical-square.svg to square-radical.svg

* Update radical.json

* Match activity height

Co-authored-by: Karsa <contact@karsa.org>

---------

Co-authored-by: Jakob Guddas <github@jguddas.de>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Karsa <contact@karsa.org>
2024-02-11 23:42:03 +01:00
Jakob Guddas
08c040a57d Update anchor icon (#1875)
* Updated icons/anchor.svg

* Updated icons/anchor.json
2024-02-11 18:07:39 +01:00
Jakob Guddas
981c3309ce Rounded corners of graduation-cap icon (#1870)
* Updated icons/graduation-cap.svg

* Updated icons/graduation-cap.json

* Updated icons/graduation-cap.svg

* Updated icons/graduation-cap.json
2024-02-11 17:58:43 +01:00
Jakob Guddas
2e0af66d8a Update school icon (#1872)
* Updated icons/school.svg

* Updated icons/school.json
2024-02-11 17:04:35 +01:00
Jakob Guddas
a789c91213 Optimized shopping-basket icon (#1871)
* Updated icons/shopping-basket.svg

* Updated icons/shopping-basket.json

* Updated icons/shopping-basket.svg

* Updated icons/shopping-basket.json
2024-02-11 17:02:13 +01:00
Jakob Guddas
91c95600f3 Update rss icon metadata (#1873)
* Updated icons/rss.svg

* Updated icons/rss.json
2024-02-11 17:01:50 +01:00
Jakob Guddas
99acf4102c Update wifi icon (#1877)
* Updated icons/wifi.svg

* Updated icons/wifi.json

* Updated icons/wifi.svg

* Updated icons/wifi.json

* Update wifi-off.svg

---------

Co-authored-by: Karsa <contact@karsa.org>
2024-02-11 17:01:19 +01:00
Jakob Guddas
86f2dc12f4 Update arrow-big-down-dash icon (#1876)
* Updated icons/arrow-big-down-dash.svg

* Updated icons/arrow-big-down-dash.json
2024-02-11 16:59:54 +01:00
DefaultLP
b9fdde2d09 Add caption icons (#1799)
* Add caption icons

* Add subtitle category

* Changed corner radius to 2px

* Fixed metadata for caption icons

* Took suggestions to heart

* Removed trailing spaces

* Fixed captions-off violations

* Fixed name and added aliases

* Removed subtitles svg and json

* Removed alias from captions-off

* format

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-02-05 19:34:34 +01:00
Karsa
5c494962e1 Various contributor fixes (#1844)
* Various manual contributor fixes

* Remove myself from a few dozen more icons I took practically no part in.

* Remove danielbayley from some icons as per https://github.com/lucide-icons/lucide/pull/1844

---------

Co-authored-by: Rigó József Karsa <karsa@sztaki.hu>
2024-02-05 19:11:34 +01:00
Karsa
772c5be034 Fixes scaling, tag and tags to have the necessary rounding. (#1850)
* Fixes scaling, tag and tags to have the necessary rounding.

* Update tags.svg

* Update tag.svg

---------

Co-authored-by: Karsa <karsa@sztaki.hu>
2024-02-05 15:08:13 +01:00
Karsa
ee3483eb1b Adds handshake icon (#1835)
* Adds handshake icon

* update handshake icon

* Update handshake.svg

* Update icons/handshake.json

Co-authored-by: Jakob Guddas <github@jguddas.de>

---------

Co-authored-by: Rigó József Karsa <karsa@sztaki.hu>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Jakob Guddas <github@jguddas.de>
2024-02-01 16:37:10 +01:00
CokaKoala
9182c51962 feat: Adds Svelte 5 support (#1748)
* adds svelte 5 support

* Update package.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-02-01 16:36:27 +01:00
Han Yeong-woo
eb035fe370 Improve formatting (#1814)
* Ignore linting for examples in docs

* Formatting JSX single attribute per line

* Separte `format` and `lint:format` in package.json

* Bump prettier version

* Run format
2024-02-01 14:38:21 +01:00
Karsa
b96e6acd2e Fix plus/minus sign size in shield-plus/shield-minus (#1840)
* Update shield-plus to match other plus icons

* Update shield-minus to match other minus icons

---------

Co-authored-by: Rigó József Karsa <karsa@sztaki.hu>
2024-01-31 22:53:23 +01:00
Daniel Bayley
7b62649c39 Add smaller dots fill fix to VS Code snippets (#1837) 2024-01-31 09:02:07 +01:00
Daniel Bayley
10aff6cf7b Fix bolt missing metadata (#1836) 2024-01-31 08:42:01 +01:00
Jordan Long
cfa8924025 Update truck icon to match ambulance (#1838) 2024-01-31 08:36:17 +01:00
Eric Fennis
713e9b8a09 Add clipboard-plus and clipboard-minus icons (#1812)
* Add clipboard icons

* Switch to same plus minus as file icons

* update contributors

* center plus and minus paths
2024-01-31 08:35:00 +01:00
Eric Fennis
8ab6f80e4f Add headset icon (#1780)
* Add headset icon

* Fix lint errors

* Revert headset change
2024-01-30 10:08:59 +01:00
Jordan Long
a5221c236a Add story icon (#1820)
* Add story icon

* Split single path into multiple elements

* Refine icons/story.svg

Co-authored-by: Jakob Guddas <github@jguddas.de>

* Update contributors to icons/story.json

Co-authored-by: Jakob Guddas <github@jguddas.de>

* Change "story" icon name to "circle-fading-plus" and add to shapes category

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Jakob Guddas <github@jguddas.de>
2024-01-30 09:30:44 +01:00
Jordan Long
cdd32b5294 Add ambulance icon (#1819)
* Add ambulance icon

* Remove extra attributes and connect path under wheels

* Lift roof and "+" to adhere to 2px gap rule for icons/ambulance.svg

Co-authored-by: Jakob Guddas <github@jguddas.de>

* Increase cab height of ambulance

* Add contributors from truck icon

---------

Co-authored-by: Jakob Guddas <github@jguddas.de>
2024-01-29 19:50:04 +01:00
CokaKoala
54c8d4078d fix: Add the license banner inside of the script tag instead of an HTML comment (#1811)
* add banner inside of the script tag instead

* renamed script

* Update packages/lucide-svelte/scripts/license.mjs

Co-authored-by: Jakob Guddas <github@jguddas.de>

* Update packages/lucide-svelte/scripts/appendLicense.mjs

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>

* renamed file

---------

Co-authored-by: Jakob Guddas <github@jguddas.de>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-01-29 19:48:14 +01:00
Daniel Bayley
3302870983 Refine helping-hand/add hand-* icons (#1328)
* Refine `helping-hand` icon

* Add `hand-platter` icon

* Add `hand-coins` (savings) icon

* Add `hand-heart` icon

* Fix/optimise `helping-hand` icon

* Fix/optimise `hand-coins` icon

* Fix/optimise `hand-heart` icon

* Rename `helping-hand` to `hand-helping`

* Fix/optimise `hand-platter` icon

* Add `thumbs-up-down` icon

* Improve `thumbs-up`/`down` icons metadata

* Improve metadata

* Delete thumbs-up-down icons

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-01-26 10:10:19 +01:00
Karsa
0f25ee86a0 Fix fills in smaller dots (#1436)
* Fixes palette

* Fixing fill on circles

* Fixing fills

* Revert tractor fill

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-01-26 09:58:05 +01:00
Jakob Guddas
28686b5bd5 feat: added webhook-off icon (#1566)
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-01-25 08:54:18 +01:00
Eric Fennis
8cc143915c Update JSdoc lucide svelte (#1826) 2024-01-25 08:23:34 +01:00
1828 changed files with 18140 additions and 7602 deletions

15
.cspell/custom-words.txt Normal file
View File

@@ -0,0 +1,15 @@
# Custom words
fullscreen
gamepad
gantt
kanban
pilcrow
squircle
strikethrough
touchpad
ungroup
# Brands
codepen
codesandbox
dribbble

View File

@@ -6,6 +6,5 @@ tests
node_modules node_modules
.eslintrc.js .eslintrc.js
docs/images docs/images
docs/guide/basics/examples docs/**/examples/
docs/guide/advanced/examples
packages/lucide-react/dynamicIconImports.js packages/lucide-react/dynamicIconImports.js

View File

@@ -42,12 +42,15 @@ module.exports = {
'@html-eslint/no-duplicate-attrs': 'error', '@html-eslint/no-duplicate-attrs': 'error',
'@html-eslint/no-inline-styles': 'error', '@html-eslint/no-inline-styles': 'error',
'@html-eslint/require-attrs': [ '@html-eslint/require-attrs': [
'error', 'error',
...Object.entries(DEFAULT_ATTRS) ...Object.entries(DEFAULT_ATTRS).map(([attr, value]) => ({
.map(([attr, value]) => ({ tag: 'svg', attr, value: String(value) })) tag: 'svg',
attr,
value: String(value),
})),
], ],
'@html-eslint/indent': ['error', 2], '@html-eslint/indent': ['error', 2],
"@html-eslint/no-multiple-empty-lines": ["error", { "max": 0 }], '@html-eslint/no-multiple-empty-lines': ['error', { max: 0 }],
'@html-eslint/no-extra-spacing-attrs': [ '@html-eslint/no-extra-spacing-attrs': [
'error', 'error',
{ {
@@ -64,7 +67,7 @@ module.exports = {
'@html-eslint/element-newline': 'error', '@html-eslint/element-newline': 'error',
'@html-eslint/no-trailing-spaces': 'error', '@html-eslint/no-trailing-spaces': 'error',
'@html-eslint/quotes': 'error', '@html-eslint/quotes': 'error',
} },
}, },
], ],
}; };

View File

@@ -35,6 +35,16 @@ body:
placeholder: e.g. 0.289.1 placeholder: e.g. 0.289.1
validations: validations:
required: true required: true
- type: checkboxes
id: can-reproduce-in-latest-version
attributes:
label: Can you reproduce this in the latest version?
description: i.e. after running `npm install lucide-react@latest`
options:
- label: Yes
- label: No
validations:
required: false
- type: checkboxes - type: checkboxes
id: browsers id: browsers
attributes: attributes:

View File

@@ -1,5 +1,5 @@
name: "Build and Test" name: 'Build and Test'
description: "Builds and test a package" description: 'Builds and test a package'
inputs: inputs:
name: name:
@@ -7,7 +7,7 @@ inputs:
required: true required: true
runs: runs:
using: "composite" using: 'composite'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4

View File

@@ -1,5 +1,5 @@
name: "Check icons" name: 'Check icons'
description: "Cross-checks icon and category references in JSON descriptors" description: 'Cross-checks icon and category references in JSON descriptors'
inputs: inputs:
name: name:
@@ -7,7 +7,7 @@ inputs:
required: true required: true
runs: runs:
using: "composite" using: 'composite'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4

96
.github/labeler.yml vendored
View File

@@ -1,92 +1,92 @@
# For changed dependencies # For changed dependencies
📦 dependencies: 📦 dependencies:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- pnpm-lock.yaml - pnpm-lock.yaml
# For changes in documentation # For changes in documentation
📖 documentation: 📖 documentation:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- docs/*.md - docs/*.md
- docs/**/*.md - docs/**/*.md
# For changes in the site, but not markdown files # For changes in the site, but not markdown files
🌍 site: 🌍 site:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'docs/**' - 'docs/**'
# For changes in the metadata # For changes in the metadata
🫧 metadata: 🫧 metadata:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'icons/*.json' - 'icons/*.json'
- categories/* - categories/*
# For changes or added icons # For changes or added icons
🎨 icon: 🎨 icon:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'icons/*.svg' - 'icons/*.svg'
# For changes in the lucide package # For changes in the lucide package
🧳 lucide package: 🧳 lucide package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide/*' - 'packages/lucide/*'
# For changes in the lucide React package # For changes in the lucide React package
⚛️ react package: ⚛️ react package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-react/*' - 'packages/lucide-react/*'
# For changes in the lucide React Native package # For changes in the lucide React Native package
⚛️ react native package: ⚛️ react native package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-react-native/*' - 'packages/lucide-react-native/*'
# For changes in the lucide vue packages # For changes in the lucide vue packages
💎 vue package: 💎 vue package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-vue/*' - 'packages/lucide-vue/*'
- 'packages/lucide-vue-next/*' - 'packages/lucide-vue-next/*'
# For changes in the lucide angular package # For changes in the lucide angular package
🅰️ angular package: 🅰️ angular package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-angular/*' - 'packages/lucide-angular/*'
# For changes in the lucide preact package # For changes in the lucide preact package
⚛️ preact package: ⚛️ preact package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-preact/*' - 'packages/lucide-preact/*'
# For changes in the lucide svelte package # For changes in the lucide svelte package
🧣 svelte package: 🧣 svelte package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-svelte/*' - 'packages/lucide-svelte/*'
# For changes in the lucide solid package # For changes in the lucide solid package
🪝 solid package: 🪝 solid package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-solid/*' - 'packages/lucide-solid/*'
# For changes in the lucide static package # For changes in the lucide static package
🪨 static package: 🪨 static package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-static/*' - 'packages/lucide-static/*'
# For changes in the lucide flutter package # For changes in the lucide flutter package
🏹 flutter package: 🏹 flutter package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-flutter/*' - 'packages/lucide-flutter/*'

View File

@@ -1,11 +1,13 @@
name: Close stale issues and PR name: Close stale issues and PR
on: on:
schedule: schedule:
- cron: "45 1 * * *" - cron: '45 1 * * *'
jobs: jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
pull-requests: write
steps: steps:
- uses: actions/stale@v9 - uses: actions/stale@v9
with: with:
@@ -14,4 +16,5 @@ jobs:
close-pr-message: This PR was closed because it has been stalled for 5 days with no activity. close-pr-message: This PR was closed because it has been stalled for 5 days with no activity.
close-pr-label: 🧶 stale close-pr-label: 🧶 stale
days-before-stale: 30 days-before-stale: 30
days-before-issue-stale: -1
days-before-close: -1 days-before-close: -1

View File

@@ -1,6 +1,6 @@
name: "Pull Request Labeler" name: 'Pull Request Labeler'
on: on:
- pull_request_target - pull_request_target
jobs: jobs:
triage: triage:
@@ -9,4 +9,4 @@ jobs:
pull-requests: write pull-requests: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/labeler@v5 - uses: actions/labeler@v5

View File

@@ -29,7 +29,7 @@ jobs:
- name: Create font in ./lucide-font - name: Create font in ./lucide-font
run: pnpm build:font run: pnpm build:font
- name: "Upload to Artifacts" - name: 'Upload to Artifacts'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: lucide-font name: lucide-font

View File

@@ -41,17 +41,18 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
package: [ package:
'lucide', [
'lucide-react', 'lucide',
'lucide-react-native', 'lucide-react',
'lucide-vue', 'lucide-react-native',
'lucide-vue-next', 'lucide-vue',
'lucide-angular', 'lucide-vue-next',
'lucide-preact', 'lucide-angular',
'lucide-solid', 'lucide-preact',
'lucide-svelte', 'lucide-solid',
] 'lucide-svelte',
]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
@@ -136,7 +137,7 @@ jobs:
- name: Create font in ./lucide-font - name: Create font in ./lucide-font
run: pnpm build:font run: pnpm build:font
- name: "Upload to Artifacts" - name: 'Upload to Artifacts'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: lucide-font name: lucide-font
@@ -145,10 +146,7 @@ jobs:
post-release: post-release:
if: github.repository == 'lucide-icons/lucide' if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ needs: [pre-release, lucide-font]
pre-release,
lucide-font,
]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View File

@@ -1,5 +1,8 @@
pnpm-lock.yaml pnpm-lock.yaml
# docs examples
docs/**/examples/
# lucide-angular # lucide-angular
packages/lucide-angular/.angular/cache packages/lucide-angular/.angular/cache

2
.vscode/launch.json vendored
View File

@@ -12,4 +12,4 @@
"webRoot": "${workspaceFolder}" "webRoot": "${workspaceFolder}"
} }
] ]
} }

11
.vscode/settings.json vendored
View File

@@ -1,13 +1,6 @@
{ {
"cSpell.words": [ "cSpell.words": ["devs", "preact", "Preact"],
"devs",
"preact",
"Preact"
],
"eslint.enable": true, "eslint.enable": true,
"eslint.validate": [ "eslint.validate": ["javascript", "svg"],
"javascript",
"svg"
],
"svg.preview.background": "transparent" "svg.preview.background": "transparent"
} }

View File

@@ -49,7 +49,7 @@
"circle", "circle",
"<circle" "<circle"
], ],
"body": "<circle cx=\"${2:12}\" cy=\"${3:$2}\" r=\"${1|10,2,.5|}\" />" "body": "<circle cx=\"${2:12}\" cy=\"${3:$2}\" r=\"${1|10,2,.5\" fill=\"currentColor|}\" />"
}, },
"Ellipse": { "Ellipse": {
"scope": "xml", "scope": "xml",

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Accessibility", "title": "Accessibility",
"icon": "accessibility" "icon": "accessibility"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Accounts & access", "title": "Accounts & access",
"icon": "user" "icon": "user"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Animals", "title": "Animals",
"icon": "dog" "icon": "dog"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Arrows", "title": "Arrows",
"icon": "arrow-left-right" "icon": "arrow-left-right"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Brands", "title": "Brands",
"icon": "facebook" "icon": "facebook"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Buildings", "title": "Buildings",
"icon": "building" "icon": "building"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Charts", "title": "Charts",
"icon": "pie-chart" "icon": "pie-chart"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Communication", "title": "Communication",
"icon": "message-circle" "icon": "message-circle"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Connectivity", "title": "Connectivity",
"icon": "wifi" "icon": "wifi"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Currency", "title": "Currency",
"icon": "dollar-sign" "icon": "dollar-sign"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Cursors", "title": "Cursors",
"icon": "mouse-pointer-2" "icon": "mouse-pointer-2"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Design", "title": "Design",
"icon": "palette" "icon": "palette"
} }

View File

@@ -1,5 +1,5 @@
{ {
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Coding & development", "title": "Coding & development",
"icon": "code-2" "icon": "code-xml"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Devices", "title": "Devices",
"icon": "smartphone" "icon": "smartphone"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Emoji", "title": "Emoji",
"icon": "smile" "icon": "smile"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "File icons", "title": "File icons",
"icon": "panels-top-left" "icon": "panels-top-left"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Food & beverage", "title": "Food & beverage",
"icon": "coffee" "icon": "coffee"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Furniture", "title": "Furniture",
"icon": "rocking-chair" "icon": "rocking-chair"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Gaming", "title": "Gaming",
"icon": "gamepad-2" "icon": "gamepad-2"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Home", "title": "Home",
"icon": "home" "icon": "home"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Layout", "title": "Layout",
"icon": "panels-top-left" "icon": "panels-top-left"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Mail", "title": "Mail",
"icon": "mail" "icon": "mail"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Maps", "title": "Maps",
"icon": "map" "icon": "map"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Maths", "title": "Maths",
"icon": "divide" "icon": "divide"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Medical", "title": "Medical",
"icon": "heart" "icon": "heart"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Money", "title": "Money",
"icon": "piggy-bank" "icon": "piggy-bank"
} }

View File

@@ -1,5 +1,5 @@
{ {
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Multimedia", "title": "Multimedia",
"icon": "play-circle" "icon": "circle-play"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Nature", "title": "Nature",
"icon": "sprout" "icon": "sprout"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Navigation", "title": "Navigation",
"icon": "compass" "icon": "compass"
} }

View File

@@ -1,5 +1,5 @@
{ {
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Notifications", "title": "Notifications",
"icon": "alert-triangle" "icon": "triangle-alert"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "People", "title": "People",
"icon": "person-standing" "icon": "person-standing"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Photography", "title": "Photography",
"icon": "camera" "icon": "camera"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Science", "title": "Science",
"icon": "flask-conical" "icon": "flask-conical"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Seasons", "title": "Seasons",
"icon": "leaf" "icon": "leaf"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Security", "title": "Security",
"icon": "shield" "icon": "shield"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Shapes", "title": "Shapes",
"icon": "triangle" "icon": "triangle"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Shopping", "title": "Shopping",
"icon": "shopping-bag" "icon": "shopping-bag"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Social", "title": "Social",
"icon": "thumbs-up" "icon": "thumbs-up"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Sports", "title": "Sports",
"icon": "type" "icon": "type"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Sustainability", "title": "Sustainability",
"icon": "recycle" "icon": "recycle"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Time & calendar", "title": "Time & calendar",
"icon": "calendar" "icon": "calendar"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Tools", "title": "Tools",
"icon": "hammer" "icon": "hammer"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Transportation", "title": "Transportation",
"icon": "train-front" "icon": "train-front"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Travel", "title": "Travel",
"icon": "backpack" "icon": "backpack"
} }

View File

@@ -2,4 +2,4 @@
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Weather", "title": "Weather",
"icon": "cloud-sun" "icon": "cloud-sun"
} }

10
cspell.json Normal file
View File

@@ -0,0 +1,10 @@
{
"dictionaries": ["en-us", "custom-words"],
"dictionaryDefinitions": [
{
"name": "custom-words",
"path": "./.cspell/custom-words.txt",
"addWords": true
}
]
}

View File

@@ -1,11 +1,11 @@
import { eventHandler, setResponseHeader } from 'h3' import { eventHandler, setResponseHeader } from 'h3';
import iconMetaData from '../../data/iconMetaData' import iconMetaData from '../../data/iconMetaData';
export default eventHandler((event) => { export default eventHandler((event) => {
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400') setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*') setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
return Object.fromEntries( return Object.fromEntries(
Object.entries(iconMetaData).map(([name, { categories }]) => [ name, categories ]) Object.entries(iconMetaData).map(([name, { categories }]) => [name, categories]),
) );
}) });

View File

@@ -37,13 +37,13 @@ export default eventHandler((event) => {
backdropString, backdropString,
src, src,
color: name in iconNodes ? 'red' : '#777', color: name in iconNodes ? 'red' : '#777',
}) }),
); );
} }
const svg = Buffer.from( const svg = Buffer.from(
// We can't use jsx here, is not supported here by nitro. // We can't use jsx here, is not supported here by nitro.
renderToString(createElement(SvgPreview, { src, showGrid: true }, children)) renderToString(createElement(SvgPreview, { src, showGrid: true }, children)),
).toString('utf8'); ).toString('utf8');
defaultContentType(event, 'image/svg+xml'); defaultContentType(event, 'image/svg+xml');

View File

@@ -28,7 +28,7 @@ export default eventHandler(async (event) => {
stroke-width="2" stroke-width="2"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
` `,
); );
const resvg = new Resvg(svg, { background: '#000' }); const resvg = new Resvg(svg, { background: '#000' });

View File

@@ -1,12 +1,12 @@
import { eventHandler, setResponseHeader, defaultContentType } from 'h3' import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
import { renderToString } from 'react-dom/server' import { renderToString } from 'react-dom/server';
import { createElement } from 'react' import { createElement } from 'react';
import SvgPreview from '../../../lib/SvgPreview/index.tsx'; import SvgPreview from '../../../lib/SvgPreview/index.tsx';
import createLucideIcon, { IconNode } from 'lucide-react/src/createLucideIcon' import createLucideIcon, { IconNode } from 'lucide-react/src/createLucideIcon';
import { parseSync } from 'svgson'; import { parseSync } from 'svgson';
export default eventHandler((event) => { export default eventHandler((event) => {
const { params } = event.context const { params } = event.context;
const [strokeWidth, svgData] = params.data.split('/'); const [strokeWidth, svgData] = params.data.split('/');
const data = svgData.slice(0, -4); const data = svgData.slice(0, -4);
@@ -16,8 +16,8 @@ export default eventHandler((event) => {
const Icon = createLucideIcon( const Icon = createLucideIcon(
'icon', 'icon',
parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`).children.map( parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`).children.map(
({ name, attributes }) => [name, attributes] ({ name, attributes }) => [name, attributes],
) as IconNode ) as IconNode,
); );
const svg = Buffer.from( const svg = Buffer.from(
@@ -33,12 +33,12 @@ export default eventHandler((event) => {
@media screen and (prefers-color-scheme: dark) { @media screen and (prefers-color-scheme: dark) {
svg { stroke: #fff; fill: transparent !important; } svg { stroke: #fff; fill: transparent !important; }
} }
</style>` </style>`,
) ),
).toString('utf8'); ).toString('utf8');
defaultContentType(event, 'image/svg+xml') defaultContentType(event, 'image/svg+xml');
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000') setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
return svg return svg;
}) });

View File

@@ -1,30 +1,30 @@
import { eventHandler, getQuery, setResponseHeader } from 'h3' import { eventHandler, getQuery, setResponseHeader } from 'h3';
import iconNodes from '../../data/iconNodes' import iconNodes from '../../data/iconNodes';
import { IconNodeWithKeys } from '../../theme/types' import { IconNodeWithKeys } from '../../theme/types';
export default eventHandler((event) => { export default eventHandler((event) => {
const query = getQuery(event) const query = getQuery(event);
const withUniqueKeys = query.withUniqueKeys === 'true' const withUniqueKeys = query.withUniqueKeys === 'true';
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400') setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*') setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
if (withUniqueKeys) { if (withUniqueKeys) {
return iconNodes return iconNodes;
} }
return Object.entries(iconNodes).reduce((acc, [name, iconNode]) => { return Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
if (withUniqueKeys) { if (withUniqueKeys) {
return [name, iconNode] return [name, iconNode];
} }
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs}]) => { const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
return [name, attrs] return [name, attrs];
}) });
acc[name] = newIconNode acc[name] = newIconNode;
return acc return acc;
}, {}) }, {});
}) });

View File

@@ -1,29 +1,29 @@
import { eventHandler, getQuery, setResponseHeader, createError } from 'h3' import { eventHandler, getQuery, setResponseHeader, createError } from 'h3';
import iconNodes from '../../data/iconNodes' import iconNodes from '../../data/iconNodes';
import createLucideIcon from 'lucide-react/src/createLucideIcon' import createLucideIcon from 'lucide-react/src/createLucideIcon';
import { renderToString } from 'react-dom/server' import { renderToString } from 'react-dom/server';
import { createElement } from 'react' import { createElement } from 'react';
export default eventHandler((event) => { export default eventHandler((event) => {
const { params } = event.context const { params } = event.context;
const iconNode = iconNodes[params.iconName] const iconNode = iconNodes[params.iconName];
if (iconNode == null) { if (iconNode == null) {
const error = createError({ const error = createError({
statusCode: 404, statusCode: 404,
message: `Icon "${params.iconName}" not found`, message: `Icon "${params.iconName}" not found`,
}) });
return sendError(event, error) return sendError(event, error);
} }
const width = getQuery(event).width || undefined const width = getQuery(event).width || undefined;
const height = getQuery(event).height || undefined const height = getQuery(event).height || undefined;
const color = getQuery(event).color || undefined const color = getQuery(event).color || undefined;
const strokeWidth = getQuery(event).strokeWidth || undefined const strokeWidth = getQuery(event).strokeWidth || undefined;
const LucideIcon = createLucideIcon(params.iconName, iconNode) const LucideIcon = createLucideIcon(params.iconName, iconNode);
const svg = Buffer.from( const svg = Buffer.from(
renderToString( renderToString(
@@ -32,14 +32,13 @@ export default eventHandler((event) => {
height, height,
color: color ? `#${color}` : undefined, color: color ? `#${color}` : undefined,
strokeWidth, strokeWidth,
} }),
)) ),
).toString('utf8'); ).toString('utf8');
defaultContentType(event, 'image/svg+xml') defaultContentType(event, 'image/svg+xml');
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000') setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*') setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
return svg return svg;
});
})

View File

@@ -1,11 +1,9 @@
import { eventHandler, setResponseHeader } from 'h3' import { eventHandler, setResponseHeader } from 'h3';
import iconMetaData from '../../data/iconMetaData' import iconMetaData from '../../data/iconMetaData';
export default eventHandler((event) => { export default eventHandler((event) => {
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400') setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*') setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
return Object.fromEntries( return Object.fromEntries(Object.entries(iconMetaData).map(([name, { tags }]) => [name, tags]));
Object.entries(iconMetaData).map(([name, { tags }]) => [ name, tags ]) });
)
})

View File

@@ -1,3 +1,3 @@
export default eventHandler(() => { export default eventHandler(() => {
return { nitro: 'Is Awesome! asda' } return { nitro: 'Is Awesome! asda' };
}) });

View File

@@ -1,10 +1,10 @@
import { fileURLToPath, URL } from 'node:url' import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vitepress' import { defineConfig } from 'vitepress';
import sidebar from './sidebar'; import sidebar from './sidebar';
const title = "Lucide"; const title = 'Lucide';
const socialTitle = "Lucide Icons"; const socialTitle = 'Lucide Icons';
const description = "Beautiful & consistent icon toolkit made by the community." const description = 'Beautiful & consistent icon toolkit made by the community.';
// https://vitepress.dev/reference/site-config // https://vitepress.dev/reference/site-config
export default defineConfig({ export default defineConfig({
@@ -19,86 +19,131 @@ export default defineConfig({
{ {
find: /^.*\/VPIconAlignLeft\.vue$/, find: /^.*\/VPIconAlignLeft\.vue$/,
replacement: fileURLToPath( replacement: fileURLToPath(
new URL('./theme/components/overrides/VPIconAlignLeft.vue', import.meta.url) new URL('./theme/components/overrides/VPIconAlignLeft.vue', import.meta.url),
) ),
}, },
{ {
find: /^.*\/VPFooter\.vue$/, find: /^.*\/VPFooter\.vue$/,
replacement: fileURLToPath( replacement: fileURLToPath(
new URL('./theme/components/overrides/VPFooter.vue', import.meta.url) new URL('./theme/components/overrides/VPFooter.vue', import.meta.url),
) ),
} },
] ],
}, },
}, },
head: [ head: [
[ 'script', { [
src: 'https://analytics.lucide.dev/js/script.js', 'script',
'data-domain': 'lucide.dev', {
defer: '' src: 'https://analytics.lucide.dev/js/script.js',
}], 'data-domain': 'lucide.dev',
[ 'meta', { defer: '',
property:"og:locale", },
content:"en_US" ],
}], [
[ 'meta', { 'meta',
property:"og:type", {
content:"website" property: 'og:locale',
}], content: 'en_US',
[ 'meta', { },
property:"og:site_name", ],
content: title, [
}], 'meta',
[ 'meta', { {
property:"og:title", property: 'og:type',
content: socialTitle, content: 'website',
}], },
[ 'meta', { ],
property:"og:description", [
content: description 'meta',
}], {
[ 'meta', { property: 'og:site_name',
property:"og:url", content: title,
content:"https://lucide.dev" },
}], ],
[ 'meta', { [
property:"og:image", 'meta',
content: "https://lucide.dev/og.png" {
}], property: 'og:title',
[ 'meta', { content: socialTitle,
property:"og:image:width", },
content:"1200" ],
}], [
[ 'meta', { 'meta',
property:"og:image:height", {
content:"630" property: 'og:description',
}], content: description,
[ 'meta', { },
property:"og:image:type", ],
content:"image/png" [
}], 'meta',
[ 'meta', { {
property:"twitter:card", property: 'og:url',
content:"summary_large_image" content: 'https://lucide.dev',
}], },
[ 'meta', { ],
property:"twitter:title", [
content: socialTitle, 'meta',
}], {
[ 'meta', { property: 'og:image',
property:"twitter:description", content: 'https://lucide.dev/og.png',
content: description },
}], ],
[ 'meta', { [
property:"twitter:image", 'meta',
content:"https://lucide.dev/og.png" {
}], property: 'og:image:width',
content: '1200',
},
],
[
'meta',
{
property: 'og:image:height',
content: '630',
},
],
[
'meta',
{
property: 'og:image:type',
content: 'image/png',
},
],
[
'meta',
{
property: 'twitter:card',
content: 'summary_large_image',
},
],
[
'meta',
{
property: 'twitter:title',
content: socialTitle,
},
],
[
'meta',
{
property: 'twitter:description',
content: description,
},
],
[
'meta',
{
property: 'twitter:image',
content: 'https://lucide.dev/og.png',
},
],
], ],
themeConfig: { themeConfig: {
// https://vitepress.dev/reference/default-theme-config // https://vitepress.dev/reference/default-theme-config
logo: { logo: {
light: '/logo.light.svg', light: '/logo.light.svg',
dark: '/logo.dark.svg' dark: '/logo.dark.svg',
}, },
nav: [ nav: [
{ text: 'Icons', link: '/icons/' }, { text: 'Icons', link: '/icons/' },
@@ -110,21 +155,21 @@ export default defineConfig({
sidebar, sidebar,
socialLinks: [ socialLinks: [
{ icon: 'github', link: 'https://github.com/lucide-icons/lucide' }, { icon: 'github', link: 'https://github.com/lucide-icons/lucide' },
{ icon: 'discord', link: 'https://discord.gg/EH6nSts' } { icon: 'discord', link: 'https://discord.gg/EH6nSts' },
], ],
footer: { footer: {
message: 'Released under the ISC License.', message: 'Released under the ISC License.',
copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors` copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`,
}, },
editLink: { editLink: {
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path' pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path',
}, },
carbonAds: { carbonAds: {
code: 'CWYIC53U', code: 'CWYIC53U',
placement: 'lucidedev' placement: 'lucidedev',
} },
}, },
sitemap: { sitemap: {
hostname: 'https://lucide.dev/' hostname: 'https://lucide.dev/',
} },
}) });

View File

@@ -3,87 +3,171 @@
"order": 0, "order": 0,
"icon": "js", "icon": "js",
"shields": [ "shields": [
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide", "href": "https://www.npmjs.com/package/lucide" }, {
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide", "href": "https://www.npmjs.com/package/lucide" } "alt": "npm",
"src": "https://img.shields.io/npm/v/lucide",
"href": "https://www.npmjs.com/package/lucide"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide",
"href": "https://www.npmjs.com/package/lucide"
}
] ]
}, },
"lucide-react": { "lucide-react": {
"order": 1, "order": 1,
"icon": "react", "icon": "react",
"shields": [ "shields": [
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-react", "href": "https://www.npmjs.com/package/lucide-react" }, {
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-react", "href": "https://www.npmjs.com/package/lucide-react" } "alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-react",
"href": "https://www.npmjs.com/package/lucide-react"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-react",
"href": "https://www.npmjs.com/package/lucide-react"
}
] ]
}, },
"lucide-vue": { "lucide-vue": {
"order": 2, "order": 2,
"icon": "vue", "icon": "vue",
"shields": [ "shields": [
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-vue", "href": "https://www.npmjs.com/package/lucide-vue" }, {
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-vue", "href": "https://www.npmjs.com/package/lucide-vue" } "alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-vue",
"href": "https://www.npmjs.com/package/lucide-vue"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-vue",
"href": "https://www.npmjs.com/package/lucide-vue"
}
] ]
}, },
"lucide-vue-next": { "lucide-vue-next": {
"order": 3, "order": 3,
"icon": "vue-next", "icon": "vue-next",
"shields": [ "shields": [
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-vue-next", "href": "https://www.npmjs.com/package/lucide-vue-next" }, {
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-vue-next", "href": "https://www.npmjs.com/package/lucide-vue-next" } "alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-vue-next",
"href": "https://www.npmjs.com/package/lucide-vue-next"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-vue-next",
"href": "https://www.npmjs.com/package/lucide-vue-next"
}
] ]
}, },
"lucide-svelte": { "lucide-svelte": {
"order": 4, "order": 4,
"icon": "svelte", "icon": "svelte",
"shields": [ "shields": [
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-svelte", "href": "https://www.npmjs.com/package/lucide-svelte" }, {
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-svelte", "href": "https://www.npmjs.com/package/lucide-svelte" } "alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-svelte",
"href": "https://www.npmjs.com/package/lucide-svelte"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-svelte",
"href": "https://www.npmjs.com/package/lucide-svelte"
}
] ]
}, },
"lucide-solid": { "lucide-solid": {
"order": 4, "order": 4,
"icon": "solid", "icon": "solid",
"shields": [ "shields": [
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-solid", "href": "https://www.npmjs.com/package/lucide-solid" }, {
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-solid", "href": "https://www.npmjs.com/package/lucide-solid" } "alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-solid",
"href": "https://www.npmjs.com/package/lucide-solid"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-solid",
"href": "https://www.npmjs.com/package/lucide-solid"
}
] ]
}, },
"lucide-preact": { "lucide-preact": {
"order": 5, "order": 5,
"icon": "preact", "icon": "preact",
"shields": [ "shields": [
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-preact", "href": "https://www.npmjs.com/package/lucide-preact" }, {
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-preact", "href": "https://www.npmjs.com/package/lucide-preact" } "alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-preact",
"href": "https://www.npmjs.com/package/lucide-preact"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-preact",
"href": "https://www.npmjs.com/package/lucide-preact"
}
] ]
}, },
"lucide-react-native": { "lucide-react-native": {
"order": 6, "order": 6,
"icon": "react-native", "icon": "react-native",
"shields": [ "shields": [
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-react-native", "href": "https://www.npmjs.com/package/lucide-react-native" }, {
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-react-native", "href": "https://www.npmjs.com/package/lucide-react-native" } "alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-react-native",
"href": "https://www.npmjs.com/package/lucide-react-native"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-react-native",
"href": "https://www.npmjs.com/package/lucide-react-native"
}
] ]
}, },
"lucide-angular": { "lucide-angular": {
"order": 7, "order": 7,
"icon": "angular", "icon": "angular",
"shields": [ "shields": [
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-angular", "href": "https://www.npmjs.com/package/lucide-angular" }, {
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-angular", "href": "https://www.npmjs.com/package/lucide-angular" } "alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-angular",
"href": "https://www.npmjs.com/package/lucide-angular"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-angular",
"href": "https://www.npmjs.com/package/lucide-angular"
}
] ]
}, },
"lucide-static": { "lucide-static": {
"order": 8, "order": 8,
"icon": "svg", "icon": "svg",
"shields": [ "shields": [
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-static", "href": "https://www.npmjs.com/package/lucide-static" }, {
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-static", "href": "https://www.npmjs.com/package/lucide-static" } "alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-static",
"href": "https://www.npmjs.com/package/lucide-static"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-static",
"href": "https://www.npmjs.com/package/lucide-static"
}
] ]
}, },
"lucide-flutter": { "lucide-flutter": {
"order": 9, "order": 9,
"icon": "flutter", "icon": "flutter",
"shields": [ "shields": [
{ "alt": "flutter", "src": "https://img.shields.io/pub/v/lucide_icons", "href": "https://img.shields.io/pub/v/lucide_icons" } {
"alt": "flutter",
"src": "https://img.shields.io/pub/v/lucide_icons",
"href": "https://img.shields.io/pub/v/lucide_icons"
}
] ]
} }
} }

View File

@@ -17,21 +17,62 @@ const Backdrop = ({ src, color = 'red', backdropString }: BackdropProps): JSX.El
patternUnits="userSpaceOnUse" patternUnits="userSpaceOnUse"
patternTransform="rotate(45 50 50)" patternTransform="rotate(45 50 50)"
> >
<line stroke={color} strokeWidth={0.1} y2={1} /> <line
<line stroke={color} strokeWidth={0.1} y2={1} /> stroke={color}
strokeWidth={0.1}
y2={1}
/>
<line
stroke={color}
strokeWidth={0.1}
y2={1}
/>
</pattern> </pattern>
</defs> </defs>
<mask id="svg-preview-backdrop-mask-outline" maskUnits="userSpaceOnUse"> <mask
<g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} /> id="svg-preview-backdrop-mask-outline"
<g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} /> maskUnits="userSpaceOnUse"
>
<g
stroke="#fff"
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
<g
dangerouslySetInnerHTML={{ __html: src }}
strokeWidth={2.05}
/>
</mask> </mask>
<mask id="svg-preview-backdrop-mask-fill" maskUnits="userSpaceOnUse"> <mask
<g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} /> id="svg-preview-backdrop-mask-fill"
<g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} /> maskUnits="userSpaceOnUse"
<g strokeWidth={1.75} dangerouslySetInnerHTML={{ __html: backdropString }} /> >
<g
stroke="#fff"
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
<g
dangerouslySetInnerHTML={{ __html: src }}
strokeWidth={2.05}
/>
<g
strokeWidth={1.75}
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
</mask> </mask>
<g strokeWidth={2.25} stroke="url(#pattern)" mask={'url(#svg-preview-backdrop-mask-outline)'}> <g
<rect x="0" y="0" width="24" height="24" fill="url(#pattern)" opacity={0.5} stroke="none" /> strokeWidth={2.25}
stroke="url(#pattern)"
mask={'url(#svg-preview-backdrop-mask-outline)'}
>
<rect
x="0"
y="0"
width="24"
height="24"
fill="url(#pattern)"
opacity={0.5}
stroke="none"
/>
</g> </g>
<rect <rect
x="0" x="0"

View File

@@ -10,7 +10,11 @@ const Grid = ({
strokeWidth: number; strokeWidth: number;
radius: number; radius: number;
} & PathProps<'stroke', 'strokeWidth'>) => ( } & PathProps<'stroke', 'strokeWidth'>) => (
<g className="svg-preview-grid-group" strokeLinecap="butt" {...props}> <g
className="svg-preview-grid-group"
strokeLinecap="butt"
{...props}
>
<rect <rect
className="svg-preview-grid-rect" className="svg-preview-grid-rect"
width={24 - props.strokeWidth} width={24 - props.strokeWidth}
@@ -21,11 +25,29 @@ const Grid = ({
fill={fill} fill={fill}
/> />
<path <path
strokeDasharray={'0 0.1 ' + '0.1 0.15 '.repeat(11) + '0 0.15'}
strokeWidth={0.1}
d={ d={
props.d || props.d ||
new Array(Math.floor(24 - 1)) new Array(Math.floor(24 - 1))
.fill(null) .fill(null)
.flatMap((_, i) => [ .map((_, i) => i)
.filter((i) => i % 3 !== 2)
.flatMap((i) => [
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
])
.join('')
}
/>
<path
d={
props.d ||
new Array(Math.floor(24 - 1))
.fill(null)
.map((_, i) => i)
.filter((i) => i % 3 === 2)
.flatMap((i) => [
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`, `M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`, `M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
]) ])
@@ -44,15 +66,21 @@ const Shadow = ({
paths: Path[]; paths: Path[];
} & PathProps<'stroke' | 'strokeWidth' | 'strokeOpacity', 'd'>) => { } & PathProps<'stroke' | 'strokeWidth' | 'strokeOpacity', 'd'>) => {
const groupedPaths = Object.entries( const groupedPaths = Object.entries(
paths.reduce((groups, val) => { paths.reduce(
const key = val.c.id; (groups, val) => {
groups[key] = [...(groups[key] || []), val]; const key = val.c.id;
return groups; groups[key] = [...(groups[key] || []), val];
}, {} as Record<number, Path[]>) return groups;
},
{} as Record<number, Path[]>,
),
); );
return ( return (
<> <>
<g className="svg-preview-shadow-mask-group" {...props}> <g
className="svg-preview-shadow-mask-group"
{...props}
>
{groupedPaths.map(([id, paths]) => ( {groupedPaths.map(([id, paths]) => (
<mask <mask
id={`svg-preview-shadow-mask-${id}`} id={`svg-preview-shadow-mask-${id}`}
@@ -61,7 +89,15 @@ const Shadow = ({
strokeWidth={props.strokeWidth} strokeWidth={props.strokeWidth}
stroke="#000" stroke="#000"
> >
<rect x={0} y={0} width={24} height={24} fill="#fff" stroke="none" rx={radius} /> <rect
x={0}
y={0}
width={24}
height={24}
fill="#fff"
stroke="none"
rx={radius}
/>
<path <path
d={paths d={paths
.flatMap(({ prev, next }) => [ .flatMap(({ prev, next }) => [
@@ -74,9 +110,16 @@ const Shadow = ({
</mask> </mask>
))} ))}
</g> </g>
<g className="svg-preview-shadow-group" {...props}> <g
className="svg-preview-shadow-group"
{...props}
>
{paths.map(({ d, c: { id } }, i) => ( {paths.map(({ d, c: { id } }, i) => (
<path key={i} mask={`url(#svg-preview-shadow-mask-${id})`} d={d} /> <path
key={i}
mask={`url(#svg-preview-shadow-mask-${id})`}
d={d}
/>
))} ))}
<path <path
d={paths d={paths
@@ -94,9 +137,16 @@ const ColoredPath = ({
paths, paths,
...props ...props
}: { paths: Path[]; colors: string[] } & PathProps<never, 'd' | 'stroke'>) => ( }: { paths: Path[]; colors: string[] } & PathProps<never, 'd' | 'stroke'>) => (
<g className="svg-preview-colored-path-group" {...props}> <g
className="svg-preview-colored-path-group"
{...props}
>
{paths.map(({ d, c }, i) => ( {paths.map(({ d, c }, i) => (
<path key={i} d={d} stroke={colors[(c.name === 'path' ? i : c.id) % colors.length]} /> <path
key={i}
d={d}
stroke={colors[(c.name === 'path' ? i : c.id) % colors.length]}
/>
))} ))}
</g> </g>
); );
@@ -138,7 +188,15 @@ const ControlPath = ({
key={i} key={i}
maskUnits="userSpaceOnUse" maskUnits="userSpaceOnUse"
> >
<rect x="0" y="0" width="24" height="24" fill="#fff" stroke="none" rx={radius} /> <rect
x="0"
y="0"
width="24"
height="24"
fill="#fff"
stroke="none"
rx={radius}
/>
<path d={`M${prev.x} ${prev.y}h.01`} /> <path d={`M${prev.x} ${prev.y}h.01`} />
<path d={`M${next.x} ${next.y}h.01`} /> <path d={`M${next.x} ${next.y}h.01`} />
</mask> </mask>
@@ -146,7 +204,10 @@ const ControlPath = ({
); );
})} })}
</g> </g>
<g className="svg-preview-control-path-group" {...props}> <g
className="svg-preview-control-path-group"
{...props}
>
{controlPaths.map(({ d, showMarker }, i) => ( {controlPaths.map(({ d, showMarker }, i) => (
<path <path
key={i} key={i}
@@ -155,18 +216,33 @@ const ControlPath = ({
/> />
))} ))}
</g> </g>
<g className="svg-preview-control-path-marker-group" {...props}> <g
className="svg-preview-control-path-marker-group"
{...props}
>
<path <path
d={controlPaths d={controlPaths
.flatMap(({ prev, next, showMarker }) => .flatMap(({ prev, next, showMarker }) =>
showMarker ? [`M${prev.x} ${prev.y}h.01`, `M${next.x} ${next.y}h.01`] : [] showMarker ? [`M${prev.x} ${prev.y}h.01`, `M${next.x} ${next.y}h.01`] : [],
) )
.join('')} .join('')}
/> />
{controlPaths.map(({ d, prev, next, startMarker, endMarker }, i) => ( {controlPaths.map(({ d, prev, next, startMarker, endMarker }, i) => (
<React.Fragment key={i}> <React.Fragment key={i}>
{startMarker && <circle cx={prev.x} cy={prev.y} r={pointSize / 2} />} {startMarker && (
{endMarker && <circle cx={next.x} cy={next.y} r={pointSize / 2} />} <circle
cx={prev.x}
cy={prev.y}
r={pointSize / 2}
/>
)}
{endMarker && (
<circle
cx={next.x}
cy={next.y}
r={pointSize / 2}
/>
)}
</React.Fragment> </React.Fragment>
))} ))}
</g> </g>
@@ -182,15 +258,16 @@ const Radii = ({
any any
>) => { >) => {
return ( return (
<g className="svg-preview-radii-group" {...props}> <g
className="svg-preview-radii-group"
{...props}
>
{paths.map( {paths.map(
({ c, prev, next, circle }, i) => ({ c, prev, next, circle }, i) =>
circle && ( circle && (
<React.Fragment key={i}> <React.Fragment key={i}>
{c.name !== "circle" && ( {c.name !== 'circle' && (
<path <path d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`} />
d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`}
/>
)} )}
<circle <circle
cy={circle.y} cy={circle.y}
@@ -200,7 +277,7 @@ const Radii = ({
stroke={ stroke={
(Math.round(circle.x * 100) / 100) % 1 !== 0 || (Math.round(circle.x * 100) / 100) % 1 !== 0 ||
(Math.round(circle.y * 100) / 100) % 1 !== 0 (Math.round(circle.y * 100) / 100) % 1 !== 0
? "red" ? 'red'
: undefined : undefined
} }
/> />
@@ -208,11 +285,7 @@ const Radii = ({
cy={circle.y} cy={circle.y}
cx={circle.x} cx={circle.x}
r={circle.r} r={circle.r}
stroke={ stroke={(Math.round(circle.r * 1000) / 1000) % 1 !== 0 ? 'red' : undefined}
(Math.round(circle.r * 1000) / 1000) % 1 !== 0
? "red"
: undefined
}
/> />
</React.Fragment> </React.Fragment>
), ),
@@ -230,13 +303,28 @@ const Handles = ({
>) => { >) => {
console.log(paths); console.log(paths);
return ( return (
<g className="svg-preview-handles-group" {...props}> <g
className="svg-preview-handles-group"
{...props}
>
{paths.map(({ c, prev, next, cp1, cp2 }) => ( {paths.map(({ c, prev, next, cp1, cp2 }) => (
<> <>
{cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />} {cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />}
{cp1 && <circle cy={cp1.y} cx={cp1.x} r={0.25} />} {cp1 && (
<circle
cy={cp1.y}
cx={cp1.x}
r={0.25}
/>
)}
{cp2 && <path d={`M${next.x} ${next.y} ${cp2.x} ${cp2.y}`} />} {cp2 && <path d={`M${next.x} ${next.y} ${cp2.x} ${cp2.y}`} />}
{cp2 && <circle cy={cp2.y} cx={cp2.x} r={0.25} />} {cp2 && (
<circle
cy={cp2.y}
cx={cp2.x}
r={0.25}
/>
)}
</> </>
))} ))}
</g> </g>
@@ -280,9 +368,27 @@ const SvgPreview = React.forwardRef<
{...props} {...props}
> >
<style>{darkModeCss}</style> <style>{darkModeCss}</style>
{showGrid && <Grid strokeWidth={0.1} stroke="#777" strokeOpacity={0.3} radius={1} />} {showGrid && (
<Shadow paths={paths} strokeWidth={4} stroke="#777" radius={1} strokeOpacity={0.15} /> <Grid
<Handles paths={paths} strokeWidth={0.12} stroke="#777" strokeOpacity={0.6} /> strokeWidth={0.1}
stroke="#777"
strokeOpacity={0.3}
radius={1}
/>
)}
<Shadow
paths={paths}
strokeWidth={4}
stroke="#777"
radius={1}
strokeOpacity={0.15}
/>
<Handles
paths={paths}
strokeWidth={0.12}
stroke="#777"
strokeOpacity={0.6}
/>
<ColoredPath <ColoredPath
paths={paths} paths={paths}
colors={[ colors={[
@@ -307,8 +413,19 @@ const SvgPreview = React.forwardRef<
stroke="#777" stroke="#777"
strokeOpacity={0.3} strokeOpacity={0.3}
/> />
<ControlPath radius={1} paths={paths} pointSize={1} stroke="#fff" strokeWidth={0.125} /> <ControlPath
<Handles paths={paths} strokeWidth={0.12} stroke="#FFF" strokeOpacity={0.3} /> radius={1}
paths={paths}
pointSize={1}
stroke="#fff"
strokeWidth={0.125}
/>
<Handles
paths={paths}
strokeWidth={0.12}
stroke="#FFF"
strokeOpacity={0.3}
/>
{children} {children}
</svg> </svg>
); );

View File

@@ -16,7 +16,7 @@ export type Path = {
export type PathProps< export type PathProps<
RequiredProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>, RequiredProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
NeverProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement> NeverProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
> = Required<Pick<React.SVGProps<SVGElement & SVGRectElement & SVGCircleElement>, RequiredProps>> & > = Required<Pick<React.SVGProps<SVGElement & SVGRectElement & SVGCircleElement>, RequiredProps>> &
Omit< Omit<
React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>, React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>,

View File

@@ -51,7 +51,7 @@ export const getCommands = (src: string) =>
getNodes(src) getNodes(src)
.map(convertToPathNode) .map(convertToPathNode)
.flatMap(({ d, name }, idx) => .flatMap(({ d, name }, idx) =>
new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })) new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })),
); );
export const getPaths = (src: string) => { export const getPaths = (src: string) => {
@@ -60,10 +60,10 @@ export const getPaths = (src: string) => {
let prev: Point | undefined = undefined; let prev: Point | undefined = undefined;
let start: Point | undefined = undefined; let start: Point | undefined = undefined;
const addPath = ( const addPath = (
c: typeof commands[number], c: (typeof commands)[number],
next: Point, next: Point,
d?: string, d?: string,
extras?: { circle?: Path['circle']; cp1?: Path['cp1']; cp2?: Path['cp2'] } extras?: { circle?: Path['circle']; cp1?: Path['cp1']; cp2?: Path['cp2'] },
) => { ) => {
assert(prev); assert(prev);
paths.push({ paths.push({
@@ -153,7 +153,7 @@ export const getPaths = (src: string) => {
{ {
cp1: { x: prev.x - reflectedCp1.x, y: prev.y - reflectedCp1.y }, cp1: { x: prev.x - reflectedCp1.x, y: prev.y - reflectedCp1.y },
cp2: { x: c.x2, y: c.y2 }, cp2: { x: c.x2, y: c.y2 },
} },
); );
break; break;
} }
@@ -169,7 +169,7 @@ export const getPaths = (src: string) => {
assert(prev); assert(prev);
const backTrackCP = ( const backTrackCP = (
index: number, index: number,
currentPoint: { x: number; y: number } currentPoint: { x: number; y: number },
): { x: number; y: number } => { ): { x: number; y: number } => {
const previousCommand = commands[index - 1]; const previousCommand = commands[index - 1];
if (!previousCommand) { if (!previousCommand) {
@@ -211,7 +211,7 @@ export const getPaths = (src: string) => {
{ {
cp1: { x: prevCP.x, y: prevCP.y }, cp1: { x: prevCP.x, y: prevCP.y },
cp2: { x: prevCP.x, y: prevCP.y }, cp2: { x: prevCP.x, y: prevCP.y },
} },
); );
break; break;
} }
@@ -226,13 +226,13 @@ export const getPaths = (src: string) => {
c.lArcFlag, c.lArcFlag,
c.sweepFlag, c.sweepFlag,
c.x, c.x,
c.y c.y,
); );
addPath( addPath(
c, c,
c, c,
`M ${prev.x} ${prev.y} A${c.rX} ${c.rY} ${c.xRot} ${c.lArcFlag} ${c.sweepFlag} ${c.x} ${c.y}`, `M ${prev.x} ${prev.y} A${c.rX} ${c.rY} ${c.xRot} ${c.lArcFlag} ${c.sweepFlag} ${c.x} ${c.y}`,
{ circle: c.rX === c.rY ? { ...center, r: c.rX } : undefined } { circle: c.rX === c.rY ? { ...center, r: c.rX } : undefined },
); );
break; break;
} }
@@ -253,7 +253,7 @@ export const arcEllipseCenter = (
fa: number, fa: number,
fs: number, fs: number,
x2: number, x2: number,
y2: number y2: number,
) => { ) => {
const phi = (a * Math.PI) / 180; const phi = (a * Math.PI) / 180;
@@ -280,7 +280,7 @@ export const arcEllipseCenter = (
sign * sign *
Math.sqrt( Math.sqrt(
Math.max(rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p, 0) / Math.max(rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p, 0) /
(rx * rx * y1p * y1p + ry * ry * x1p * x1p) (rx * rx * y1p * y1p + ry * ry * x1p * x1p),
); );
const V2 = [(rx * y1p) / ry, (-ry * x1p) / rx]; const V2 = [(rx * y1p) / ry, (-ry * x1p) / rx];

View File

@@ -1,28 +1,34 @@
import fs from "fs"; import fs from 'fs';
import path from "path"; import path from 'path';
import {Category, IconEntity} from "../theme/types"; import { Category, IconEntity } from '../theme/types';
const directory = path.join(process.cwd(), "../categories"); const directory = path.join(process.cwd(), '../categories');
export function getAllCategoryFiles(): Category[] { export function getAllCategoryFiles(): Category[] {
const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json'); const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json');
return fileNames.map((fileName) => { return fileNames.map((fileName) => {
const name = path.basename(fileName, '.json') const name = path.basename(fileName, '.json');
const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8') const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8');
const parsedFileContent = JSON.parse(fileContent) const parsedFileContent = JSON.parse(fileContent);
return { return {
name, name,
title: parsedFileContent.title, title: parsedFileContent.title,
} };
}); });
} }
export function mapCategoryIconCount(categories: Category[], icons: { categories: IconEntity['categories'] }[]) { export function mapCategoryIconCount(
categories: Category[],
icons: { categories: IconEntity['categories'] }[],
) {
return categories.map((category) => ({ return categories.map((category) => ({
...category, ...category,
iconCount: icons.reduce((acc, curr) => (curr.categories.includes(category.name) ? ++acc : acc), 0) iconCount: icons.reduce(
})) (acc, curr) => (curr.categories.includes(category.name) ? ++acc : acc),
0,
),
}));
} }

View File

@@ -1,24 +1,18 @@
import { import { bundledLanguages, type ThemeRegistration } from 'shikiji';
bundledLanguages, import { getHighlighter } from 'shikiji';
type ThemeRegistration
} from 'shikiji'
import {
getHighlighter,
} from 'shikiji'
type CodeExampleType = { type CodeExampleType = {
title: string, title: string;
language: string, language: string;
code: string, code: string;
}[] }[];
const getIconCodes = (): CodeExampleType => { const getIconCodes = (): CodeExampleType => {
return [ return [
{ {
language: 'html', language: 'html',
title: 'HTML', title: 'HTML',
code: `<i data-lucide="Name"></i>` code: `<i data-lucide="Name"></i>`,
}, },
{ {
language: 'tsx', language: 'tsx',
@@ -109,36 +103,37 @@ import { LucideAngularModule, PascalCase } from 'lucide-angular';
<div class="icon-Name"></div> <div class="icon-Name"></div>
`, `,
} },
] ];
} };
export type ThemeOptions = export type ThemeOptions =
| ThemeRegistration | ThemeRegistration
| { light: ThemeRegistration; dark: ThemeRegistration } | { light: ThemeRegistration; dark: ThemeRegistration };
const highLightCode = async (code: string, lang: string, active?: boolean) => { const highLightCode = async (code: string, lang: string, active?: boolean) => {
const highlighter = await getHighlighter({ const highlighter = await getHighlighter({
themes: ['github-light', 'github-dark'], themes: ['github-light', 'github-dark'],
langs: Object.keys(bundledLanguages) langs: Object.keys(bundledLanguages),
}) });
const highlightedCode = highlighter.codeToHtml(code, { const highlightedCode = highlighter
lang, .codeToHtml(code, {
themes: { lang,
light: 'github-light', themes: {
dark: 'github-dark' light: 'github-light',
}, dark: 'github-dark',
defaultColor: false },
}).replace('shiki-themes', 'shiki-themes vp-code') defaultColor: false,
})
.replace('shiki-themes', 'shiki-themes vp-code');
return `<div class="language-${lang} ${active ? 'active' : ''}"> return `<div class="language-${lang} ${active ? 'active' : ''}">
<button title="Copy Code" class="copy"></button> <button title="Copy Code" class="copy"></button>
<span class="lang">${lang}</span> <span class="lang">${lang}</span>
${highlightedCode} ${highlightedCode}
</div>` </div>`;
} };
export default async function createCodeExamples() { export default async function createCodeExamples() {
const codes = getIconCodes(); const codes = getIconCodes();
@@ -153,7 +148,7 @@ export default async function createCodeExamples() {
language: language, language: language,
code: codeString, code: codeString,
}; };
}) });
return Promise.all(codeExamplePromises); return Promise.all(codeExamplePromises);
} }

View File

@@ -1,38 +1,42 @@
import { promises as fs, constants } from 'fs'; import { promises as fs, constants } from 'fs';
import path from 'path'; import path from 'path';
import yaml from 'js-yaml' import yaml from 'js-yaml';
import { PackageItem } from '../theme/types'; import { PackageItem } from '../theme/types';
const fileExist = (filePath) => fs.access(filePath, constants.F_OK).then(() => true).catch(() => false) const fileExist = (filePath) =>
fs
.access(filePath, constants.F_OK)
.then(() => true)
.catch(() => false);
const fetchPackages = async (): Promise<PackageItem[]> => { const fetchPackages = async (): Promise<PackageItem[]> => {
const docsDir = path.resolve(process.cwd(), '../packages'); const docsDir = path.resolve(process.cwd(), '../packages');
const fileNames = await (await fs.readdir(docsDir)).map(filename => ({filename, directory: docsDir})) const fileNames = await (
await fs.readdir(docsDir)
).map((filename) => ({ filename, directory: docsDir }));
const packageJsons = await Promise.all(fileNames.map( async ({filename, directory}) => { const packageJsons = await Promise.all(
const filePath = path.resolve(directory, filename) fileNames.map(async ({ filename, directory }) => {
const fileStat = await fs.lstat(filePath); const filePath = path.resolve(directory, filename);
const fileStat = await fs.lstat(filePath);
if(!fileStat.isDirectory()) return null; if (!fileStat.isDirectory()) return null;
const jsonFilePath = path.resolve(filePath, 'package.json') const jsonFilePath = path.resolve(filePath, 'package.json');
if (await fileExist(jsonFilePath)) { if (await fileExist(jsonFilePath)) {
return JSON.parse( return JSON.parse(await fs.readFile(jsonFilePath, 'utf-8'));
await fs.readFile(jsonFilePath, 'utf-8') }
)
}
const ymlFilePath = path.resolve(filePath, 'pubspec.yaml') const ymlFilePath = path.resolve(filePath, 'pubspec.yaml');
if(await fileExist(ymlFilePath)) { if (await fileExist(ymlFilePath)) {
return yaml.load( return yaml.load(await fs.readFile(ymlFilePath, 'utf-8'));
await fs.readFile(ymlFilePath, 'utf-8') }
);
}
return null return null;
})) }),
);
return packageJsons return packageJsons;
} };
export default fetchPackages; export default fetchPackages;

View File

@@ -1,17 +1,15 @@
export type IconContent = [icon: string, src:string]; export type IconContent = [icon: string, src: string];
async function generateZip(icons: IconContent[]) { async function generateZip(icons: IconContent[]) {
const JSZip = (await import('jszip')).default const JSZip = (await import('jszip')).default;
const zip = new JSZip(); const zip = new JSZip();
const addingZipPromises = icons.map(([name, src]) => const addingZipPromises = icons.map(([name, src]) => zip.file(`${name}.svg`, src));
zip.file(`${name}.svg`, src),
);
await Promise.all(addingZipPromises) await Promise.all(addingZipPromises);
return zip.generateAsync({ type: 'blob' }); return zip.generateAsync({ type: 'blob' });
} }
export default generateZip export default generateZip;

View File

@@ -1,17 +1,15 @@
import { createLucideIcon } from "lucide-react/src/lucide-react" import { createLucideIcon } from 'lucide-react/src/lucide-react';
import { type LucideProps, type IconNode } from "lucide-react/src/createLucideIcon" import { type LucideProps, type IconNode } from 'lucide-react/src/createLucideIcon';
import { IconEntity } from "../theme/types" import { IconEntity } from '../theme/types';
import { renderToStaticMarkup } from 'react-dom/server'; import { renderToStaticMarkup } from 'react-dom/server';
import { IconContent } from "./generateZip"; import { IconContent } from './generateZip';
const getFallbackZip = (icons: IconEntity[], params: LucideProps) => { const getFallbackZip = (icons: IconEntity[], params: LucideProps) => {
return icons return icons.map<IconContent>((icon) => {
.map<IconContent>((icon) => { const Icon = createLucideIcon(icon.name, icon.iconNode as IconNode);
const Icon = createLucideIcon(icon.name, icon.iconNode as IconNode) const src = renderToStaticMarkup(<Icon {...params} />);
const src = renderToStaticMarkup(<Icon {...params} />) return [icon.name, src];
return [icon.name, src] });
}) };
}
export default getFallbackZip;
export default getFallbackZip

View File

@@ -1,34 +1,34 @@
import fs from "fs"; import fs from 'fs';
import path from "path"; import path from 'path';
import { IconNodeWithKeys } from "../theme/types"; import { IconNodeWithKeys } from '../theme/types';
import iconNodes from '../data/iconNodes' import iconNodes from '../data/iconNodes';
import releaseMeta from "../data/releaseMetaData.json"; import releaseMeta from '../data/releaseMetaData.json';
const DATE_OF_FORK = '2020-06-08T16:39:52+0100'; const DATE_OF_FORK = '2020-06-08T16:39:52+0100';
const directory = path.join(process.cwd(), "../icons"); const directory = path.join(process.cwd(), '../icons');
export interface GetDataOptions { export interface GetDataOptions {
withChildKeys?: boolean withChildKeys?: boolean;
} }
export async function getData(name: string) { export async function getData(name: string) {
const jsonPath = path.join(directory, `${name}.json`); const jsonPath = path.join(directory, `${name}.json`);
const jsonContent = fs.readFileSync(jsonPath, "utf8"); const jsonContent = fs.readFileSync(jsonPath, 'utf8');
const { tags, categories, contributors } = JSON.parse(jsonContent); const { tags, categories, contributors } = JSON.parse(jsonContent);
const iconNode = iconNodes[name] const iconNode = iconNodes[name];
const releaseData = releaseMeta?.[name] ?? { const releaseData = releaseMeta?.[name] ?? {
"createdRelease": { createdRelease: {
"version": "0.0.0", version: '0.0.0',
"date": DATE_OF_FORK date: DATE_OF_FORK,
}, },
"changedRelease": { changedRelease: {
"version": "0.0.0", version: '0.0.0',
"date": DATE_OF_FORK date: DATE_OF_FORK,
} },
} };
return { return {
name, name,
@@ -36,11 +36,11 @@ export async function getData(name: string) {
categories, categories,
iconNode, iconNode,
contributors, contributors,
...releaseData ...releaseData,
}; };
} }
export async function getAllData(): Promise<{ name: string, iconNode: IconNodeWithKeys}[]> { export async function getAllData(): Promise<{ name: string; iconNode: IconNodeWithKeys }[]> {
const names = Object.keys(iconNodes); const names = Object.keys(iconNodes);
return Promise.all(names.map((name) => getData(name))); return Promise.all(names.map((name) => getData(name)));

View File

@@ -1,31 +1,31 @@
import { DefaultTheme, UserConfig } from "vitepress" import { DefaultTheme, UserConfig } from 'vitepress';
const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = { const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
'guide':[ guide: [
{ {
text: 'Introduction', text: 'Introduction',
items: [ items: [
{ text: 'What is lucide?', link: '/guide/' }, { text: 'What is lucide?', link: '/guide/' },
{ text: 'Installation', link: '/guide/installation' }, { text: 'Installation', link: '/guide/installation' },
{ text: 'Comparison', link: '/guide/comparison' } { text: 'Comparison', link: '/guide/comparison' },
] ],
}, },
{ {
text: 'Basics', text: 'Basics',
items: [ items: [
{ {
text: 'Color', text: 'Color',
link: '/guide/basics/color' link: '/guide/basics/color',
}, },
{ {
text: 'Sizing', text: 'Sizing',
link: '/guide/basics/sizing' link: '/guide/basics/sizing',
}, },
{ {
text: 'Stroke width', text: 'Stroke width',
link: '/guide/basics/stroke-width' link: '/guide/basics/stroke-width',
}, },
] ],
}, },
// TODO: Add this section // TODO: Add this section
{ {
@@ -37,14 +37,14 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
// }, // },
{ {
text: 'Global styling', text: 'Global styling',
link: '/guide/advanced/global-styling' link: '/guide/advanced/global-styling',
}, },
// { // {
// text: 'Animations', // text: 'Animations',
// }, // },
{ {
text: 'Filled icons', text: 'Filled icons',
link: '/guide/advanced/filled-icons' link: '/guide/advanced/filled-icons',
}, },
// { // {
// text: 'Combining icons', // text: 'Combining icons',
@@ -55,75 +55,73 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
// { // {
// text: 'Auto importing' // text: 'Auto importing'
// }, // },
] ],
}, },
{ {
text: 'Packages', text: 'Packages',
items: [ items: [
{ {
text: 'Lucide', text: 'Lucide',
link: '/guide/packages/lucide' link: '/guide/packages/lucide',
}, },
{ {
text: 'Lucide React', text: 'Lucide React',
link: '/guide/packages/lucide-react' link: '/guide/packages/lucide-react',
}, },
{ {
text: 'Lucide React Native', text: 'Lucide React Native',
link: '/guide/packages/lucide-react-native' link: '/guide/packages/lucide-react-native',
}, },
{ {
text: 'Lucide Vue', text: 'Lucide Vue',
link: '/guide/packages/lucide-vue-next' link: '/guide/packages/lucide-vue-next',
}, },
{ {
text: 'Lucide Svelte', text: 'Lucide Svelte',
link: '/guide/packages/lucide-svelte' link: '/guide/packages/lucide-svelte',
}, },
{ {
text: 'Lucide Solid', text: 'Lucide Solid',
link: '/guide/packages/lucide-solid' link: '/guide/packages/lucide-solid',
}, },
{ {
text: 'Lucide Preact', text: 'Lucide Preact',
link: '/guide/packages/lucide-preact' link: '/guide/packages/lucide-preact',
}, },
{ {
text: 'Lucide Angular', text: 'Lucide Angular',
link: '/guide/packages/lucide-angular' link: '/guide/packages/lucide-angular',
}, },
{ {
text: 'Lucide Static', text: 'Lucide Static',
link: '/guide/packages/lucide-static' link: '/guide/packages/lucide-static',
}, },
] ],
}, },
{ {
text: 'Contributing', text: 'Contributing',
items: [ items: [
{ {
text: 'Icon Design Principles', text: 'Icon Design Principles',
link: '/guide/design/icon-design-guide' link: '/guide/design/icon-design-guide',
}, },
{ {
text: 'Designing in Illustrator', text: 'Designing in Illustrator',
link: '/guide/design/illustrator-guide' link: '/guide/design/illustrator-guide',
}, },
{ {
text: 'Designing in InkScape', text: 'Designing in InkScape',
link: '/guide/design/inkscape-guide' link: '/guide/design/inkscape-guide',
}, },
{ {
text: 'Designing in Figma', text: 'Designing in Figma',
link: '/guide/design/figma-guide' link: '/guide/design/figma-guide',
}, },
] ],
}, },
], ],
// This should be here to keep the sidebar shown on the icons page // This should be here to keep the sidebar shown on the icons page
'icons': [ icons: [{ text: '', link: '/' }],
{ text: '', link: '/' }, };
],
}
export default sidebar export default sidebar;

View File

@@ -1,16 +1,18 @@
export default { export default {
async load() { async load() {
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest').then(res => { const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest')
if (res.ok) { .then((res) => {
const releaseData = res.json() as Promise<{ tag_name: string }> if (res.ok) {
const releaseData = res.json() as Promise<{ tag_name: string }>;
return releaseData return releaseData;
} }
return null return null;
}).then(res => res.tag_name) })
.then((res) => res.tag_name);
return { return {
version version,
} };
} },
} };

View File

@@ -1,16 +1,17 @@
import iconNodes from '../../../data/iconNodes' import iconNodes from '../../../data/iconNodes';
const getRandomItem = <Item>(items: Item[]): Item => items[Math.floor(Math.random()*items.length)]; const getRandomItem = <Item>(items: Item[]): Item =>
items[Math.floor(Math.random() * items.length)];
export default { export default {
async load() { async load() {
const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode })) const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode }));
const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons)) const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons));
return { return {
icons: randomIcons, icons: randomIcons,
iconsCount: icons.length, iconsCount: icons.length,
} };
} },
} };

View File

@@ -47,7 +47,7 @@ export default {
logo: '/framework-logos/flutter.svg', logo: '/framework-logos/flutter.svg',
label: 'Lucide documentation for Flutter', label: 'Lucide documentation for Flutter',
}, },
] ],
} };
} },
} };

View File

@@ -1,16 +1,15 @@
import { getAllData } from '../../../lib/icons'; import { getAllData } from '../../../lib/icons';
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories'; import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
import iconsMetaData from '../../../data/iconMetaData' import iconsMetaData from '../../../data/iconMetaData';
export default { export default {
async load() { async load() {
let categories = getAllCategoryFiles() let categories = getAllCategoryFiles();
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData)) categories = mapCategoryIconCount(categories, Object.values(iconsMetaData));
return { return {
categories, categories,
} };
} },
} };

View File

@@ -1,13 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue' import { ref, computed, watch } from 'vue'
import { useData } from 'vitepress' import { useData } from 'vitepress'
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue' import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
import { isActive } from 'vitepress/dist/client/shared' import { isActive } from 'vitepress/dist/client/shared'
import { useActiveAnchor } from '../../composables/useActiveAnchor' import { useActiveAnchor } from '../../composables/useActiveAnchor'
import { data } from './CategoryList.data' import { data } from './CategoryList.data'
import CategoryListItem from './CategoryListItem.vue' import CategoryListItem from './CategoryListItem.vue'
import { useCategoryView } from '../../composables/useCategoryView'
const { page } = useData() const { page } = useData()
const { categoryCounts } = useCategoryView();
const categoriesIsActive = computed(() => { const categoriesIsActive = computed(() => {
return isActive(page.value.relativePath, '/icons/categories'); return isActive(page.value.relativePath, '/icons/categories');
@@ -25,14 +27,21 @@ const headers = computed(() => {
level: 2, level: 2,
link: `${linkPrefix}#${name}`, link: `${linkPrefix}#${name}`,
title, title,
iconCount iconCount: categoryCounts.value[name] ?? iconCount,
name
})) }))
}) })
const container = ref() const container = ref()
const marker = ref() const marker = ref()
useActiveAnchor(container, marker) const { setActiveLinkDebounced } = useActiveAnchor(container, marker)
watch(headers, () => {
setTimeout(() => {
setActiveLinkDebounced()
}, 200)
})
</script> </script>
<template> <template>

View File

@@ -7,6 +7,7 @@ interface Header {
slug: string; slug: string;
iconCount: number; iconCount: number;
link: string; link: string;
name: string;
children: Header[]; children: Header[];
} }
@@ -14,36 +15,35 @@ type MenuItem = Omit<Header, 'slug' | 'children'> & {
children?: MenuItem[]; children?: MenuItem[];
}; };
const props = defineProps<{ defineProps<{
headers: MenuItem[]; headers: MenuItem[];
root?: boolean; root?: boolean;
}>(); }>();
const { selectedCategory } = useCategoryView(); const { selectedCategory } = useCategoryView();
function onClick(event: Event) { function onClick(categoryName: string) {
const target = selectedCategory.value = categoryName;
(event.target as HTMLElement).nodeName === 'span'
? (event.target as HTMLElement).parentNode
: (event.target as HTMLElement);
const href = (target as HTMLAnchorElement)?.href;
if (href) { const heading = document.querySelector<HTMLAnchorElement>(categoryName);
const id = '#' + href.split('#')[1]; heading?.focus();
const decodedId = decodeURIComponent(id);
selectedCategory.value = decodedId.replace('#', ''); window.history.pushState({}, '', `/icons/categories#${categoryName}`)
const heading = document.querySelector<HTMLAnchorElement>(decodedId);
heading?.focus();
}
} }
</script> </script>
<template> <template>
<ul :class="root ? 'root' : 'nested'"> <ul :class="root ? 'root' : 'nested'">
<li v-for="{ children, link, title, iconCount } in headers"> <li v-for="{ children, link, title, iconCount, name } in headers">
<a class="outline-link" :href="link" @click="onClick" :title="title"> <a
class="outline-link"
:href="link"
@click="onClick(name)"
:title="title"
:class="{
inactive: iconCount === 0,
}"
>
<span> <span>
{{ title }} {{ title }}
</span> </span>
@@ -83,6 +83,10 @@ function onClick(event: Event) {
transition: color 0.25s; transition: color 0.25s;
} }
.outline-link.inactive {
color: var(--vp-c-text-4);
}
.outline-link.nested { .outline-link.nested {
padding-left: 13px; padding-left: 13px;
} }
@@ -93,4 +97,8 @@ function onClick(event: Event) {
font-size: 11px; font-size: 11px;
font-weight: 400; font-weight: 400;
} }
.outline-link.inactive .icon-count {
opacity: 0;
}
</style> </style>

View File

@@ -4,7 +4,7 @@ import IconItem from './IconItem.vue'
const emit = defineEmits(['setActiveIcon']) const emit = defineEmits(['setActiveIcon'])
const props = defineProps<{ defineProps<{
icons: IconEntity[] icons: IconEntity[]
activeIcon?: string activeIcon?: string
overlayMode?: boolean overlayMode?: boolean
@@ -40,7 +40,6 @@ function setActiveIcon(name: string) {
.icons { .icons {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
/* padding: 32px 32px 96px; */
gap: 8px; gap: 8px;
width: 100%; width: 100%;
} }

View File

@@ -1,56 +1,53 @@
<script lang="ts">
import { IconEntity } from '../../types';
type CategoryNameRow = {
type: 'category';
title: string;
name: string;
};
type CategoryIconsRow = {
type: 'icons';
icons: IconEntity[];
};
export type CategoryRow = CategoryNameRow | CategoryIconsRow;
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue';
import { Category } from '../../types';
import IconGrid from './IconGrid.vue' import IconGrid from './IconGrid.vue'
import { vIntersectionObserver } from '@vueuse/components'
defineProps<{ defineProps<{
activeIconName: string activeIconName: string
category: Category categoryRow: CategoryRow
}>() }>()
const emit = defineEmits(['setActiveIcon']) const emit = defineEmits(['setActiveIcon'])
const showIcons = ref(false)
// Added intersection observer to improve performance
const onIntersectionObserver: IntersectionObserverCallback = ([{ isIntersecting }]) => {
showIcons.value = isIntersecting
}
</script> </script>
<template> <template>
<section <h2
class="category" v-if="categoryRow.type === 'category'"
:key="category.name" class="title"
:id="category.name"
v-intersection-observer="onIntersectionObserver"
> >
<h2 class="title" > <a class="header-anchor" :href="`#${categoryRow.name}`" :aria-label="`Permalink to &quot;${categoryRow.title}&quot;`">&ZeroWidthSpace;</a>
<a class="header-anchor" :href="`#${category.name}`" :aria-label="`Permalink to &quot;${category.title}&quot;`">&ZeroWidthSpace;</a> {{ categoryRow.title }}
{{ category.title }} </h2>
</h2> <IconGrid
<IconGrid v-else-if="categoryRow.type === 'icons'"
:activeIcon="activeIconName" :activeIcon="activeIconName"
:icons="category.icons" :icons="categoryRow.icons"
@setActiveIcon="$event => $emit('setActiveIcon', $event)" @setActiveIcon="$event => $emit('setActiveIcon', $event)"
overlayMode overlayMode
:hideIcons="!showIcons" />
/>
</section>
</template> </template>
<style scoped> <style scoped>
.title { .title {
margin-bottom: 24px; margin-bottom: 8px;
font-size: 19px; font-size: 19px;
font-weight: 500; font-weight: 500;
padding-top: 86px; padding: 24px 0 8px;
/* scroll-padding-top: 240px; */
}
.category {
margin-bottom: calc(-86px + 32px);
} }
</style> </style>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, defineAsyncComponent } from 'vue'; import { ref, computed, defineAsyncComponent, onMounted } from 'vue';
import type { IconEntity, Category } from '../../types'; import type { IconEntity, Category } from '../../types';
import useSearch from '../../composables/useSearch'; import useSearch from '../../composables/useSearch';
import InputSearch from '../base/InputSearch.vue'; import InputSearch from '../base/InputSearch.vue';
@@ -8,6 +8,13 @@ import StickyBar from './StickyBar.vue';
import IconsCategory from './IconsCategory.vue'; import IconsCategory from './IconsCategory.vue';
import useFetchTags from '../../composables/useFetchTags'; import useFetchTags from '../../composables/useFetchTags';
import useFetchCategories from '../../composables/useFetchCategories'; import useFetchCategories from '../../composables/useFetchCategories';
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
import chunkArray from '../../utils/chunkArray';
import { CategoryRow } from './IconsCategory.vue';
import useScrollToCategory from '../../composables/useScrollToCategory';
const ICON_SIZE = 56;
const ICON_GRID_GAP = 8;
const props = defineProps<{ const props = defineProps<{
icons: IconEntity[]; icons: IconEntity[];
@@ -17,7 +24,6 @@ const props = defineProps<{
const activeIconName = ref(null); const activeIconName = ref(null);
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput(); const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
const isSearching = computed(() => !!searchQuery.value); const isSearching = computed(() => !!searchQuery.value);
function setActiveIconName(name: string) { function setActiveIconName(name: string) {
@@ -27,6 +33,13 @@ function setActiveIconName(name: string) {
const { execute: fetchTags, data: tags } = useFetchTags(); const { execute: fetchTags, data: tags } = useFetchTags();
const { execute: fetchCategories, data: categoriesMap } = useFetchCategories(); const { execute: fetchCategories, data: categoriesMap } = useFetchCategories();
const overviewEl = ref<HTMLElement | null>(null);
const { width: containerWidth } = useElementSize(overviewEl)
const columnSize = computed(() => {
return Math.floor((containerWidth.value) / ((ICON_SIZE + ICON_GRID_GAP)));
});
const mappedIcons = computed(() => { const mappedIcons = computed(() => {
if (tags.value == null) { if (tags.value == null) {
return props.icons; return props.icons;
@@ -43,9 +56,10 @@ const mappedIcons = computed(() => {
}); });
}); });
const searchResults = useSearch(searchQuery, mappedIcons, [ const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
{ name: 'name', weight: 2 }, { name: 'name', weight: 3 },
{ name: 'tags', weight: 1 }, { name: 'aliases', weight: 3 },
{ name: 'tags', weight: 2 },
]); ]);
const categories = computed(() => { const categories = computed(() => {
@@ -71,9 +85,43 @@ const categories = computed(() => {
icons: searchedCategoryIcons, icons: searchedCategoryIcons,
}; };
}) })
.filter(({ icons }) => icons.length);
}); });
const categoriesList = computed(() => {
return categories.value
.filter(({ icons }) => icons.length)
.reduce<CategoryRow[]>((acc, category) => {
acc.push({ type: 'category', title: category.title, name: category.name });
const categoryIcons = chunkArray(category.icons, columnSize.value);
categoryIcons.forEach((icons) => {
acc.push({ type: 'icons', icons });
});
return acc;
}, []);
});
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
categoriesList,
{
itemHeight: ICON_SIZE + ICON_GRID_GAP,
overscan: 10
},
)
useScrollToCategory({
categories,
categoriesList,
scrollTo,
searchQueryDebounced,
})
onMounted(() => {
containerProps.ref.value = document.documentElement;
useEventListener(window, 'scroll', containerProps.onScroll)
})
function onFocusSearchInput() { function onFocusSearchInput() {
if (tags.value == null) { if (tags.value == null) {
fetchTags(); fetchTags();
@@ -84,28 +132,35 @@ function onFocusSearchInput() {
} }
const NoResults = defineAsyncComponent(() => import('./NoResults.vue')); const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue')); const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
</script> </script>
<template> <template>
<StickyBar class="search-bar category-search"> <div ref="overviewEl" class="overview-container">
<InputSearch <StickyBar class="category-search">
:placeholder="`Search ${icons.length} icons ...`" <InputSearch
v-model="searchQuery" :placeholder="`Search ${icons.length} icons ...`"
class="input-wrapper" v-model="searchQuery"
ref="searchInput" class="input-wrapper"
@focus="onFocusSearchInput" ref="searchInput"
@focus="onFocusSearchInput"
/>
</StickyBar>
<NoResults
v-if="categories.length === 0"
:searchQuery="searchQuery"
@clear="searchQuery = ''"
/> />
</StickyBar> <div v-bind="wrapperProps">
<NoResults v-if="categories.length === 0" :searchQuery="searchQuery" @clear="searchQuery = ''" /> <IconsCategory
<IconsCategory v-for="{ index, data } in list"
v-for="category in categories" :categoryRow="data"
:key="category.name" :activeIconName="activeIconName"
:category="category" @setActiveIcon="setActiveIconName"
:activeIconName="activeIconName" :key="index"
@setActiveIcon="setActiveIconName" />
/> </div>
</div>
<IconDetailOverlay <IconDetailOverlay
v-if="activeIconName != null" v-if="activeIconName != null"
:iconName="activeIconName" :iconName="activeIconName"
@@ -119,6 +174,21 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
} }
.search-bar.category-search { .search-bar.category-search {
margin-bottom: -54px; margin-bottom: 10px;
}
.title {
margin-bottom: 8px;
font-size: 19px;
font-weight: 500;
padding: 24px 0 8px;
}
.icons {
margin-bottom: 8px;
}
.overview-container {
padding-bottom: 288px;
} }
</style> </style>

View File

@@ -1,15 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch, defineAsyncComponent } from 'vue'; import { ref, computed, defineAsyncComponent, onMounted, watch } from 'vue';
import type { IconEntity } from '../../types'; import type { IconEntity } from '../../types';
import { useMediaQuery, useOffsetPagination } from '@vueuse/core'; import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
import IconGrid from './IconGrid.vue'; import IconGrid from './IconGrid.vue';
import InputSearch from '../base/InputSearch.vue'; import InputSearch from '../base/InputSearch.vue';
import useSearch from '../../composables/useSearch'; import useSearch from '../../composables/useSearch';
import EndOfPage from '../base/EndOfPage.vue';
import useSearchInput from '../../composables/useSearchInput'; import useSearchInput from '../../composables/useSearchInput';
import StickyBar from './StickyBar.vue'; import StickyBar from './StickyBar.vue';
import useFetchTags from '../../composables/useFetchTags'; import useFetchTags from '../../composables/useFetchTags';
import useFetchCategories from '../../composables/useFetchCategories'; import useFetchCategories from '../../composables/useFetchCategories';
import chunkArray from '../../utils/chunkArray';
const ICON_SIZE = 56;
const ICON_GRID_GAP = 8;
const props = defineProps<{ const props = defineProps<{
icons: IconEntity[]; icons: IconEntity[];
@@ -17,32 +20,16 @@ const props = defineProps<{
const activeIconName = ref(null); const activeIconName = ref(null);
const isExtraLargeScreen = useMediaQuery('(min-width: 1440px)');
const isLargeScreen = useMediaQuery('(min-width: 1280px)');
const isMediumScreen = useMediaQuery('(min-width: 960px)');
const isSmallScreen = useMediaQuery('(min-width: 640px)');
const pageSize = computed(() => {
if (isExtraLargeScreen.value) {
return 16 * 20;
}
if (isLargeScreen.value) {
return 16 * 12;
}
if (isMediumScreen.value) {
return 13 * 12;
}
if (isSmallScreen.value) {
return 10 * 10;
}
return 10 * 5;
});
const { execute: fetchTags, data: tags } = useFetchTags(); const { execute: fetchTags, data: tags } = useFetchTags();
const { execute: fetchCategories, data: categories } = useFetchCategories(); const { execute: fetchCategories, data: categories } = useFetchCategories();
const overviewEl = ref<HTMLElement | null>(null);
const { width: containerWidth } = useElementSize(overviewEl)
const columnSize = computed(() => {
return Math.floor((containerWidth.value) / ((ICON_SIZE + ICON_GRID_GAP)));
});
const mappedIcons = computed(() => { const mappedIcons = computed(() => {
if (tags.value == null) { if (tags.value == null) {
return props.icons; return props.icons;
@@ -63,26 +50,33 @@ const mappedIcons = computed(() => {
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput(); const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [ const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
{ name: 'name', weight: 3 }, { name: 'name', weight: 3 },
{ name: 'aliases', weight: 3 },
{ name: 'tags', weight: 2 }, { name: 'tags', weight: 2 },
{ name: 'categories', weight: 1 }, { name: 'categories', weight: 1 },
]); ]);
const { next, currentPage } = useOffsetPagination({ pageSize }); const chunkedIcons = computed(() => {
return chunkArray(searchResults.value, columnSize.value);
const paginatedIcons = computed(() => {
const end = pageSize.value * currentPage.value;
return searchResults.value.slice(0, end);
}); });
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
chunkedIcons,
{
itemHeight: ICON_SIZE + ICON_GRID_GAP,
overscan: 10
},
)
onMounted(() => {
containerProps.ref.value = document.documentElement;
useEventListener(window, 'scroll', containerProps.onScroll)
})
function setActiveIconName(name: string) { function setActiveIconName(name: string) {
activeIconName.value = name; activeIconName.value = name;
} }
watch(searchQueryDebounced, (searchString) => {
currentPage.value = 1;
});
function onFocusSearchInput() { function onFocusSearchInput() {
if (tags.value == null) { if (tags.value == null) {
fetchTags(); fetchTags();
@@ -95,30 +89,40 @@ function onFocusSearchInput() {
const NoResults = defineAsyncComponent(() => import('./NoResults.vue')); const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue')); const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
watch(searchQueryDebounced, () => {
scrollTo(0)
})
</script> </script>
<template> <template>
<StickyBar> <div ref="overviewEl" class="overview-container">
<InputSearch <StickyBar>
:placeholder="`Search ${icons.length} icons ...`" <InputSearch
v-model="searchQuery" :placeholder="`Search ${icons.length} icons ...`"
ref="searchInput" v-model="searchQuery"
class="input-wrapper" ref="searchInput"
@focus="onFocusSearchInput" class="input-wrapper"
@focus="onFocusSearchInput"
/>
</StickyBar>
<NoResults
v-if="list.length === 0"
:searchQuery="searchQuery"
@clear="searchQuery = ''"
/> />
</StickyBar> <div v-bind="wrapperProps" class="icon">
<NoResults <IconGrid
v-if="paginatedIcons.length === 0" v-for="{ index, data: icons } in list"
:searchQuery="searchQuery" :key="index"
@clear="searchQuery = ''" overlayMode
/> :icons="icons"
<IconGrid :activeIcon="activeIconName"
overlayMode @setActiveIcon="setActiveIconName"
:activeIcon="activeIconName" />
:icons="paginatedIcons" </div>
@setActiveIcon="setActiveIconName" </div>
/>
<EndOfPage @end-of-page="next" class="bottom-page" />
<IconDetailOverlay <IconDetailOverlay
v-if="activeIconName != null" v-if="activeIconName != null"
:iconName="activeIconName" :iconName="activeIconName"
@@ -128,10 +132,7 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
<style> <style>
.icons { .icons {
display: grid; margin-bottom: 8px;
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
gap: 8px;
width: 100%;
} }
.icon { .icon {
@@ -142,7 +143,7 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
width: 100%; width: 100%;
} }
.bottom-page { .overview-container {
height: 288px; padding-bottom: 288px;
} }
</style> </style>

View File

@@ -1,9 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import {ref} from 'vue' import { ref, onMounted, computed } from 'vue'
import {bird} from '../../../data/iconNodes' import { bird, squirrel, rabbit } from '../../../data/iconNodes'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon' import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
import {useEventListener} from '@vueuse/core' import {useEventListener} from '@vueuse/core'
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue' import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
import { IconNode } from '../../types'
defineProps<{ defineProps<{
searchQuery: string searchQuery: string
@@ -11,32 +12,48 @@ defineProps<{
defineEmits(['clear']) defineEmits(['clear'])
const birdIcon = ref<HTMLElement>() const animalIcon = ref<HTMLElement>()
const Bird = createLucideIcon('bird', bird) const randomAnimal = computed<IconNode>(() => {
return Math.random() > 0.5 ? squirrel : Math.random() > 0.5 ? rabbit : bird
})
const animalComponent = computed(() => createLucideIcon('animal', randomAnimal.value))
const flip = ref(false) const flip = ref(false)
useEventListener(document, 'mousemove', (mouseEvent) => { onMounted(() => {
const {width, height, x, y} = birdIcon.value.getBoundingClientRect() useEventListener(document, 'mousemove', (mouseEvent) => {
const {width, height, x, y} = animalIcon.value.getBoundingClientRect()
const centerX = (width / 2) + x const centerX = (width / 2) + x
flip.value = mouseEvent.x < centerX flip.value = mouseEvent.x < centerX
})
}) })
</script> </script>
<template> <template>
<div class="no-results"> <div class="no-results">
<Bird class="bird-icon" ref="birdIcon" :class="{ flip }" :strokeWidth="1"/> <component
:is="animalComponent"
class="animal-icon"
ref="animalIcon"
:class="{ flip }"
:strokeWidth="1"
/>
<h2 class="no-results-text"> <h2 class="no-results-text">
No icons found for '{{ searchQuery }}' No icons found for '{{ searchQuery }}'
</h2> </h2>
<VPButton text="Clear your search and try again" theme="alt" @click="$emit('clear')"/> <VPButton
text="Clear your search and try again"
theme="alt"
@click="$emit('clear')"
/>
<span class="text-divider">or</span> <span class="text-divider">or</span>
<VPButton text="Search on Github issues" <VPButton
theme="alt" text="Search on Github issues"
:href="`https://github.com/lucide-icons/lucide/issues?q=is%3Aopen+${searchQuery}`" theme="alt"
target="_blank" :href="`https://github.com/lucide-icons/lucide/issues?q=is%3Aopen+${searchQuery}`"
target="_blank"
/> />
</div> </div>
</template> </template>
@@ -48,7 +65,7 @@ useEventListener(document, 'mousemove', (mouseEvent) => {
align-items: center; align-items: center;
} }
.bird-icon { .animal-icon {
width: 160px; width: 160px;
height: 160px; height: 160px;
color: var(--vp-c-neutral); color: var(--vp-c-neutral);
@@ -56,12 +73,12 @@ useEventListener(document, 'mousemove', (mouseEvent) => {
margin-top: 72px; margin-top: 72px;
} }
.bird-icon.flip { .animal-icon.flip {
transform: rotateY(180deg); transform: rotateY(180deg);
} }
@media (min-width: 960px) { @media (min-width: 960px) {
.bird-icon { .animal-icon {
width: 240px; width: 240px;
height: 240px; height: 240px;
} }

View File

@@ -12,7 +12,7 @@
.confetti-button:before, .confetti-button:before,
.confetti-button:after { .confetti-button:after {
position: absolute; position: absolute;
content: ""; content: '';
display: block; display: block;
width: 140%; width: 140%;
max-width: 160px; max-width: 160px;
@@ -41,8 +41,16 @@
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%), radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%), radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%); radial-gradient(circle, var(--confetti-color) 20%, transparent 20%);
background-size: 10% 10%, 20% 20%, 15% 15%, 20% 20%, 18% 18%, 10% 10%, 15% 15%, background-size:
10% 10%, 18% 18%; 10% 10%,
20% 20%,
15% 15%,
20% 20%,
18% 18%,
10% 10%,
15% 15%,
10% 10%,
18% 18%;
} }
.confetti-button:after { .confetti-button:after {
@@ -55,7 +63,14 @@
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%), radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%), radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%); radial-gradient(circle, var(--confetti-color) 20%, transparent 20%);
background-size: 15% 15%, 20% 20%, 18% 18%, 20% 20%, 15% 15%, 10% 10%, 20% 20%; background-size:
15% 15%,
20% 20%,
18% 18%,
20% 20%,
15% 15%,
10% 10%,
20% 20%;
} }
.confetti-button.animate:before { .confetti-button.animate:before {
@@ -70,35 +85,89 @@
@keyframes topBubbles { @keyframes topBubbles {
0% { 0% {
color: rgb(var(--text-color) / 0); color: rgb(var(--text-color) / 0);
background-position: 5% 90%, 10% 90%, 10% 90%, 15% 90%, 25% 90%, 25% 90%, background-position:
40% 90%, 55% 90%, 70% 90%; 5% 90%,
10% 90%,
10% 90%,
15% 90%,
25% 90%,
25% 90%,
40% 90%,
55% 90%,
70% 90%;
} }
30% { 30% {
color: rgb(var(--text-color) / 1); color: rgb(var(--text-color) / 1);
} }
50% { 50% {
background-position: 0% 80%, 0% 20%, 10% 40%, 20% 0%, 30% 30%, 22% 50%, background-position:
50% 50%, 65% 20%, 90% 30%; 0% 80%,
0% 20%,
10% 40%,
20% 0%,
30% 30%,
22% 50%,
50% 50%,
65% 20%,
90% 30%;
} }
100% { 100% {
background-position: 0% 70%, 0% 10%, 10% 30%, 20% -10%, 30% 20%, 22% 40%, background-position:
50% 40%, 65% 10%, 90% 20%; 0% 70%,
background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%; 0% 10%,
10% 30%,
20% -10%,
30% 20%,
22% 40%,
50% 40%,
65% 10%,
90% 20%;
background-size:
0% 0%,
0% 0%,
0% 0%,
0% 0%,
0% 0%,
0% 0%;
color: rgb(var(--text-color) / 0); color: rgb(var(--text-color) / 0);
} }
} }
@keyframes bottomBubbles { @keyframes bottomBubbles {
0% { 0% {
background-position: 10% -10%, 30% 10%, 55% -10%, 70% -10%, 85% -10%, background-position:
70% -10%, 70% 0%; 10% -10%,
30% 10%,
55% -10%,
70% -10%,
85% -10%,
70% -10%,
70% 0%;
} }
50% { 50% {
background-position: 0% 80%, 20% 80%, 45% 60%, 60% 100%, 75% 70%, 95% 60%, background-position:
0% 80%,
20% 80%,
45% 60%,
60% 100%,
75% 70%,
95% 60%,
105% 0%; 105% 0%;
} }
100% { 100% {
background-position: 0% 90%, 20% 90%, 45% 70%, 60% 110%, 75% 80%, 95% 70%, background-position:
0% 90%,
20% 90%,
45% 70%,
60% 110%,
75% 80%,
95% 70%,
110% 10%; 110% 10%;
background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%; background-size:
0% 0%,
0% 0%,
0% 0%,
0% 0%,
0% 0%,
0% 0%;
} }
} }

View File

@@ -1,21 +1,22 @@
import packageData from '../../../data/packageData.json'; import packageData from '../../../data/packageData.json';
import thirdPartyPackages from '../../../data/packageData.thirdParty.json'; import thirdPartyPackages from '../../../data/packageData.thirdParty.json';
import fetchPackages from "../../../lib/fetchPackages"; import fetchPackages from '../../../lib/fetchPackages';
export default { export default {
async load() { async load() {
const packages = await fetchPackages(); const packages = await fetchPackages();
return { return {
packages: packages packages: packages
.filter(p => p.name in packageData) .filter((p) => p.name in packageData)
.map((pData) => ({ .map((pData) => ({
...pData, ...pData,
...packageData[pData.name], ...packageData[pData.name],
documentation: `/guide/packages/${pData.name}`, documentation: `/guide/packages/${pData.name}`,
source: `https://github.com/lucide-icons/lucide/tree/main/packages/${pData.name}`, source: `https://github.com/lucide-icons/lucide/tree/main/packages/${pData.name}`,
icon: `/framework-logos/${packageData[pData.name].icon}.svg`, icon: `/framework-logos/${packageData[pData.name].icon}.svg`,
})).sort((a, b) => a.order - b.order), }))
.sort((a, b) => a.order - b.order),
thirdPartyPackages, thirdPartyPackages,
}; };
} },
} };

View File

@@ -1,73 +1,76 @@
import { onMounted, onUpdated, onUnmounted } from 'vue'; import { onMounted, onUpdated, onUnmounted } from 'vue';
import { throttleAndDebounce } from 'vitepress/dist/client/theme-default/support/utils' import { throttleAndDebounce } from 'vitepress/dist/client/theme-default/support/utils';
/* /*
* This file is compied and adjusted from vitepress/dist/client/theme-default/composables/useActiveAnchor.ts * This file is compied and adjusted from vitepress/dist/client/theme-default/composables/useActiveAnchor.ts
*/ */
export function useActiveAnchor(container, marker) { export function useActiveAnchor(container, marker) {
const onScroll = throttleAndDebounce(setActiveLink, 100); const setActiveLinkDebounced = throttleAndDebounce(setActiveLink, 100);
let prevActiveLink = null; let prevActiveLink = null;
onMounted(() => { onMounted(() => {
requestAnimationFrame(setActiveLink); requestAnimationFrame(setActiveLink);
window.addEventListener('scroll', onScroll); window.addEventListener('scroll', setActiveLinkDebounced);
}); });
onUpdated(() => { onUpdated(() => {
// sidebar update means a route change // sidebar update means a route change
activateLink(location.hash); activateLink(location.hash);
}); });
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('scroll', onScroll); window.removeEventListener('scroll', setActiveLinkDebounced);
}); });
function setActiveLink() { function setActiveLink() {
const links = [].slice.call(container.value.querySelectorAll('.outline-link')); const links = [].slice.call(container.value.querySelectorAll('.outline-link'));
const anchors = [].slice const anchors = [].slice
.call(document.querySelectorAll('.content .header-anchor')) .call(document.querySelectorAll('.content .header-anchor'))
.filter((anchor) => { .filter((anchor) => {
return links.some((link) => { return links.some((link) => {
return link.hash === anchor.hash && anchor.offsetParent !== null; return link.hash === anchor.hash && anchor.offsetParent !== null;
}); });
}); });
const scrollY = window.scrollY; const scrollY = window.scrollY;
const innerHeight = window.innerHeight; const innerHeight = window.innerHeight;
const offsetHeight = document.body.offsetHeight; const offsetHeight = document.body.offsetHeight;
const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1; const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1;
// page bottom - highlight last one // page bottom - highlight last one
if (anchors.length && isBottom) { if (anchors.length && isBottom) {
activateLink(anchors[anchors.length - 1].hash); activateLink(anchors[anchors.length - 1].hash);
return; return;
} }
for (let i = 0; i < anchors.length; i++) { for (let i = 0; i < anchors.length; i++) {
const anchor = anchors[i]; const anchor = anchors[i];
const nextAnchor = anchors[i + 1]; const nextAnchor = anchors[i + 1];
const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor); const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor);
if (isActive) { if (isActive) {
activateLink(hash); activateLink(hash);
return; return;
}
} }
}
} }
function activateLink(hash) { function activateLink(hash) {
if (prevActiveLink) { if (prevActiveLink) {
prevActiveLink.classList.remove('active'); prevActiveLink.classList.remove('active');
} }
if (hash !== null) { if (hash !== null) {
prevActiveLink = container.value.querySelector(`a[href="${decodeURIComponent(hash)}"]`); prevActiveLink = container.value.querySelector(`a[href="${decodeURIComponent(hash)}"]`);
} }
const activeLink = prevActiveLink; const activeLink = prevActiveLink;
if (activeLink) { if (activeLink) {
activeLink.classList.add('active'); activeLink.classList.add('active');
marker.value.style.top = activeLink.offsetTop + 5 + 'px'; marker.value.style.top = activeLink.offsetTop + 5 + 'px';
marker.value.style.opacity = '1'; marker.value.style.opacity = '1';
} } else {
else { marker.value.style.top = '33px';
marker.value.style.top = '33px'; marker.value.style.opacity = '0';
marker.value.style.opacity = '0'; }
}
} }
return {
setActiveLinkDebounced,
};
} }
const PAGE_OFFSET = 64; const PAGE_OFFSET = 128;
function getAnchorTop(anchor) { function getAnchorTop(anchor) {
return anchor.parentElement.offsetTop - PAGE_OFFSET; return anchor.parentElement.offsetTop - PAGE_OFFSET;
@@ -75,13 +78,13 @@ function getAnchorTop(anchor) {
function isAnchorActive(index, anchor, nextAnchor) { function isAnchorActive(index, anchor, nextAnchor) {
const scrollTop = window.scrollY; const scrollTop = window.scrollY;
if (index === 0 && scrollTop === 0) { if (index === 0 && scrollTop === 0) {
return [true, null]; return [true, anchor.hash];
} }
if (scrollTop < getAnchorTop(anchor)) { if (scrollTop < getAnchorTop(anchor)) {
return [false, null]; return [false, null];
} }
if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor)) { if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor)) {
return [true, anchor.hash]; return [true, anchor.hash];
} }
return [false, null]; return [false, null];
} }

View File

@@ -1,25 +1,38 @@
import { import { useRoute } from 'vitepress';
ref, inject, Ref import { ref, inject, Ref, onMounted, watch } from 'vue';
} from 'vue';
export const CATEGORY_VIEW_CONTEXT = Symbol('categoryView'); export const CATEGORY_VIEW_CONTEXT = Symbol('categoryView');
interface CategoryViewContext { interface CategoryViewContext {
selectedCategory: Ref<string> selectedCategory: Ref<string>;
categoryCounts: Ref<Record<string, number>> categoryCounts: Ref<Record<string, number>>;
} }
export const categoryViewContext = { export const categoryViewContext = {
selectedCategory: ref(''), selectedCategory: ref(),
categoryCounts: ref({}), categoryCounts: ref({}),
}; };
export function useCategoryView(): CategoryViewContext { export function useCategoryView(): CategoryViewContext {
const context = inject<CategoryViewContext>(CATEGORY_VIEW_CONTEXT); const context = inject<CategoryViewContext>(CATEGORY_VIEW_CONTEXT);
const route = useRoute();
if (!context) { if (!context) {
throw new Error('useCategoryView must be used with categoryView context'); throw new Error('useCategoryView must be used with categoryView context');
} }
onMounted(() => {
if (window.location.hash) {
context.selectedCategory.value = decodeURIComponent(window.location.hash.slice(1));
}
});
watch(route, (currentRoute) => {
if (currentRoute.path !== '/icons/categories') {
context.selectedCategory.value = '';
context.categoryCounts.value = {};
}
});
return context; return context;
} }

View File

@@ -1,8 +1,8 @@
import { ref } from "vue"; import { ref } from 'vue';
export default function useConfetti() { export default function useConfetti() {
const animate = ref(false) const animate = ref(false);
const confettiText = ref('confetti!') const confettiText = ref('confetti!');
function confetti() { function confetti() {
animate.value = true; animate.value = true;
@@ -15,6 +15,6 @@ export default function useConfetti() {
return { return {
animate, animate,
confetti, confetti,
confettiText confettiText,
} };
} }

View File

@@ -1,12 +1,12 @@
import { useFetch } from "@vueuse/core" import { useFetch } from '@vueuse/core';
const useFetchCategories = () => useFetch<Record<string, string[]>>( const useFetchCategories = () =>
`${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/categories`, useFetch<Record<string, string[]>>(
{ `${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/categories`,
immediate: {
typeof window !== 'undefined' immediate:
&& new URLSearchParams(window.location.search).has('search'), typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('search'),
} },
).json() ).json();
export default useFetchCategories export default useFetchCategories;

View File

@@ -1,12 +1,12 @@
import { useFetch } from "@vueuse/core" import { useFetch } from '@vueuse/core';
const useFetchTags = () => useFetch<Record<string, string[]>>( const useFetchTags = () =>
`${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/tags`, useFetch<Record<string, string[]>>(
{ `${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/tags`,
immediate: {
typeof window !== 'undefined' immediate:
&& new URLSearchParams(window.location.search).has('search'), typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('search'),
} },
).json() ).json();
export default useFetchTags export default useFetchTags;

Some files were not shown because too many files have changed in this diff Show More