Compare commits

...

122 Commits

Author SHA1 Message Date
Jakob Guddas
4bf91a3c51 feat(icons): added image-play icon (#2054)
* Added icons/image-play.svg

* Added icons/image-play.json

* Updated icons/image-play.json
2024-04-10 09:26:32 +02:00
Jakob Guddas
3cde4f2a41 fix(icons): changed cable icon (#2026)
* Updated icons/cable.svg

* Update cable.svg

* Update cable.svg

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-04-10 08:36:44 +02:00
Nicolas Diotto
d75c7613b2 feat(icons): add keyboard-off (#1963)
* feat(icons): added keyboard-off

* fix: change name contribuitor

* fix: fix adjust 2px gap of empty space to the right of the line

* fix: fix adjust 2px gap of empty space to the right of the line

* feat: add contribuitor

* fix: update metadata

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Jakob Guddas <github@jguddas.de>
2024-04-09 09:20:49 +02:00
Jakob Guddas
d17e81d712 fix(icons): added rounding to video-off icon (#2051)
* Updated icons/video-off.svg

* Updated icons/video-off.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-04-09 09:17:22 +02:00
Jakob Guddas
3b4a19efcf fix(icons): added rounding to video icon (#2050)
* Updated icons/video.svg

* Updated icons/video.svg

* Updated icons/video.json
2024-04-09 09:15:35 +02:00
Jakob Guddas
94bbdb4e06 fix(icons): added rounding to coffee icon (#2004)
* Updated icons/coffee.svg

* Updated icons/coffee.json
2024-04-05 16:28:45 +02:00
Zakher Masri
4b87cdb55f feat: added pilcrow-right icon (#1668)
* feat: added `pilcrow-right` icon

* Update icons/pilcrow-right.svg

* Format pilcrow-right.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-04-05 16:27:57 +02:00
Zakher Masri
52de557ec1 feat: added pilcrow-left icon (#1667)
* Fixed Illustrator template link + fixed typo

* Added `pilcrow-left` icon

* Update pilcrow-left.json

* Update illustrator-guide.md

* Update CONTRIBUTING.md

* update pilcrow-left icon

* Formatting pilcrow-left.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-04-05 15:56:40 +02:00
Jakob Guddas
642fa718f6 refactor(icons): optimized fingerprint icon (#1993)
* Updated icons/fingerprint.svg

* Updated icons/fingerprint.json

* Updated icons/fingerprint.svg
2024-04-05 15:48:01 +02:00
Riley
215f0767d7 meta(icons): Add axis tag to rotate-3d (#2034)
* Add axis tag to rotate-3d

* Update rotate-3d.json

* Update rotate-3d.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-04-05 15:35:06 +02:00
Jakob Guddas
a0befaf186 fix(icons): added rounding pen-tool icon (#2006)
* Updated icons/pen-tool.svg

* Updated icons/pen-tool.svg

* Updated icons/pen-tool.json

* Updated icons/pen-tool.svg
2024-04-05 15:31:20 +02:00
Jakob Guddas
167f563f6d fix(icons): added rounding to cctv icon (#2003)
* Updated icons/cctv.svg

* Updated icons/cctv.svg
2024-04-05 10:54:21 +02:00
dependabot[bot]
0ee6b84a06 chore(deps-dev): bump vite from 5.0.12 to 5.0.13 (#2043)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.12 to 5.0.13.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.0.13/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.13/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-05 08:45:48 +02:00
Jakob Guddas
9a65837e45 Update generateChangedIconsCommentMarkup.mjs (#2029) 2024-04-03 09:54:30 +02:00
Jakob Guddas
4c6587612a feat: include code snippet in preview comment (#1973)
* feat: include code snippet in preview comment

* feat: collapse code snippet in preview comment when it has more than 20 lines

* Update scripts/generateChangedIconsCommentMarkup.mjs

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

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-04-02 17:48:08 +02:00
Karsa
4aa36db1cb feat(icons): added mouse off icon (#2020)
* feat(icons): added mouse off icon

* Update icons/mouse-off.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-04-01 11:14:40 +02:00
Jakob Guddas
01401a3c97 docs: improved warning message in lucide-static readme (#2025) 2024-04-01 11:14:06 +02:00
Eric Fennis
d1e528fd95 feat(site): Add team section and subtle ads (#2016)
* Add team member cards

* finish up

* Final improvements

* Add ads

* Fix lint errors

* Update docs/.vitepress/data/teamData.json

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

* Update docs/.vitepress/data/teamData.json

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

---------

Co-authored-by: Jakob Guddas <github@jguddas.de>
Co-authored-by: Karsa <contact@karsa.org>
2024-03-25 16:35:43 +01:00
Karsa
5f5be9ec1e fix(icons): fix tram front icon dots (#2017)
Co-authored-by: Karsa <karsa@sztaki.hu>
2024-03-25 15:12:05 +01:00
Karsa
c66cda28da refactor(icons): optimizes drumstick and improves pixel perfection (#1948)
* fix(icons): optimizes drumstick and improves pixel perfection

* feat(icons): convert drumstick manually to arcs

* feat(icons): close gaps

---------

Co-authored-by: Karsa <karsa@sztaki.hu>
2024-03-22 16:36:58 +01:00
Jakob Guddas
e565116a4a fix(icons): increased file-question icon question mark size to match other icons (#1992)
* Updated icons/file-question.svg

* Updated icons/file-question.json
2024-03-22 16:35:19 +01:00
Jakob Guddas
cf50c9f849 Updated icons/monitor-stop.svg (#1991) 2024-03-22 16:32:02 +01:00
Jakob Guddas
9fa47227a7 fix(icons): added rounding to pause icon (#1988)
* Updated icons/pause.svg

* Updated icons/pause.json
2024-03-22 16:22:30 +01:00
Jakob Guddas
547111ed5b Updated icons/map.svg (#1998) 2024-03-22 16:14:45 +01:00
Jakob Guddas
0d6aa9feaa Updated icons/wallet.svg (#2009) 2024-03-22 12:10:37 +01:00
Jakob Guddas
3a0f2b3fa8 Update pull_request_template.md (#2013) 2024-03-22 10:26:00 +01:00
Jakob Guddas
712e6bf6e4 fix(icons): fixed align-vertical-distribute-center icon (#2014)
* Updated icons/align-vertical-distribute-center.svg

* Updated icons/align-vertical-distribute-center.json
2024-03-22 09:12:03 +01:00
Jakob Guddas
58319dd447 fix(icons): added rounding to bug-play icon (#1990)
* Updated icons/bug-play.svg

* Updated icons/bug-play.json
2024-03-21 12:08:42 +01:00
Karsa
cf89b8eeac fix(icons/cpu): add guideline compliant rounding to CPU center (#1981)
Co-authored-by: Karsa <karsa@sztaki.hu>
2024-03-18 18:43:05 +01:00
Karsa
a8c6add82b feat(icons): add worm icon 2024-03-14 13:51:17 +01:00
Karsa
d000e4904d Update 02_bug_report.yml
Fix newly arisen syntax issue
2024-03-14 09:21:36 +01:00
Jakob Guddas
c99d6cbf4a fix(icons): optimized chef-hat icon (#1974)
* Updated icons/chef-hat.svg

* Updated icons/chef-hat.json

* Updated icons/chef-hat.svg

* Updated icons/chef-hat.svg

* Update icons/chef-hat.svg

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

---------

Co-authored-by: Karsa <contact@karsa.org>
2024-03-14 08:41:01 +01:00
Jakob Guddas
0b1fb8ff1f feat(icons): added beer-off icon (#1971)
* Added icons/beer-off.svg

* Added icons/beer-off.json
2024-03-13 17:10:03 +01:00
Karsa
dcde43e97e fix(packages/lucide): exports aliases from lucide package (#1976)
Co-authored-by: Karsa <karsa@sztaki.hu>
2024-03-13 17:09:39 +01:00
Karsa
96da23cc88 feat(icons): added hospital (#1952)
* feat(icons): added hospital

* chore(icons): add some extra icons

* chore(icons): add contributors

---------

Co-authored-by: Karsa <karsa@sztaki.hu>
2024-03-11 15:00:43 +01:00
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
1873 changed files with 18628 additions and 7894 deletions

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

@@ -0,0 +1,16 @@
# Custom words
fullscreen
gamepad
gantt
kanban
pilcrow
squircle
strikethrough
touchpad
ungroup
pilcrow
# 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

@@ -47,7 +47,7 @@
- [ ] I've made sure that the icons look sharp on low DPI displays. - [ ] I've made sure that the icons look sharp on low DPI displays.
- [ ] I've made sure that the icons look consistent with the icon set in size, optical volume and density. - [ ] I've made sure that the icons look consistent with the icon set in size, optical volume and density.
- [ ] I've made sure that the icons are visually centered. - [ ] I've made sure that the icons are visually centered.
- [ ] I've correctly optimized all icons to two points of precision. - [ ] I've correctly optimized all icons to three points of precision.
## Before Submitting <!-- For every PR! --> ## Before Submitting <!-- For every PR! -->
<!-- All of these requirements must be fulfilled. --> <!-- All of these requirements must be fulfilled. -->

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

@@ -94,6 +94,10 @@ jobs:
comment-author: 'github-actions[bot]' comment-author: 'github-actions[bot]'
body-includes: Added or changed icons body-includes: Added or changed icons
- uses: actions/setup-node@v4
- name: Install svgson for code preview (safer and faster than installing all deps)
run: npm install svgson
- name: Generate comment markup - name: Generate comment markup
run: node ./scripts/generateChangedIconsCommentMarkup.mjs >> comment-markup.md run: node ./scripts/generateChangedIconsCommentMarkup.mjs >> comment-markup.md
id: comment-markup id: comment-markup

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

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

@@ -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

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

View File

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

10
cspell.json Normal file
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

@@ -0,0 +1,48 @@
[
{
"name": "Eric Fennis",
"title": "Creator of Lucide & Software engineer @nedap",
"image": "https://github.com/ericfennis.png?size=192",
"sponsor": "https://github.com/sponsors/ericfennis",
"socialLinks": [
{
"icon": "github",
"link": "https://github.com/ericfennis"
},
{
"icon": "x",
"link": "https://github.com/ericfennis"
}
]
},
{
"name": "Karsa Rigó",
"title": "Maintainer of Lucide & Software engineer @sztaki",
"image": "https://github.com/karsa-mistmere.png?size=192",
"socialLinks": [
{
"icon": "github",
"link": "https://github.com/karsa-mistmere"
},
{
"icon": "linkedin",
"link": "https://www.linkedin.com/in/karsamistmere"
}
]
},
{
"name": "jguddas",
"title": "Maintainer of Lucide & Software engineer @lego",
"image": "https://github.com/jguddas.png?size=192",
"socialLinks": [
{
"icon": "github",
"link": "https://github.com/jguddas"
},
{
"icon": "linkedin",
"link": "https://www.linkedin.com/in/jguddas"
}
]
}
]

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,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import { useRouter } from 'vitepress'; import { useRouter } from 'vitepress';
const { go } = useRouter() const { go } = useRouter()

View File

@@ -1,11 +1,11 @@
<template> <template>
<div class="grid"> <div class="card-grid-flex">
<slot /> <slot />
</div> </div>
</template> </template>
<style scoped> <style>
.grid { .card-grid-flex {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: stretch; align-items: stretch;
@@ -15,20 +15,20 @@
margin: -8px; margin: -8px;
} }
.grid > * { .card-grid-flex > * {
flex-basis: 100%; flex-basis: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 8px; padding: 8px;
} }
@media (min-width: 960px) { @media (min-width: 960px) {
.grid > * { .card-grid-flex > * {
flex-basis: 50%; flex-basis: 50%;
} }
} }
@media (min-width: 1280px) { @media (min-width: 1280px) {
.grid > * { .card-grid-flex > * {
flex-basis: 33.33%; flex-basis: 33.33%;
} }
} }

View File

@@ -24,40 +24,10 @@ const headingElement = computed(() => `h${props.headingLevel}`)
font-size: 32px; font-size: 32px;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
margin-bottom: 32px; margin-bottom: 36px;
} }
section { section {
margin-bottom: 96px; margin-bottom: 96px;
} }
.grid {
display: flex;
flex-wrap: wrap;
align-items: stretch;
justify-content: center;
align-content: space-evenly;
box-sizing: border-box;
margin: -8px;
}
.grid > * {
flex-basis: 100%;
box-sizing: border-box;
padding: 8px;
}
@media (min-width: 960px) {
.grid > * {
flex-basis: 50%;
}
}
@media (min-width: 1280px) {
.grid > * {
flex-basis: 33.33%;
}
}
</style> </style>

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

@@ -28,8 +28,6 @@ function insert() {
const replaceIndex = random(0, 48) const replaceIndex = random(0, 48)
const newIcon = getRandomNewIcon() const newIcon = getRandomNewIcon()
// items.value.splice(replaceIndex, 0, newIcon);
items.value[replaceIndex] = newIcon items.value[replaceIndex] = newIcon
} }
@@ -76,7 +74,6 @@ onBeforeUnmount(() => {
<style scoped> <style scoped>
.card-wrapper { .card-wrapper {
/* padding: 0 24px; */
margin-left: auto; margin-left: auto;
margin-bottom: auto; margin-bottom: auto;
margin-top: 48px; margin-top: 48px;
@@ -87,13 +84,10 @@ onBeforeUnmount(() => {
border-radius: 8px; border-radius: 8px;
width: 100%; width: 100%;
height:100%; height:100%;
/* box-shadow: var(--vp-shadow-2); */
max-height: 220px; max-height: 220px;
max-width: 560px; max-width: 560px;
margin: 0 auto; margin: 0 auto;
position: relative; position: relative;
/* max-height: 240px; */
/* margin-top: 96px; */
} }
.card-grid { .card-grid {
@@ -107,7 +101,6 @@ onBeforeUnmount(() => {
max-width: 512px; max-width: 512px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
/* white-space: nowrap; */
} }
.list-enter-active { .list-enter-active {

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,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import HomeContainer from './HomeContainer.vue' import HomeContainer from './HomeContainer.vue'
import HomeSectionTitle from './HomeSectionTitle.vue'
import { useRouter } from 'vitepress'; import { useRouter } from 'vitepress';
import { data } from './HomePackagesSection.data' import { data } from './HomePackagesSection.data'
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'; import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
@@ -9,7 +10,7 @@ const { go } = useRouter()
<template> <template>
<HomeContainer> <HomeContainer>
<h2 class="section-title">Available For:</h2> <HomeSectionTitle>Available For:</HomeSectionTitle>
<div class="packages-list"> <div class="packages-list">
<a <a
v-for="{ name, logo } in data.packages" v-for="{ name, logo } in data.packages"
@@ -34,14 +35,6 @@ const { go } = useRouter()
</template> </template>
<style scoped> <style scoped>
.section-title {
color: var(--vp-c-text-2);
font-weight: 500;
line-height: 32px;
font-size: 16px;
text-align: center;
margin-bottom: 16px;
}
.packages-list { .packages-list {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

View File

@@ -0,0 +1,26 @@
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{
headingLevel: 1 | 2 | 3 | 4 | 5 | 6,
}>()
const headingElement = computed(() => `h${props.headingLevel ?? 2}`)
</script>
<template>
<component :is="headingElement" class="section-title">
<slot />
</component>
</template>
<style scoped>
.section-title {
color: var(--vp-c-text-2);
font-weight: 500;
line-height: 32px;
font-size: 16px;
text-align: center;
margin-bottom: 16px;
}
</style>

View File

@@ -0,0 +1,46 @@
<script setup lang="ts">
import Card from '../base/Card.vue'
import HomeSectionTitle from './HomeSectionTitle.vue'
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
</script>
<template>
<HomeSectionTitle :headingLevel="3">
Sponsor the Lucide maintainers
</HomeSectionTitle>
<Card class="sponsor-card">
<img
src="/open-collective.png"
alt="Open Collective logo"
width="242"
height="42"
/>
<VPButton
href="https://opencollective.com/lucide-icons"
class="sponsor-button"
text="Become a sponsor"
/>
</Card>
</template>
<style scoped>
.sponsor-card {
margin: 0 auto;
max-width: 500px;
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
}
.sponsor-button {
margin: auto 0;
}
@media (min-width: 640px) {
.sponsor-card {
flex-direction: row;
justify-content: space-between;
}
}
</style>

View File

@@ -0,0 +1,91 @@
<script setup lang="ts">
import { useData } from 'vitepress';
import HomeContainer from './HomeContainer.vue'
import GridSection from '../base/GridSection.vue'
import TeamMemberCard, { TeamMember } from './TeamMemberCard.vue'
import teamData from '../../../data/teamData.json'
import HomeSponsorCard from './HomeSponsorCard.vue';
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue'
const { theme } = useData()
</script>
<template>
<HomeContainer>
<GridSection
title="Meet the team"
:headingLevel="2"
class="team-members"
>
<TeamMemberCard
v-for="teamMember in (teamData as TeamMember[])"
v-bind="teamMember"
/>
</GridSection>
<HomeSponsorCard />
<VPDocAsideCarbonAds
:carbon-ads="theme.carbonAds"
class="ad-card"
/>
</HomeContainer>
</template>
<style scoped>
.team-members {
gap: 24px;
margin-top: 48px;
margin-bottom: 48px;
}
@media (min-width: 640px) {
.team-members :deep(.card-grid > *) {
flex-basis: 50%;
}
}
@media (min-width: 768px) {
.team-members :deep(.card-grid > *) {
flex-basis: 33.33%;
}
}
.ad-card {
margin: 32px auto 0;
width: 100%;;
max-width: 500px;
display: flex;
flex-direction: row;
justify-content: space-between;
display: none;
}
@media (min-width: 960px) {
.ad-card {
display: block;
}
}
.ad-card :deep(.VPCarbonAds) {
width: 100%;
text-align: left;
min-height: auto;
}
.ad-card :deep(.VPCarbonAds .carbon-wrap) {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 24px;
}
.ad-card :deep(.VPCarbonAds .carbon-text) {
padding-top: 0;
}
.ad-card :deep(.VPCarbonAds .carbon-poweredby) {
text-align: right;
margin-top: -24px;
}
</style>

View File

@@ -0,0 +1,91 @@
<script lang="ts">
export interface TeamMember {
name: string
title: string
image: string
sponsor: string
socialLinks: DefaultTheme.SocialLink[]
}
</script>
<script setup lang="ts">
import { DefaultTheme } from 'vitepress';
import Card from '../base/Card.vue'
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
import VPSocialLinks from 'vitepress/dist/client/theme-default/components/VPSocialLinks.vue'
defineProps<TeamMember>()
</script>
<template>
<div>
<Card class="member-card">
<img :src="image" :alt="name" class="member-image"/>
<h3 class="member-name">
{{name}}
</h3>
<p class="member-title">
{{title}}
</p>
<div class="member-links">
<VPButton
v-if="sponsor"
:href="sponsor"
text="Sponsor"
theme="sponsor"
class="sponsor-button"
size="medium"
/>
<VPSocialLinks
:links="socialLinks"
/>
</div>
</Card>
</div>
</template>
<style scoped>
.member-card {
flex-basis: 100%;
height:100%;
}
.member-image {
width: 64px;
height: 64px;
border-radius: 32px;
margin: 0 auto;
background-color: var(--vp-c-bg);
}
.member-name {
text-align: center;
margin-top: 16px;
font-size: 21px;
font-weight: 500;
color: var(--textColor);
}
.member-title {
flex-grow: 1;
padding-top: 8px;
line-height: 24px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-text-2);
text-align: center;
margin-bottom: 16px;;
}
.sponsor-button {
width: auto;
}
.member-links {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
</style>

View File

@@ -0,0 +1,83 @@
<script setup lang="ts">
import { useData } from 'vitepress';
import { useSessionStorage } from '@vueuse/core';
import IconButton from '../base/IconButton.vue';
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue'
import { x } from '../../../data/iconNodes'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
const { theme } = useData()
const showAd = useSessionStorage('show-carbon-ads',true)
defineProps<{
drawerOpen: boolean
}>()
const CloseIcon = createLucideIcon('Close', x)
</script>
<template>
<div
:class="{
'drawer-open': drawerOpen,
'hide-ad': !showAd
}"
class="floating-ad"
v-if="theme.carbonAds"
>
<IconButton @click="showAd = false" class="hide-button">
<component :is="CloseIcon" :size="20" absoluteStrokeWidth />
</IconButton>
<VPDocAsideCarbonAds
:carbon-ads="theme.carbonAds"
/>
</div>
</template>
<style scoped>
.floating-ad {
display: none;
position: fixed;
bottom: 32px;
width: 224px;
right: 32px;
transition: opacity 0.5s, transform 0.25s ease;
}
.floating-ad.drawer-open {
transform: translateY(-288px);
}
.floating-ad.hide-ad {
transform: translateX(224px);
opacity: 0;
}
.floating-ad.drawer-open.hide-ad {
transform: translateY(-288px) translateX(224px);
}
.floating-ad.drawer-open, .floating-ad.hide-ad {
transition: opacity 0.25s, transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
}
@media (min-width: 1280px) {
.floating-ad {
display: block;
}
}
@media (min-width: 1440px) {
.floating-ad {
right: calc(((100% - (var(--vp-layout-max-width) - var(--vp-sidebar-width))) - 272px) / 2);
}
}
.hide-button {
padding: 4px;
position: absolute;
top: 8px;
right: 8px;
background-color: transparent;
}
</style>

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

@@ -13,12 +13,18 @@ import { computedAsync } from '@vueuse/core';
import { satisfies } from 'semver'; import { satisfies } from 'semver';
const props = defineProps<{ const props = defineProps<{
iconName: string iconName: string | null
}>() }>()
const { go } = useRouter()
const icon = computedAsync<IconEntity | null>(async () => { const icon = computedAsync<IconEntity | null>(async () => {
if (props.iconName) { if (props.iconName) {
return (await import(`../../../data/iconDetails/${props.iconName}.ts`)).default as IconEntity try {
return (await import(`../../../data/iconDetails/${props.iconName}.ts`)).default as IconEntity
} catch (err) {
go(`/icons/${props.iconName}`)
}
} }
return null return null
}, null) }, null)
@@ -36,8 +42,6 @@ function onClose() {
emit('close') emit('close')
} }
const { go } = useRouter()
const CloseIcon = createLucideIcon('Close', x) const CloseIcon = createLucideIcon('Close', x)
const Expand = createLucideIcon('Expand', expand) const Expand = createLucideIcon('Expand', expand)
</script> </script>
@@ -144,11 +148,11 @@ const Expand = createLucideIcon('Expand', expand)
} }
.drawer-enter-active { .drawer-enter-active {
transition: all 0.2s cubic-bezier(.21,.8,.46,.9); transition: opacity 0.5s, transform 0.25s ease;
} }
.drawer-leave-active { .drawer-leave-active {
transition: all 0.4s cubic-bezier(1, 0.5, 0.8, 1); transition: opacity 0.25s ease, transform 1.6s ease-out;
} }
.drawer-enter-from, .drawer-enter-from,

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

@@ -52,8 +52,7 @@ async function navigateToIcon(event) {
event.preventDefault() event.preventDefault()
window.history.pushState({}, '', `/icons/${props.name}`) window.history.pushState({}, '', `/icons/${props.name}`)
emit('setActiveIcon', props.name) emit('setActiveIcon', props.name)
} } else {
else {
event.preventDefault() event.preventDefault()
go(`/icons/${props.name}`) go(`/icons/${props.name}`)
} }

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,14 @@ import StickyBar from './StickyBar.vue';
import IconsCategory from './IconsCategory.vue'; import IconsCategory from './IconsCategory.vue';
import useFetchTags from '../../composables/useFetchTags'; import useFetchTags from '../../composables/useFetchTags';
import useFetchCategories from '../../composables/useFetchCategories'; import useFetchCategories from '../../composables/useFetchCategories';
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
import chunkArray from '../../utils/chunkArray';
import { CategoryRow } from './IconsCategory.vue';
import useScrollToCategory from '../../composables/useScrollToCategory';
import CarbonAdOverlay from './CarbonAdOverlay.vue';
const ICON_SIZE = 56;
const ICON_GRID_GAP = 8;
const props = defineProps<{ const props = defineProps<{
icons: IconEntity[]; icons: IconEntity[];
@@ -17,7 +25,6 @@ const props = defineProps<{
const activeIconName = ref(null); const activeIconName = ref(null);
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput(); const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
const isSearching = computed(() => !!searchQuery.value); const isSearching = computed(() => !!searchQuery.value);
function setActiveIconName(name: string) { function setActiveIconName(name: string) {
@@ -27,6 +34,13 @@ function setActiveIconName(name: string) {
const { execute: fetchTags, data: tags } = useFetchTags(); const { execute: fetchTags, data: tags } = useFetchTags();
const { execute: fetchCategories, data: categoriesMap } = useFetchCategories(); const { execute: fetchCategories, data: categoriesMap } = useFetchCategories();
const overviewEl = ref<HTMLElement | null>(null);
const { width: containerWidth } = useElementSize(overviewEl)
const columnSize = computed(() => {
return Math.floor((containerWidth.value) / ((ICON_SIZE + ICON_GRID_GAP)));
});
const mappedIcons = computed(() => { const mappedIcons = computed(() => {
if (tags.value == null) { if (tags.value == null) {
return props.icons; return props.icons;
@@ -43,9 +57,10 @@ const mappedIcons = computed(() => {
}); });
}); });
const searchResults = useSearch(searchQuery, mappedIcons, [ const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
{ name: 'name', weight: 2 }, { name: 'name', weight: 3 },
{ name: 'tags', weight: 1 }, { name: 'aliases', weight: 3 },
{ name: 'tags', weight: 2 },
]); ]);
const categories = computed(() => { const categories = computed(() => {
@@ -71,9 +86,43 @@ const categories = computed(() => {
icons: searchedCategoryIcons, icons: searchedCategoryIcons,
}; };
}) })
.filter(({ icons }) => icons.length);
}); });
const categoriesList = computed(() => {
return categories.value
.filter(({ icons }) => icons.length)
.reduce<CategoryRow[]>((acc, category) => {
acc.push({ type: 'category', title: category.title, name: category.name });
const categoryIcons = chunkArray(category.icons, columnSize.value);
categoryIcons.forEach((icons) => {
acc.push({ type: 'icons', icons });
});
return acc;
}, []);
});
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
categoriesList,
{
itemHeight: ICON_SIZE + ICON_GRID_GAP,
overscan: 10
},
)
useScrollToCategory({
categories,
categoriesList,
scrollTo,
searchQueryDebounced,
})
onMounted(() => {
containerProps.ref.value = document.documentElement;
useEventListener(window, 'scroll', containerProps.onScroll)
})
function onFocusSearchInput() { function onFocusSearchInput() {
if (tags.value == null) { if (tags.value == null) {
fetchTags(); fetchTags();
@@ -84,33 +133,48 @@ function onFocusSearchInput() {
} }
const NoResults = defineAsyncComponent(() => import('./NoResults.vue')); const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue')); const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
function handleCloseDrawer() {
setActiveIconName('');
window.history.pushState({}, '', '/icons/categories');
}
</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"
@close="setActiveIconName('')" @close="handleCloseDrawer"
/> />
<CarbonAdOverlay :drawerOpen="!!activeIconName" />
</template> </template>
<style scoped> <style scoped>
@@ -119,6 +183,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,19 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch, defineAsyncComponent } from 'vue'; import { ref, computed, defineAsyncComponent, onMounted, watch } from 'vue';
import type { IconEntity } from '../../types'; import type { IconEntity } from '../../types';
import { useMediaQuery, useOffsetPagination } from '@vueuse/core'; import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
import IconGrid from './IconGrid.vue'; import IconGrid from './IconGrid.vue';
import InputSearch from '../base/InputSearch.vue'; import InputSearch from '../base/InputSearch.vue';
import useSearch from '../../composables/useSearch'; import useSearch from '../../composables/useSearch';
import EndOfPage from '../base/EndOfPage.vue';
import useSearchInput from '../../composables/useSearchInput'; import useSearchInput from '../../composables/useSearchInput';
import StickyBar from './StickyBar.vue'; import StickyBar from './StickyBar.vue';
import useFetchTags from '../../composables/useFetchTags'; import useFetchTags from '../../composables/useFetchTags';
import useFetchCategories from '../../composables/useFetchCategories'; import useFetchCategories from '../../composables/useFetchCategories';
import chunkArray from '../../utils/chunkArray';
import CarbonAdOverlay from './CarbonAdOverlay.vue';
const ICON_SIZE = 56;
const ICON_GRID_GAP = 8;
const props = defineProps<{ const props = defineProps<{
icons: IconEntity[]; icons: IconEntity[];
@@ -17,32 +21,16 @@ const props = defineProps<{
const activeIconName = ref(null); const activeIconName = ref(null);
const isExtraLargeScreen = useMediaQuery('(min-width: 1440px)');
const isLargeScreen = useMediaQuery('(min-width: 1280px)');
const isMediumScreen = useMediaQuery('(min-width: 960px)');
const isSmallScreen = useMediaQuery('(min-width: 640px)');
const pageSize = computed(() => {
if (isExtraLargeScreen.value) {
return 16 * 20;
}
if (isLargeScreen.value) {
return 16 * 12;
}
if (isMediumScreen.value) {
return 13 * 12;
}
if (isSmallScreen.value) {
return 10 * 10;
}
return 10 * 5;
});
const { execute: fetchTags, data: tags } = useFetchTags(); const { execute: fetchTags, data: tags } = useFetchTags();
const { execute: fetchCategories, data: categories } = useFetchCategories(); const { execute: fetchCategories, data: categories } = useFetchCategories();
const overviewEl = ref<HTMLElement | null>(null);
const { width: containerWidth } = useElementSize(overviewEl)
const columnSize = computed(() => {
return Math.floor((containerWidth.value) / ((ICON_SIZE + ICON_GRID_GAP)));
});
const mappedIcons = computed(() => { const mappedIcons = computed(() => {
if (tags.value == null) { if (tags.value == null) {
return props.icons; return props.icons;
@@ -63,26 +51,33 @@ const mappedIcons = computed(() => {
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput(); const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [ const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
{ name: 'name', weight: 3 }, { name: 'name', weight: 3 },
{ name: 'aliases', weight: 3 },
{ name: 'tags', weight: 2 }, { name: 'tags', weight: 2 },
{ name: 'categories', weight: 1 }, { name: 'categories', weight: 1 },
]); ]);
const { next, currentPage } = useOffsetPagination({ pageSize }); const chunkedIcons = computed(() => {
return chunkArray(searchResults.value, columnSize.value);
const paginatedIcons = computed(() => {
const end = pageSize.value * currentPage.value;
return searchResults.value.slice(0, end);
}); });
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
chunkedIcons,
{
itemHeight: ICON_SIZE + ICON_GRID_GAP,
overscan: 10
},
)
onMounted(() => {
containerProps.ref.value = document.documentElement;
useEventListener(window, 'scroll', containerProps.onScroll)
})
function setActiveIconName(name: string) { function setActiveIconName(name: string) {
activeIconName.value = name; activeIconName.value = name;
} }
watch(searchQueryDebounced, (searchString) => {
currentPage.value = 1;
});
function onFocusSearchInput() { function onFocusSearchInput() {
if (tags.value == null) { if (tags.value == null) {
fetchTags(); fetchTags();
@@ -95,43 +90,57 @@ function onFocusSearchInput() {
const NoResults = defineAsyncComponent(() => import('./NoResults.vue')); const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue')); const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
watch(searchQueryDebounced, () => {
scrollTo(0)
})
function handleCloseDrawer() {
setActiveIconName('');
window.history.pushState({}, '', '/icons/');
}
</script> </script>
<template> <template>
<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"
:iconName="activeIconName" :iconName="activeIconName"
@close="setActiveIconName('')" @close="handleCloseDrawer"
/> />
<CarbonAdOverlay :drawerOpen="!!activeIconName" />
</template> </template>
<style> <style>
.icons { .icons {
display: grid; margin-bottom: 8px;
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
gap: 8px;
width: 100%;
} }
.icon { .icon {
@@ -142,7 +151,7 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
width: 100%; width: 100%;
} }
.bottom-page { .overview-container {
height: 288px; padding-bottom: 288px;
} }
</style> </style>

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,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import {data} from './PackageList.data' import { data } from './PackageList.data'
import GridSection from '../base/GridSection.vue' import GridSection from '../base/GridSection.vue'
import PackageListItem from "./PackageListItem.vue";</script> import PackageListItem from "./PackageListItem.vue";</script>
@@ -38,33 +38,4 @@ import PackageListItem from "./PackageListItem.vue";</script>
.package-group { .package-group {
margin-bottom: 96px; margin-bottom: 96px;
} }
.grid {
display: flex;
flex-wrap: wrap;
align-items: stretch;
justify-content: center;
align-content: space-evenly;
box-sizing: border-box;
margin: -8px;
}
.grid > * {
flex-basis: 100%;
box-sizing: border-box;
padding: 8px;
}
@media (min-width: 960px) {
.grid > * {
flex-basis: 50%;
}
}
@media (min-width: 1280px) {
.grid > * {
flex-basis: 33.33%;
}
}
</style> </style>

View File

@@ -40,33 +40,4 @@ import ShowcaseListItem from "./ShowcaseListItem.vue";
.package-group { .package-group {
margin-bottom: 96px; margin-bottom: 96px;
} }
.grid {
display: flex;
flex-wrap: wrap;
align-items: stretch;
justify-content: center;
align-content: space-evenly;
box-sizing: border-box;
margin: -8px;
}
.grid > * {
flex-basis: 100%;
box-sizing: border-box;
padding: 8px;
}
@media (min-width: 960px) {
.grid > * {
flex-basis: 50%;
}
}
@media (min-width: 1280px) {
.grid > * {
flex-basis: 33.33%;
}
}
</style> </style>

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;

View File

@@ -1,16 +1,14 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import { import { ref, inject, Ref } from 'vue';
ref, inject, Ref
} from 'vue';
export const ICON_STYLE_CONTEXT = Symbol('size'); export const ICON_STYLE_CONTEXT = Symbol('size');
interface IconSizeContext { interface IconSizeContext {
size: Ref<number> size: Ref<number>;
strokeWidth: Ref<number> strokeWidth: Ref<number>;
color: Ref<string> color: Ref<string>;
absoluteStrokeWidth: Ref<boolean> absoluteStrokeWidth: Ref<boolean>;
} }
export const STYLE_DEFAULTS = { export const STYLE_DEFAULTS = {
@@ -27,7 +25,7 @@ export const iconStyleContext = {
absoluteStrokeWidth: ref(false), absoluteStrokeWidth: ref(false),
}; };
export function useIconStyleContext(): IconSizeContext{ export function useIconStyleContext(): IconSizeContext {
const context = inject<IconSizeContext>(ICON_STYLE_CONTEXT); const context = inject<IconSizeContext>(ICON_STYLE_CONTEXT);
if (!context) { if (!context) {

View File

@@ -0,0 +1,52 @@
import { Ref, onMounted, watch } from 'vue';
import { CategoryRow } from '../components/icons/IconsCategory.vue';
import { Category } from '../types';
import { useCategoryView } from './useCategoryView';
interface UseScrollToCategory {
categories: Ref<Pick<Category, 'name' | 'icons'>[]>;
categoriesList: Ref<CategoryRow[]>;
scrollTo: (index: number) => void;
searchQueryDebounced: Ref<string>;
}
export default function useScrollToCategory({
categories,
categoriesList,
scrollTo,
searchQueryDebounced,
}: UseScrollToCategory) {
const { selectedCategory, categoryCounts } = useCategoryView();
function scrollToSelectedCategory(selectedCategory: string) {
const category = categories.value.find((category) => category.name === selectedCategory);
if (category != null) {
const categoryRowIndex = categoriesList.value.findIndex(
(row) => row.type === 'category' && row.name === selectedCategory,
);
if (categoryRowIndex !== -1) {
setTimeout(() => {
scrollTo(categoryRowIndex);
}, 0);
}
}
}
watch(selectedCategory, scrollToSelectedCategory);
onMounted(() => {
setTimeout(() => {
scrollToSelectedCategory(selectedCategory.value);
}, 0);
});
watch(searchQueryDebounced, () => {
scrollTo(0);
});
watch(categories, (items) => {
categoryCounts.value = Object.fromEntries(items.map(({ name, icons }) => [name, icons.length]));
});
}

View File

@@ -1,13 +1,17 @@
import Fuse from 'fuse.js'; import Fuse from 'fuse.js';
import { shallowRef, computed, Ref } from 'vue'; import { shallowRef, computed, Ref } from 'vue';
const useSearch = <T>(query: Ref<string>, collection: Ref<T[]>, keys: Fuse.FuseOptionKey<T>[] = []) => { const useSearch = <T>(
query: Ref<string>,
collection: Ref<T[]>,
keys: Fuse.FuseOptionKeyObject<T>[] = [],
) => {
const index = shallowRef( const index = shallowRef(
new Fuse(collection.value, { new Fuse(collection.value, {
threshold: 0.2, threshold: 0.2,
keys, keys,
}) }),
) );
const results = computed(() => { const results = computed(() => {
index.value.setCollection(collection.value); index.value.setCollection(collection.value);
@@ -16,6 +20,16 @@ const useSearch = <T>(query: Ref<string>, collection: Ref<T[]>, keys: Fuse.FuseO
return index.value.search(query.value).map((result) => result.item); return index.value.search(query.value).map((result) => result.item);
} }
if (keys.length !== 0) {
const mainKey = keys[0].name;
return collection.value.sort((a, b) => {
const aString = a[mainKey as keyof T] as string;
const bString = b[mainKey as keyof T] as string;
return aString.localeCompare(bString);
});
}
return collection.value; return collection.value;
}); });

View File

@@ -1,12 +1,12 @@
import { h } from 'vue' import { h } from 'vue';
import DefaultTheme from 'vitepress/theme' import DefaultTheme from 'vitepress/theme';
import './style.css' import './style.css';
import { Theme } from 'vitepress' import { Theme } from 'vitepress';
import IconsSidebarNavAfter from './layouts/IconsSidebarNavAfter.vue' import IconsSidebarNavAfter from './layouts/IconsSidebarNavAfter.vue';
import HomeHeroIconsCard from './components/home/HomeHeroIconsCard.vue' import HomeHeroIconsCard from './components/home/HomeHeroIconsCard.vue';
import HomeHeroBefore from "./components/home/HomeHeroBefore.vue"; import HomeHeroBefore from './components/home/HomeHeroBefore.vue';
import { ICON_STYLE_CONTEXT, iconStyleContext } from './composables/useIconStyle' import { ICON_STYLE_CONTEXT, iconStyleContext } from './composables/useIconStyle';
import { CATEGORY_VIEW_CONTEXT, categoryViewContext } from './composables/useCategoryView' import { CATEGORY_VIEW_CONTEXT, categoryViewContext } from './composables/useCategoryView';
const theme: Partial<Theme> = { const theme: Partial<Theme> = {
extends: DefaultTheme, extends: DefaultTheme,
@@ -15,12 +15,12 @@ const theme: Partial<Theme> = {
'home-hero-before': () => h(HomeHeroBefore), 'home-hero-before': () => h(HomeHeroBefore),
'sidebar-nav-after': () => h(IconsSidebarNavAfter), 'sidebar-nav-after': () => h(IconsSidebarNavAfter),
'home-hero-image': () => h(HomeHeroIconsCard), 'home-hero-image': () => h(HomeHeroIconsCard),
}) });
}, },
enhanceApp({ app }) { enhanceApp({ app }) {
app.provide(ICON_STYLE_CONTEXT, iconStyleContext) app.provide(ICON_STYLE_CONTEXT, iconStyleContext);
app.provide(CATEGORY_VIEW_CONTEXT, categoryViewContext) app.provide(CATEGORY_VIEW_CONTEXT, categoryViewContext);
} },
} };
export default theme export default theme;

View File

@@ -1,43 +1,47 @@
:root { :root {
--vp-c-brand: #F56565; --vp-c-brand: #f56565;
--vp-c-brand-light: #F67373; --vp-c-brand-light: #f67373;
--vp-c-brand-lighter: #F89191; --vp-c-brand-lighter: #f89191;
--vp-c-brand-dark: #DC5A5A; --vp-c-brand-dark: #dc5a5a;
--vp-c-brand-darker: #C45050; --vp-c-brand-darker: #c45050;
--vp-c-brand-1: #F67373; --vp-c-brand-1: #f67373;
--vp-c-brand-2: #FF7070; --vp-c-brand-2: #ff7070;
--vp-c-brand-3: #F56565; --vp-c-brand-3: #f56565;
--vp-c-brand-4: #DC5A5A; --vp-c-brand-4: #dc5a5a;
--vp-c-brand-5: #C45050; --vp-c-brand-5: #c45050;
--vp-c-bg-alt-up: #fff; --vp-c-bg-alt-up: #fff;
--vp-c-bg-alt-down: #fff; --vp-c-bg-alt-down: #fff;
--vp-code-editor-plain: #24292E; --vp-code-editor-plain: #24292e;
--vp-code-editor-comment: #6A737D; --vp-code-editor-comment: #6a737d;
--vp-code-editor-keyword: #D73A49; --vp-code-editor-keyword: #d73a49;
--vp-code-editor-tag: #22863A; --vp-code-editor-tag: #22863a;
--vp-code-editor-punctuation: #24292E; --vp-code-editor-punctuation: #24292e;
--vp-code-editor-definition: #6F42C1; --vp-code-editor-definition: #6f42c1;
--vp-code-editor-property: #005CC5; --vp-code-editor-property: #005cc5;
--vp-code-editor-static: #F78C6C; --vp-code-editor-static: #f78c6c;
--vp-code-editor-string: #032F62; --vp-code-editor-string: #032f62;
--vp-c-text-4: rgba(60, 60, 67, 0.32);
} }
.dark { .dark {
--vp-c-bg-alt-up: #1B1B1D; --vp-c-bg-alt-up: #1b1b1d;
--vp-c-bg-alt-down: #0F0F10; --vp-c-bg-alt-down: #0f0f10;
--vp-code-editor-plain: #E1E4E8; --vp-code-editor-plain: #e1e4e8;
--vp-code-editor-comment: #6A737D; --vp-code-editor-comment: #6a737d;
--vp-code-editor-keyword: #F97583; --vp-code-editor-keyword: #f97583;
--vp-code-editor-tag: #85E89D; --vp-code-editor-tag: #85e89d;
--vp-code-editor-punctuation: #9ECBFF; --vp-code-editor-punctuation: #9ecbff;
--vp-code-editor-definition: #B392F0; --vp-code-editor-definition: #b392f0;
--vp-code-editor-property: #79B8FF; --vp-code-editor-property: #79b8ff;
--vp-code-editor-static: #F78C6C; --vp-code-editor-static: #f78c6c;
--vp-code-editor-string: #9ECBFF; --vp-code-editor-string: #9ecbff;
--vp-c-text-4: rgba(235, 235, 245, 0.16);
} }
.VPNavBarTitle .logo { .VPNavBarTitle .logo {
@@ -69,7 +73,6 @@
.VPHomeHero .container .main h1.name { .VPHomeHero .container .main h1.name {
color: var(--vp-c-text); color: var(--vp-c-text);
} }
.VPHomeHero .container .main h1.name .clip { .VPHomeHero .container .main h1.name .clip {
color: inherit; color: inherit;
@@ -82,7 +85,6 @@
color: var(--vp-c-brand); color: var(--vp-c-brand);
} }
/* */ /* */
.VPHomeHero .container .image { .VPHomeHero .container .image {
margin: 0; margin: 0;
@@ -101,14 +103,14 @@
} }
.VPFeature .icon { .VPFeature .icon {
background-color: var(--vp-c-bg);; background-color: var(--vp-c-bg);
} }
.vp-doc[class*=" _icons_"] > div { .vp-doc[class*=' _icons_'] > div {
max-width: 100%; max-width: 100%;
} }
.VPDoc:has(.vp-doc[class*=" _icons_"]) > .container > .content{ .VPDoc:has(.vp-doc[class*=' _icons_']) > .container > .content {
padding-right: 0; padding-right: 0;
padding-left: 0; padding-left: 0;
} }
@@ -120,7 +122,6 @@
} }
@media (min-width: 960px) { @media (min-width: 960px) {
.VPHomeHero .container .image { .VPHomeHero .container .image {
order: 1; order: 1;
margin-bottom: auto; margin-bottom: auto;
@@ -141,7 +142,6 @@
} }
.VPHomeHero .container .main h1.name { .VPHomeHero .container .main h1.name {
} }
} }
@@ -198,10 +198,10 @@ html:has(* .outline-link:target) {
transition: background-color 0.25s; transition: background-color 0.25s;
} }
.sp-wrapper .sp-tabs .sp-tab-button[data-active="true"] { .sp-wrapper .sp-tabs .sp-tab-button[data-active='true'] {
color: var(--vp-code-tab-active-text-color); color: var(--vp-code-tab-active-text-color);
} }
.sp-wrapper .sp-tabs .sp-tab-button[data-active="true"]:after { .sp-wrapper .sp-tabs .sp-tab-button[data-active='true']:after {
background-color: var(--vp-code-tab-active-bar-color); background-color: var(--vp-code-tab-active-bar-color);
} }

View File

@@ -1,5 +1,5 @@
export type IconNode = [elementName: string, attrs: Record<string, string>][] export type IconNode = [elementName: string, attrs: Record<string, string>][];
export type IconNodeWithKeys = [elementName: string, attrs: Record<string, string>, key: string][] export type IconNodeWithKeys = [elementName: string, attrs: Record<string, string>, key: string][];
export interface IconEntity { export interface IconEntity {
name: string; name: string;
@@ -13,45 +13,44 @@ export interface IconEntity {
} }
export interface Category { export interface Category {
name: string name: string;
title: string title: string;
icon?: string icon?: string;
iconCount: number iconCount: number;
icons?: IconEntity[] icons?: IconEntity[];
} }
interface Shield { interface Shield {
alt: string alt: string;
src: string src: string;
href: string href: string;
} }
export interface PackageItem { export interface PackageItem {
name: string name: string;
description: string description: string;
icon: string icon: string;
iconDark: string iconDark: string;
shields: Shield[] shields: Shield[];
source: string source: string;
documentation: string documentation: string;
order?: number order?: number;
private?: boolean private?: boolean;
flutter?: object flutter?: object;
} }
export interface Release { export interface Release {
version: string version: string;
date: string date: string;
} }
interface ShowcaseItemImage { interface ShowcaseItemImage {
light: string light: string;
dark: string dark: string;
} }
export interface ShowcaseItem { export interface ShowcaseItem {
name: string name: string;
url: string url: string;
image: ShowcaseItemImage image: ShowcaseItemImage;
} }

View File

@@ -0,0 +1,9 @@
const chunkArray = <ArrayType>(stream: ArrayType, size: number) => {
return stream.reduce<ArrayType[][]>(
(chunks, item, idx, arr) =>
idx % size == 0 ? [...chunks, arr.slice(idx, idx + size)] : chunks,
[],
);
};
export default chunkArray;

View File

@@ -1,6 +1,6 @@
export default function downloadData(filename:string, data:string) { export default function downloadData(filename: string, data: string) {
const link = document.createElement('a'); const link = document.createElement('a');
link.download = filename; link.download = filename;
link.href = data link.href = data;
link.click(); link.click();
} }

View File

@@ -9,26 +9,26 @@ const allowedAttrs = [
'stroke-linecap', 'stroke-linecap',
'stroke-linejoin', 'stroke-linejoin',
'class', 'class',
] ];
export default function getSVGIcon(element?: HTMLElement, attrs?: Record<string, string>) { export default function getSVGIcon(element?: HTMLElement, attrs?: Record<string, string>) {
const svg = element ?? document.querySelector('#previewer svg') const svg = element ?? document.querySelector('#previewer svg');
if (!svg) return if (!svg) return;
const clonedSvg = svg.cloneNode(true) as SVGElement const clonedSvg = svg.cloneNode(true) as SVGElement;
// Filter out attributes that are not allowed in SVGs // Filter out attributes that are not allowed in SVGs
for (const attr of Array.from(clonedSvg.attributes)) { for (const attr of Array.from(clonedSvg.attributes)) {
if (!allowedAttrs.includes(attr.name)) { if (!allowedAttrs.includes(attr.name)) {
clonedSvg.removeAttribute(attr.name) clonedSvg.removeAttribute(attr.name);
} }
} }
for (const [key, value] of Object.entries(attrs ?? {})) { for (const [key, value] of Object.entries(attrs ?? {})) {
clonedSvg.setAttribute(key, value) clonedSvg.setAttribute(key, value);
} }
const svgString = new XMLSerializer().serializeToString(clonedSvg) const svgString = new XMLSerializer().serializeToString(clonedSvg);
return svgString return svgString;
} }

View File

@@ -1,5 +1,7 @@
import { IconNode } from 'lucide-vue-next/src/createLucideIcon';
import Vue from 'vue';
declare module '*.vue' { declare module '*.vue' {
import Vue from 'vue';
export default Vue; export default Vue;
} }
@@ -16,3 +18,7 @@ declare const resvg_wasm: RequestInfo | URL | Response | BufferSource | WebAssem
declare module 'node:module' { declare module 'node:module' {
function createRequire(filename: string): NodeRequire; function createRequire(filename: string): NodeRequire;
} }
declare module '*.node.json' {
export default IconNode;
}

View File

@@ -106,6 +106,15 @@ Here are rules that should be followed to keep quality and consistency when maki
6. Names containing numerals are not allowed, unless the number itself is represented in the icon.\ 6. Names containing numerals are not allowed, unless the number itself is represented in the icon.\
For example: `arrow-down-0-to-1` contains both numerals. For example: `arrow-down-0-to-1` contains both numerals.
7. Icons depicting multiple elements (e.g. a person and a circle) of different sizes must list these elements in decreasing order of size.\
For example: if the circle is bigger, it should be `circle-person`, if the person is bigger, it should be `person-circle`.
8. Icons depicting multiple elements of roughly equal sizes (e.g. a `ruler` and a `pencil`) must list these elements in English reading order.\
For example: if the `pencil` is either above or left of `ruler`, it should be `pencil-ruler`, otherwise, it should be `ruler-pencil`.
9. Icons depicting some sort of variation of an element must use the `[element]-[modifier]` naming scheme, with modifiers being applied to each element respectively.\
For example: a dashed circle must be named `circle-dashed`, not `dashed-circle`, and in coordination with the previous guidelines, a dashed circle containing a broken heart would be named `circle-dashed-heart-broken`, due to the heart being smaller than the circle.
## Code Conventions ## Code Conventions
Before an icon is added to the library, we like to have readable and optimized SVG code. Before an icon is added to the library, we like to have readable and optimized SVG code.

View File

@@ -4,7 +4,7 @@ Implementation of the lucide icon library for React Native applications
## Installation ## Installation
First, ensure that you have `react-native-svg` (version between 12 and 14) installed. Then, install the package: First, ensure that you have `react-native-svg` (version between 12 and 15) installed. Then, install the package:
::: code-group ::: code-group

View File

@@ -1,23 +1,22 @@
import relatedIcons from '../.vitepress/data/relatedIcons.json' import relatedIcons from '../.vitepress/data/relatedIcons.json';
import iconNodes from '../.vitepress/data/iconNodes' import iconNodes from '../.vitepress/data/iconNodes';
import * as iconDetails from '../.vitepress/data/iconDetails' import * as iconDetails from '../.vitepress/data/iconDetails';
import { IconEntity } from "../.vitepress/theme/types"; import { IconEntity } from '../.vitepress/theme/types';
export default { export default {
paths: async () => { paths: async () => {
return (Object.values(iconDetails) as unknown as IconEntity[]).map((iconEntity) => { return (Object.values(iconDetails) as unknown as IconEntity[]).map((iconEntity) => {
const params = { const params = {
...iconEntity, ...iconEntity,
relatedIcons: relatedIcons[iconEntity.name].map((name: string) => ({ relatedIcons: relatedIcons[iconEntity.name].map((name: string) => ({
name, name,
iconNode: iconNodes[name], iconNode: iconNodes[name],
})) })),
} };
return { return {
params, params,
} };
}) });
} },
} };

View File

@@ -1,13 +1,13 @@
import { getAllCategoryFiles } from '../.vitepress/lib/categories' import { getAllCategoryFiles } from '../.vitepress/lib/categories';
import iconMetaData from '../.vitepress/data/iconMetaData' import iconMetaData from '../.vitepress/data/iconMetaData';
export default { export default {
async load() { async load() {
return { return {
categories: getAllCategoryFiles(), categories: getAllCategoryFiles(),
iconCategories: Object.fromEntries( iconCategories: Object.fromEntries(
Object.entries(iconMetaData).map(([name, { categories }]) => [name, categories]) Object.entries(iconMetaData).map(([name, { categories }]) => [name, categories]),
), ),
} };
} },
} };

View File

@@ -10,7 +10,6 @@ import { data } from './icons.data.ts'
import { data as categoriesData } from './categories.data.ts' import { data as categoriesData } from './categories.data.ts'
import PageContainer from '../.vitepress/theme/components/PageContainer.vue' import PageContainer from '../.vitepress/theme/components/PageContainer.vue'
import IconsCategoryOverview from '../.vitepress/theme/components/icons/IconsCategoryOverview.vue' import IconsCategoryOverview from '../.vitepress/theme/components/icons/IconsCategoryOverview.vue'
</script> </script>
<div class="VPDoc content"> <div class="VPDoc content">

View File

@@ -1,13 +1,13 @@
import createCodeExamples from '../.vitepress/lib/createCodeExamples' import createCodeExamples from '../.vitepress/lib/createCodeExamples';
export default { export default {
async load() { async load() {
const codeExamples = await createCodeExamples() const codeExamples = await createCodeExamples();
// const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons)) // const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons))
return { return {
codeExamples, codeExamples,
} };
} },
} };

View File

@@ -1,9 +1,9 @@
import iconNodes from '../.vitepress/data/iconNodes' import iconNodes from '../.vitepress/data/iconNodes';
export default { export default {
async load() { async load() {
return { return {
icons: Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode })), icons: Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode })),
} };
} },
} };

View File

@@ -56,7 +56,9 @@ features:
<script setup> <script setup>
import HomePackagesSection from './.vitepress/theme/components/home/HomePackagesSection.vue' import HomePackagesSection from './.vitepress/theme/components/home/HomePackagesSection.vue'
import HomeIconCustomizer from './.vitepress/theme/components/home/HomeIconCustomizer.vue' import HomeIconCustomizer from './.vitepress/theme/components/home/HomeIconCustomizer.vue'
import HomeTeamSection from './.vitepress/theme/components/home/HomeTeamSection.vue'
</script> </script>
<HomePackagesSection /> <HomePackagesSection />
<HomeIconCustomizer /> <HomeIconCustomizer />
<HomeTeamSection />

View File

@@ -48,7 +48,7 @@
"lucide-vue-next": "workspace:*", "lucide-vue-next": "workspace:*",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"sandpack-vue3": "3.1.8", "sandpack-vue3": "3.1.11",
"semver": "^7.5.2", "semver": "^7.5.2",
"shikiji": "^0.7.4", "shikiji": "^0.7.4",
"simple-git": "^3.18.0", "simple-git": "^3.18.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -4,6 +4,6 @@
"jsx": "react", "jsx": "react",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"noEmit": true "noEmit": true,
} },
} }

View File

@@ -10,7 +10,9 @@
"cast", "cast",
"mirroring", "mirroring",
"screen", "screen",
"monitor" "monitor",
"macos",
"osx"
], ],
"categories": [ "categories": [
"multimedia", "multimedia",

View File

@@ -10,5 +10,5 @@
stroke-linejoin="round" stroke-linejoin="round"
> >
<path d="M5 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-1" /> <path d="M5 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-1" />
<polygon points="12 15 17 21 7 21 12 15" /> <path d="m12 15 5 6H7Z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 344 B

After

Width:  |  Height:  |  Size: 327 B

View File

@@ -1,7 +1,8 @@
{ {
"$schema": "../icon.schema.json", "$schema": "../icon.schema.json",
"contributors": [ "contributors": [
"danielbayley" "danielbayley",
"karsa-mistmere"
], ],
"tags": [ "tags": [
"fire", "fire",

View File

@@ -1,7 +1,8 @@
{ {
"$schema": "../icon.schema.json", "$schema": "../icon.schema.json",
"contributors": [ "contributors": [
"ericfennis" "ericfennis",
"jguddas"
], ],
"tags": [ "tags": [
"items", "items",

View File

@@ -9,10 +9,10 @@
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
> >
<rect width="14" height="6" x="5" y="14" rx="2" />
<rect width="10" height="6" x="7" y="4" rx="2" />
<path d="M22 7h-5" />
<path d="M7 7H1" />
<path d="M22 17h-3" /> <path d="M22 17h-3" />
<path d="M22 7h-5" />
<path d="M5 17H2" /> <path d="M5 17H2" />
<path d="M7 7H2" />
<rect x="5" y="14" width="14" height="6" rx="2" />
<rect x="7" y="4" width="10" height="6" rx="2" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 407 B

After

Width:  |  Height:  |  Size: 407 B

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