Compare commits

...
Author SHA1 Message Date
Eric f00986df24 upgrade to nitro 3 2026-11-06 13:05:36 +01:00
Eric 0052a4e012 Merge branch 'vitepress-2-upgrade' into vitepress-nitro-plugin 2026-11-06 12:35:09 +01:00
Eric da870c8119 Fix ts imports 2026-11-06 12:34:58 +01:00
Eric 3d4c190134 Merge branch 'vitepress-2-upgrade' into vitepress-nitro-plugin 2026-11-06 12:28:19 +01:00
Eric ce082fe4a9 Merge branch 'main' of https://github.com/lucide-icons/lucide into vitepress-nitro-plugin 2026-11-06 12:09:27 +01:00
Eric 66f607629d Fix types and imports 2026-11-06 12:00:45 +01:00
Eric Fennis 1a49fd88d8 chore(site): Add support for reduced motion in HomeHeroAnimation (#5159)
* Refactor code

* Respect reduce motion

* Add icon

* Revert Icon switch
2026-11-06 09:09:23 +01:00
gridalchemygithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Eric Fennis
b826f0c527 feat(icons): added grid-3x3-off icon (#5071)
* feat(icons): added grid-3x3-off icon

* chore(icons): add suggested tags to `grid-3x3-off`

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(icons): use standard off geometry for `grid-3x3-off`

* chore(icons): use metadata markers for `grid-3x3-off`

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-11-05 23:02:56 +01:00
Eric FennisandKarsa 558bdd83b5 feat(icons): added user-exclamation-point, user-round-exclamation-point icons (#5144)
* Added icons/user-round-exclamation-point.svg

* Added icons/user-round-exclamation-point.json

* Updated metadata

* Add user-exclamation-point

* Adds suggestions

* Update icons/user-round-exclamation-point.svg

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

* Modify user-exclamation-point icon SVG paths

---------

Co-authored-by: Karsa <karsa@karsa.org>
2026-11-05 20:32:56 +01:00
Jamie Lawgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Eric Fennis
a805955781 feat(icons): added moped icon (#3373)
* Add `dirt-bike` icon

* Fix JSON formatting

* Fix JSON formatting again

* Rounded the handlebars

* Rename icon and add metadata

* Update icons/moped.json

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-11-05 18:43:20 +01:00
tlq5landEric Fennis ee8e10d746 fix(lucide-react-native): emit runtime files for alias icon subpaths (#5055)
closes #5043

Agent: claude

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-11-05 14:11:40 +01:00
DominikCursorgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Eric FennisKarsa
2c2ee80687 feat(icons): added square-dashed-search icon (#5119)
* Add the square-dashed-search lab icon.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(lab): added zoom selection use case to `square-dashed-search`

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(icons): delegated `square-dashed-search` icon from lab

Co-authored-by: Cursor <cursoragent@cursor.com>

* Update icons/square-dashed-search.json

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update icons/square-dashed-search.json

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Adjust metadata

* Apply feedback

* Remove text category

* Update icons/square-dashed-search.svg

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Karsa <contact@karsa.org>
2026-11-05 10:24:38 +01:00
Eric FennisandClaude 1c93e0a476 fix(docs): fix vercel.json redirects that land on 404 (#5157)
- Redirect /docs/lucide-*, /guide/lucide-* and /guide/packages/lucide-*/ to
  the current /guide/<pkg> pages (vue-next -> vue)
- Redirect old /guide/basics/*, /guide/advanced/* and /guide/design/* pages
- Redirect graduated lab icons carton-off and briefcase-plus to core
- Remove the /docs/:slug(lucide-?.*) rule that produced 404 destinations

Fixes LUCIDE-15


Claude-Session: https://claude.ai/code/session_01G9u8FetMRtXGCVFrfg619a

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-05 07:49:42 +01:00
91a1359b5e feat(icons): added circle-fading-check icon (#4089)
* Added icons/circle-fading-check.svg

* Added icons/circle-fading-check.json

* Refactor SVG paths in circle-fading-check.svg

* Add empty use-cases array to circle-fading-check.json

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Update metadata

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-11-04 16:03:14 +01:00
Ilya VredandEric 90e8041a35 feat(icons): added opacity icon (#4222)
* Added icons/opacity.svg

* Added icons/opacity.json

* Updated icons/opacity.svg

* Updated icons/opacity.json

* Adjust usecases

---------

Co-authored-by: Eric <eric.fennis@gmail.com>
2026-11-04 15:56:22 +01:00
Eric FennisandKarsa 3898742b6a feat(icons): added package-question-mark icon (#5155)
* Add metadata

* Add more tags to `package.json`

* Update icons/package-question-mark.json

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

---------

Co-authored-by: Karsa <contact@karsa.org>
2026-11-04 15:29:36 +01:00
Eric FennisandKarsa a4a0e20ff5 feat(icons): added package-pen icon (#5154)
* Add package pen icon

* Update icons/package-pen.json

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

* update metadata

---------

Co-authored-by: Karsa <contact@karsa.org>
2026-11-04 15:26:37 +01:00
Eric Fennis 1980e3d59b chore(icons): Renamed *-warning and *-alert to *-exclamation-point (#5143)
* Rename icons

* Update other file

* Remove deprecation booleans
2026-11-03 15:49:26 +01:00
aa0b77386c feat(icons): added mango icon (#4902)
* feat: add mango icon

* fix: adjust mango icon leaf and body shape

* refactor: switch to rrod497's mango design, expand metadata

* Update use cases for mango in icons/mango.json

Removed outdated use cases and added updated ones for mango.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Apply suggestion from @karsa-mistmere

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

* Update mango.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Karsa <contact@karsa.org>
2026-11-03 14:16:15 +01:00
Bin 7bb15b80a3 fix(docs): keep sponsor tiers within content width (#5149) 2026-11-03 09:41:31 +01:00
Eric FennisandClaude Opus 5.5 4b203ec9b6 chore(deps): Fix security vulnerabilities alerts (#5147)
* Upgrade deps

* Fix security alerts

* Keep incompatible consumers off minimatch 11 and file-type 21

The security overrides forced minimatch 11 and file-type 21 onto every
consumer, which broke ESLint (eslint-plugin-import-x imports the removed
makeRe) and the font build (jimp 0.22 calls file-type's removed
fromBuffer). glob 7 and @eslint/eslintrc also rely on the minimatch 3
callable export. Scope those consumers back to their compatible lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Remove minimatch override

* Fix font build

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:58:02 +01:00
Eric FennisandCopilot Autofix powered by AI 58c85b4758 fix(dev): avoid polynomial regex backtracking in toCamelCase helper (#5148)
* fix(dev): avoid polynomial regex backtracking in toCamelCase helper

* Add test for toCamelCase with non-word character separators

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-11-02 20:18:25 +01:00
Claude 43e9f059d7 chore(docs): upgrade to VitePress 2
- Bump vitepress to 2.0.0-alpha.21
- Drop direct @docsearch/js and @docsearch/css deps; VitePress 2 ships
  DocSearch v4 and imports its styles itself
- VPNavBarSearch override: use the new VPAlgoliaSearchBox props
  (algoliaOptions + openRequest) instead of dispatching synthetic
  keydown events
- VPCarbonAds override: replace removed useAside composable with a
  media query, matching upstream
- VPFooter override: use useLayout from vitepress/theme instead of the
  removed useSidebar().hasSidebar

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G9u8FhZukDbCKe1ZFByn43
2026-11-02 18:08:55 +00:00
Eric Fennis b196797ede Update deps (#5146) 2026-11-02 18:39:06 +01:00
dependabot[bot]Claude Opus 5.5dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
7ebbb6abc3 chore(deps-dev): bump the solid-deps group across 1 directory with 6 updates (#5142)
* chore(deps-dev): bump the solid-deps group across 1 directory with 6 updates

Bumps the solid-deps group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) | `7.29.7` | `8.0.10` |
| [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) | `7.29.7` | `8.0.7` |
| [@babel/preset-typescript](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-typescript) | `7.29.7` | `8.0.7` |
| [@rollup/plugin-babel](https://github.com/rollup/plugins/tree/HEAD/packages/babel) | `6.1.0` | `7.1.0` |
| [@solidjs/testing-library](https://github.com/solidjs/solid-testing-library) | `0.8.10` | `1.0.0-rc.2` |
| [vite-plugin-solid](https://github.com/solidjs/solid-vite-plugin) | `2.11.14` | `2.11.15` |



Updates `@babel/core` from 7.29.7 to 8.0.10
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v8.0.10/packages/babel-core)

Updates `@babel/preset-env` from 7.29.7 to 8.0.7
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v8.0.7/packages/babel-preset-env)

Updates `@babel/preset-typescript` from 7.29.7 to 8.0.7
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v8.0.7/packages/babel-preset-typescript)

Updates `@rollup/plugin-babel` from 6.1.0 to 7.1.0
- [Changelog](https://github.com/rollup/plugins/blob/master/packages/babel/CHANGELOG.md)
- [Commits](https://github.com/rollup/plugins/commits/babel-v7.1.0/packages/babel)

Updates `@solidjs/testing-library` from 0.8.10 to 1.0.0-rc.2
- [Commits](https://github.com/solidjs/solid-testing-library/commits)

Updates `vite-plugin-solid` from 2.11.14 to 2.11.15
- [Release notes](https://github.com/solidjs/solid-vite-plugin/releases)
- [Changelog](https://github.com/solidjs/solid-vite-plugin/blob/main/CHANGELOG.md)
- [Commits](https://github.com/solidjs/solid-vite-plugin/compare/vite-plugin-solid@2.11.14...vite-plugin-solid@2.11.15)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-version: 8.0.10
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: solid-deps
- dependency-name: "@babel/preset-env"
  dependency-version: 8.0.7
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: solid-deps
- dependency-name: "@babel/preset-typescript"
  dependency-version: 8.0.7
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: solid-deps
- dependency-name: "@rollup/plugin-babel"
  dependency-version: 7.1.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: solid-deps
- dependency-name: "@solidjs/testing-library"
  dependency-version: 1.0.0-rc.2
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: solid-deps
- dependency-name: vite-plugin-solid
  dependency-version: 2.11.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: solid-deps
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(solid): make Solid builds work with Babel 8

- Drop the `bugfixes` option from `@babel/preset-env`; it was removed in
  Babel 8 because bugfix plugins are now always enabled.
- Enable the `jsx` parser plugin for `.jsx`/`.tsx` files. Babel 8's
  `@babel/preset-typescript` no longer does this, and babel-preset-solid's
  Babel 7 `@babel/plugin-syntax-jsx` skips it when TypeScript is enabled.
- Keep `@solidjs/testing-library` at `^0.8.10` for lucide-solid. The 1.x
  line targets Solid 2 (peer `solid-js >=2`) and breaks the Solid 1 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G9u8FhrFFoA9JwQHtymeAf

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 18:24:15 +01:00
dependabot[bot]Claude Opus 5.5dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
5beea9bf83 chore(deps-dev): bump react-native from 0.88.0 to 0.88.1 in the react-native-deps group across 1 directory (#5134)
* chore(deps-dev): bump react-native

Bumps the react-native-deps group with 1 update in the / directory: [react-native](https://github.com/react/react-native/tree/HEAD/packages/react-native).


Updates `react-native` from 0.88.0 to 0.88.1
- [Release notes](https://github.com/react/react-native/releases)
- [Changelog](https://github.com/react/react-native/blob/main/CHANGELOG.md)
- [Commits](https://github.com/react/react-native/commits/v0.88.1/packages/react-native)

---
updated-dependencies:
- dependency-name: react-native
  dependency-version: 0.88.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: react-native-deps
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(deps): regenerate pnpm-lock.yaml for react-native 0.88.1

Dependabot's lockfile for this bump added a second, peer-less
`vitest@5.0.3` snapshot and pointed `@vitest/coverage-v8` / `@vitest/ui`
at it. That loaded two vitest instances, which broke snapshot tests in
lucide, lucide-react, lucide-vue and lucide-react-native ("The snapshot
state ... is not found. Did you call 'SnapshotClient.setup()'?"). It also
pulled unrelated @types/node and @babel/* bumps into other importers.

Regenerated with pnpm 11.22.0 from the base lockfile, so it now only
changes the react-native tree and hermes-compiler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G9u8Fhnpt1Di4KhMFtKbb2

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 18:06:07 +01:00
dependabot[bot]Claude Opus 5.5dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Eric
c06c5f0703 chore(deps-dev): bump the angular-deps group across 1 directory with 15 updates (#5114)
* chore(deps-dev): bump the angular-deps group across 1 directory with 15 updates

Bumps the angular-deps group with 15 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@angular-eslint/builder](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/builder) | `22.5.0` | `22.6.0` |
| [@angular-eslint/schematics](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/schematics) | `22.5.0` | `22.6.0` |
| [@angular/build](https://github.com/angular/angular-cli) | `22.2.2` | `22.2.5` |
| [@angular/cli](https://github.com/angular/angular-cli) | `22.2.2` | `22.2.5` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `22.2.2` | `22.2.4` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `22.2.2` | `22.2.4` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `22.2.2` | `22.2.4` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `22.2.2` | `22.2.4` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `22.2.2` | `22.2.4` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `22.2.2` | `22.2.4` |
| [@angular/platform-server](https://github.com/angular/angular/tree/HEAD/packages/platform-server) | `22.2.2` | `22.2.4` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `22.2.2` | `22.2.4` |
| [@vitest/browser-playwright](https://github.com/vitest-dev/vitest/tree/HEAD/packages/browser-playwright) | `5.0.3` | `5.0.5` |
| [angular-eslint](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/angular-eslint) | `22.5.0` | `22.6.0` |
| [ng-packagr](https://github.com/ng-packagr/ng-packagr) | `22.2.4` | `22.2.5` |



Updates `@angular-eslint/builder` from 22.5.0 to 22.6.0
- [Release notes](https://github.com/angular-eslint/angular-eslint/releases)
- [Changelog](https://github.com/angular-eslint/angular-eslint/blob/main/packages/builder/CHANGELOG.md)
- [Commits](https://github.com/angular-eslint/angular-eslint/commits/v22.6.0/packages/builder)

Updates `@angular-eslint/schematics` from 22.5.0 to 22.6.0
- [Release notes](https://github.com/angular-eslint/angular-eslint/releases)
- [Changelog](https://github.com/angular-eslint/angular-eslint/blob/main/packages/schematics/CHANGELOG.md)
- [Commits](https://github.com/angular-eslint/angular-eslint/commits/v22.6.0/packages/schematics)

Updates `@angular/build` from 22.2.2 to 22.2.5
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/v22.2.5/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v22.2.2...v22.2.5)

Updates `@angular/cli` from 22.2.2 to 22.2.5
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/v22.2.5/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v22.2.2...v22.2.5)

Updates `@angular/common` from 22.2.2 to 22.2.4
- [Release notes](https://github.com/angular/angular/releases)
- [Commits](https://github.com/angular/angular/commits/v22.2.4/packages/common)

Updates `@angular/compiler` from 22.2.2 to 22.2.4
- [Release notes](https://github.com/angular/angular/releases)
- [Commits](https://github.com/angular/angular/commits/v22.2.4/packages/compiler)

Updates `@angular/compiler-cli` from 22.2.2 to 22.2.4
- [Release notes](https://github.com/angular/angular/releases)
- [Commits](https://github.com/angular/angular/commits/v22.2.4/packages/compiler-cli)

Updates `@angular/core` from 22.2.2 to 22.2.4
- [Release notes](https://github.com/angular/angular/releases)
- [Commits](https://github.com/angular/angular/commits/v22.2.4/packages/core)

Updates `@angular/forms` from 22.2.2 to 22.2.4
- [Release notes](https://github.com/angular/angular/releases)
- [Commits](https://github.com/angular/angular/commits/v22.2.4/packages/forms)

Updates `@angular/platform-browser` from 22.2.2 to 22.2.4
- [Release notes](https://github.com/angular/angular/releases)
- [Commits](https://github.com/angular/angular/commits/v22.2.4/packages/platform-browser)

Updates `@angular/platform-server` from 22.2.2 to 22.2.4
- [Release notes](https://github.com/angular/angular/releases)
- [Commits](https://github.com/angular/angular/commits/v22.2.4/packages/platform-server)

Updates `@angular/router` from 22.2.2 to 22.2.4
- [Release notes](https://github.com/angular/angular/releases)
- [Commits](https://github.com/angular/angular/commits/v22.2.4/packages/router)

Updates `@vitest/browser-playwright` from 5.0.3 to 5.0.5
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.5/packages/browser-playwright)

Updates `angular-eslint` from 22.5.0 to 22.6.0
- [Release notes](https://github.com/angular-eslint/angular-eslint/releases)
- [Changelog](https://github.com/angular-eslint/angular-eslint/blob/main/packages/angular-eslint/CHANGELOG.md)
- [Commits](https://github.com/angular-eslint/angular-eslint/commits/v22.6.0/packages/angular-eslint)

Updates `ng-packagr` from 22.2.4 to 22.2.5
- [Release notes](https://github.com/ng-packagr/ng-packagr/releases)
- [Changelog](https://github.com/ng-packagr/ng-packagr/blob/22.2.5/CHANGELOG.md)
- [Commits](https://github.com/ng-packagr/ng-packagr/compare/22.2.4...22.2.5)

---
updated-dependencies:
- dependency-name: "@angular-eslint/builder"
  dependency-version: 22.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: angular-deps
- dependency-name: "@angular-eslint/schematics"
  dependency-version: 22.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: angular-deps
- dependency-name: "@angular/build"
  dependency-version: 22.2.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/cli"
  dependency-version: 22.2.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/common"
  dependency-version: 22.2.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/compiler"
  dependency-version: 22.2.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/compiler-cli"
  dependency-version: 22.2.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/core"
  dependency-version: 22.2.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/forms"
  dependency-version: 22.2.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/platform-browser"
  dependency-version: 22.2.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/platform-server"
  dependency-version: 22.2.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/router"
  dependency-version: 22.2.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@vitest/browser-playwright"
  dependency-version: 5.0.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: angular-eslint
  dependency-version: 22.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: angular-deps
- dependency-name: ng-packagr
  dependency-version: 22.2.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
...

Signed-off-by: dependabot[bot] <support@github.com>

* Dedupe deps

* fix(deps): align vitest override with @vitest/* 5.0.5

The workspace override pinned vitest to 5.0.3 while package.json files
already ask for 5.0.5, and this PR moves @vitest/browser-playwright to
5.0.5. That left @vitest/browser, mocker, utils and spy at 5.0.5 next
to a vitest 5.0.3 runtime, so the browser-mode packages were out of
sync with vitest. This broke:

- `expect.element` and jest-dom matcher types (Svelte, Nuxt, Solid 2)
- snapshot state in @lucide/vue tests ("snapshot state ... not found")

Bump the override to 5.0.5 so the whole vitest family resolves to a
single version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G9u8FeiSseTUuaZQjh6Z2N

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eric <eric.fennis@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 18:00:37 +01:00
Vugar BakhishovandVugar Bakhishov 588113e359 test(build-icons): cover Windows file URL imports (#4862)
* fix(dev): import icon templates with file URLs

Convert the resolved template path to an encoded file URL so the Node.js ESM loader accepts Windows paths and URL characters.

* fix(dev): import icon metadata with file URLs

Encode metadata file paths before dynamic JSON import, including Windows drive paths and URL characters.

* test(dev): cover icon build assets in paths with URL characters

Run the icon CLI in a Node process with a small SVG and metadata fixture; verify generation and metadata in a path containing spaces, # and %.

* test(build-icons): cover Windows file URL imports

---------

Co-authored-by: Vugar Bakhishov <vugarbbakhishov-hub@users.noreply.github.com>
2026-11-02 18:00:04 +01:00
DrashtiC07andEric 08193b1ef5 feat(lab): added cup-bubble-tea icon (#5098)
* feat(icons): add boba icon

* feat(lab): add boba icon

* Rename icon

* Update design

---------

Co-authored-by: Eric <eric.fennis@gmail.com>
2026-11-02 17:34:13 +01:00
Eric Fennis 0a3b505812 Make it work 2026-07-17 18:17:59 +02:00
Eric Fennis f7adb61d78 update lockfile 2026-07-17 16:48:12 +02:00
Eric Fennis ca9d8d693a Merge branch 'main' of https://github.com/lucide-icons/lucide into vitepress-nitro-plugin 2026-07-17 16:48:06 +02:00
Eric Fennis c46ecc3230 Merge branch 'main' of https://github.com/lucide-icons/lucide into vitepress-nitro-plugin 2026-03-05 18:45:17 +01:00
Eric Fennis 3c7c64b583 uh 2026-03-05 13:32:53 +01:00
Eric Fennis 9beea31e3f Fix types 2026-03-05 13:07:02 +01:00
Eric Fennis ee8b855533 Update lockfile 2026-03-05 12:41:46 +01:00
Eric Fennis 9667df9f59 Merge branch 'update-nitro' of https://github.com/lucide-icons/lucide into vitepress-nitro-plugin 2026-03-05 12:39:33 +01:00
Eric Fennis 82f13daeb2 Merge branch 'main' of https://github.com/lucide-icons/lucide into update-nitro 2026-03-05 12:39:07 +01:00
Eric Fennis 7d58a7a6cd Disable public directory in nitro build 2026-03-05 12:36:44 +01:00
Eric Fennis f59954da43 update docs 2026-03-05 12:29:23 +01:00
Eric Fennis 11e50e10cc Move api directory 2026-03-05 12:27:58 +01:00
Eric Fennis 8249335371 make it work 2026-03-05 11:12:22 +01:00
Eric Fennis a0ea6a6550 Merge branch 'update-nitro' of https://github.com/lucide-icons/lucide into vitepress-nitro-plugin 2026-03-05 10:17:54 +01:00
Eric Fennis ac707c99f2 Merge branch 'main' of https://github.com/lucide-icons/lucide into update-nitro 2026-03-02 07:25:16 +01:00
Eric Fennis 839cc915af Format code 2026-02-26 17:34:02 +01:00
Eric Fennis 5f2ccade97 Try @analogjs/vite-plugin-nitro 2026-02-26 17:32:31 +01:00
Eric Fennis 2bdcec6c0a Remove @resvg/resvg-wasm 2026-02-26 17:21:11 +01:00
Eric Fennis 526aacb192 Fix types 2026-02-26 17:08:02 +01:00
Eric Fennis 68a7c22cbd Adjust react in api 2026-02-26 16:57:10 +01:00
Eric Fennis 0020c21fd6 Fix output 2026-02-26 15:46:46 +01:00
Eric Fennis 40dd406524 Adjust 404 catch all 2026-02-26 15:31:29 +01:00
Eric Fennis 68afe65eef Fix endpoints 2026-02-26 15:17:44 +01:00
Eric Fennis 896e43c732 Add await 2026-02-26 14:59:48 +01:00
Eric Fennis 2203a715cd update vercel output 2026-02-26 14:54:30 +01:00
Eric Fennis e6d4ac6b26 Try this 2026-02-26 14:45:55 +01:00
Eric Fennis 9bf36567f9 try this? 2026-02-26 12:33:53 +01:00
Eric Fennis 8c60563eea update nitro 2026-02-26 12:08:05 +01:00
149 changed files with 3839 additions and 4141 deletions
@@ -12,6 +12,6 @@
"@octokit/rest": "^22.0.1",
"dotenv": "^17.4.2",
"openai": "^7.9.0",
"zod": "^4.5.4"
"zod": "^4.6.7"
}
}
@@ -56,7 +56,7 @@ When checking or adding a `$group:` marker:
- A marker must be exactly `"$group:"` followed by the name of an existing file in `groups/`, without the `.json` extension, in lowercase kebab-case.
- `$group:` is allowed **only** inside `tags`. It is not valid in `categories` or `contributors`.
- Groups are named after the variant suffix: `file-plus` uses `$group:plus`, `file-question-mark` uses `$group:question-mark`. Suffixes with the same meaning share a group: `mail-warning` uses `$group:exclamation-mark`, and every currency suffix uses `$group:currency`.
- Groups are named after the variant suffix: `file-plus` uses `$group:plus`, `file-question-mark` uses `$group:question-mark`. Suffixes with the same meaning share a group: `mail-exclamation-point` uses `$group:exclamation-mark`, and every currency suffix uses `$group:currency`.
- The same suffix can mean different things in different contexts — `-off` means "disabled" on `wifi-off` but "allergy free" on `wheat-off` — so only use the group when the icon carries the group's meaning, and keep context-specific tags as literals.
- Do not repeat a tag a group already provides, and do not use the same group twice in one array.
- Conventional order: the `$extends:` base icon first, then `$group:` markers, then the icon's own literal tags.
+1 -1
View File
@@ -175,7 +175,7 @@ cd docs
2. **Start the Local Development Server**
```sh
pnpm run docs:dev
pnpm dev
```
3. **Open the Website Locally**
+1 -1
View File
@@ -2,5 +2,5 @@
"$schema": "../category.schema.json",
"title": "Notifications",
"description": "Icons for alerts, status updates, reminders, approvals, and attention states.",
"icon": "triangle-alert"
"icon": "triangle-exclamation-point"
}
@@ -1,11 +0,0 @@
import { eventHandler, setResponseHeader } from 'h3';
import iconMetaData from '../../data/iconMetaData';
export default eventHandler((event) => {
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
return Object.fromEntries(
Object.entries(iconMetaData).map(([name, { categories }]) => [name, categories]),
);
});
@@ -1,14 +0,0 @@
import { eventHandler, setResponseHeader } from 'h3';
import iconMetaData from '../../../data/lab/iconMetaData';
export default eventHandler((event) => {
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
for (const iconDetail in iconMetaData) {
// Remove $schema from the response
delete iconMetaData[iconDetail]['$schema'];
}
return iconMetaData;
});
-9
View File
@@ -1,9 +0,0 @@
import { eventHandler, setResponseHeader } from 'h3';
import iconMetaData from '../../data/iconMetaData';
export default eventHandler((event) => {
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
return Object.fromEntries(Object.entries(iconMetaData).map(([name, { tags }]) => [name, tags]));
});
+33 -7
View File
@@ -1,15 +1,17 @@
import { fileURLToPath, URL } from 'node:url';
import type { Plugin } from 'vite';
import { defineConfig, UserConfig } from 'vitepress';
import { groupIconMdPlugin, groupIconVitePlugin } from 'vitepress-plugin-group-icons';
import sidebar from './sidebar';
import snackPlayer from './markdown/snackPlayer';
import sandpackPlugin from './markdown/sandpack';
import examplePlugin from './markdown/example';
import sidebar from './sidebar/index.ts';
import { nitro } from 'nitro/vite';
import snackPlayer from './markdown/snackPlayer.ts';
import sandpackPlugin from './markdown/sandpack.ts';
import examplePlugin from './markdown/example.ts';
import { readFile } from 'node:fs/promises';
import { resourcesSidebar } from './sidebar/resources';
import { resourcesSidebar } from './sidebar/resources.ts';
import llmstxt from 'vitepress-plugin-llms';
import { transformPageData } from './transformPageData';
import getHeadConfig from './getHeadConfig';
import { transformPageData } from './transformPageData.ts';
import getHeadConfig from './getHeadConfig.ts';
const defaultSandpackCSS = await readFile(
fileURLToPath(new URL('./theme/sandpack-default.css', import.meta.url)),
@@ -24,6 +26,26 @@ const title = 'Lucide';
const socialTitle = 'Lucide Icons';
const description = 'Beautiful & consistent icon toolkit made by the community.';
const isDev = process.env.NODE_ENV !== 'production';
/**
* Nitro's dev middleware assumes it owns the app: it answers every extensionless request and
* never falls through, so VitePress pages would 404. `_nitroHandled` is the flag it checks to
* skip a request. It is private, so re-check this when upgrading Nitro.
*/
const nitroApiOnly: Plugin = {
name: 'lucide:nitro-api-only',
apply: 'serve',
configureServer(server) {
server.middlewares.use((req, _res, next) => {
if (!req.url?.startsWith('/api/')) {
(req as typeof req & { _nitroHandled?: boolean })._nitroHandled = true;
}
next();
});
},
};
// https://vitepress.dev/reference/site-config
export default defineConfig({
title,
@@ -48,6 +70,8 @@ export default defineConfig({
},
},
vite: {
// Nitro defaults this to 'custom', which drops the HTML fallback VitePress relies on in dev.
appType: 'spa',
resolve: {
alias: [
{
@@ -81,6 +105,8 @@ export default defineConfig({
],
},
plugins: [
isDev && nitroApiOnly,
isDev && nitro(),
groupIconVitePlugin(),
llmstxt({
ignoreFiles: [
+5 -3
View File
@@ -16,8 +16,10 @@ export type Path = {
export type PathProps<
RequiredProps extends keyof React.SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
NeverProps extends keyof React.SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
> = Required<Pick<React.SVGProps<SVGElement & SVGRectElement & SVGCircleElement>, RequiredProps>> &
> = Required<
Pick<React.SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>, RequiredProps>
> &
Omit<
React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>,
RequiredProps & NeverProps
React.SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
RequiredProps | NeverProps
>;
+1 -1
View File
@@ -1,6 +1,6 @@
import fs from 'fs';
import path from 'path';
import { Category, IconEntity } from '../theme/types';
import type { Category, IconEntity } from '../theme/types.ts';
const directory = path.join(process.cwd(), '../categories');
@@ -1,4 +1,4 @@
import { highLightCode } from './createCodeExamples';
import { highLightCode } from './createCodeExamples.ts';
type CodeExampleType = {
title: string;
+1 -1
View File
@@ -1,6 +1,6 @@
import { promises as fs, constants } from 'fs';
import path from 'path';
import { PackageItem } from '../theme/types';
import { type PackageItem } from '../theme/types.ts';
const fileExist = (filePath) =>
fs
+1 -1
View File
@@ -1,6 +1,6 @@
import { readFileSync } from 'node:fs';
import { dirname, isAbsolute, resolve } from 'node:path';
import type { MarkdownIt, RenderRule } from './types';
import type { MarkdownIt, RenderRule } from './types.ts';
import container from 'markdown-it-container';
type ContainerArgs = [typeof container, string, { render: RenderRule }];
+2 -2
View File
@@ -1,6 +1,6 @@
import type { MarkdownIt, RenderRule, Token } from './types';
import type { MarkdownIt, RenderRule, Token } from './types.ts';
import container from 'markdown-it-container';
import sandpackTheme from '../theme/sandpackTheme.json';
import sandpackTheme from '../theme/sandpackTheme.json' with { type: 'json' };
type SnackParams = {
defaultFiles?: Record<
+1 -1
View File
@@ -16,7 +16,7 @@
* />
*/
import type { MarkdownIt } from './types';
import type { MarkdownIt } from './types.ts';
type SnackParams = Record<string, string>;
+11 -11
View File
@@ -1,15 +1,15 @@
import { DefaultTheme, UserConfig } from 'vitepress';
import { reactSidebar } from './react';
import { vueSidebar } from './vue';
import { svelteSidebar } from './svelte';
import { lucideSidebar } from './lucide';
import { solidSidebar } from './solid';
import { preactSidebar } from './preact';
import { reactNativeSidebar } from './react-native';
import { astroSidebar } from './astro';
import { resourcesSidebar } from './resources';
import { angularSidebar } from './angular';
import { lucideStaticSidebar } from './static';
import { reactSidebar } from './react.ts';
import { vueSidebar } from './vue.ts';
import { svelteSidebar } from './svelte.ts';
import { lucideSidebar } from './lucide.ts';
import { solidSidebar } from './solid.ts';
import { preactSidebar } from './preact.ts';
import { reactNativeSidebar } from './react-native.ts';
import { astroSidebar } from './astro.ts';
import { resourcesSidebar } from './resources.ts';
import { angularSidebar } from './angular.ts';
import { lucideStaticSidebar } from './static.ts';
type Sidebar = UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'];
@@ -1,8 +1,9 @@
<script setup lang="ts">
import { ref, shallowRef } from 'vue';
import { onMounted, ref, shallowRef, type DefineComponent } from 'vue';
import { data } from './HomeHeroIconsCard.data';
import { useRouter } from 'vitepress';
import { motion, useScroll, useTransform, type Options as MotionOptions } from 'motion-v';
import { useMediaQuery } from '@vueuse/core';
import LucideIcon from '../base/LucideIcon.vue';
// motion-v re-exports framer-motion's `Variants`, which doesn't match the `variants` prop of its components
@@ -10,7 +11,8 @@ type Variants = MotionOptions['variants'];
const emit = defineEmits(['animation-complete']);
const MotionLucideIcon = motion.create(LucideIcon);
// motion.create only accepts a props-less DefineComponent, so the SFC's typed props need widening
const MotionLucideIcon = motion.create(LucideIcon as unknown as DefineComponent);
const preventFocusProps = {
tabindex: '-1',
@@ -21,6 +23,89 @@ const COLUMNS = 8;
const SIZE = 2;
const GAP = 1;
/** The two spirals of the Lucide logo, drawn the way the SVG editor previews them. */
const LOGO_PATH_OUTER =
'M14 12C14 9.79086 12.2091 8 10 8C7.79086 8 6 9.79086 6 12C6 16.4183 9.58172 20 14 20C18.4183 20 22 16.4183 22 12C22 8.446 20.455 5.25285 18 3.05557';
const LOGO_PATH_INNER =
'M10 12C10 14.2091 11.7909 16 14 16C16.2091 16 18 14.2091 18 12C18 7.58172 14.4183 4 10 4C5.58172 4 2 7.58172 2 12C2 15.5841 3.57127 18.8012 6.06253 21';
const VERTICES =
'M14 12h.01M10 8h.01M6 12h.01M14 20h.01M22 12h.01M18 3.05557h.01M10 12h.01M14 16h.01M18 12h.01M10 4h.01M2 12h.01M6.06253 21h.01';
const MARKERS = [
[14, 12],
[18, 3.05557],
[10, 12],
[6.06253, 21],
];
/** Bezier handles as [x1, y1, x2, y2] segments, grouped by when they appear. */
const HANDLE_GROUPS = [
// Outer spiral
{ delay: 0.2, segments: [[14, 12, 14, 9.79086]] },
{
delay: 0.4,
segments: [
[10, 8, 12.2091, 8],
[10, 8, 7.79086, 8],
],
},
{
delay: 0.6,
segments: [
[6, 12, 6, 9.79086],
[6, 12, 6, 16.4183],
],
},
{
delay: 0.8,
segments: [
[14, 20, 9.58172, 20],
[14, 20, 18.4183, 20],
],
},
{
delay: 1,
segments: [
[22, 12, 22, 16.4183],
[22, 12, 22, 8.446],
],
},
{ delay: 1.2, segments: [[18, 3.05557, 20.455, 5.25285]] },
// Inner spiral
{ delay: 0.2, segments: [[10, 12, 10, 14.2091]] },
{
delay: 0.4,
segments: [
[14, 16, 11.7909, 16],
[14, 16, 16.2091, 16],
],
},
{
delay: 0.6,
segments: [
[18, 12, 18, 14.2091],
[18, 12, 18, 7.58172],
],
},
{
delay: 0.8,
segments: [
[10, 4, 14.4183, 4],
[10, 4, 5.58172, 4],
],
},
{
delay: 1,
segments: [
[2, 12, 2, 7.58172],
[2, 12, 2, 15.5841],
],
},
{ delay: 1.2, segments: [[6.06253, 21, 3.57127, 18.8012]] },
];
const { scrollYProgress } = useScroll();
const opacity = useTransform(() => 1 - scrollYProgress.get() * 8);
@@ -101,6 +186,9 @@ const draw: Variants = {
// motion-v passes the variant label that finished, not the resolved variant
const onAnimationComplete = (variant: unknown) => {
// Reduced motion stops at the icon grid, so don't shrink the icons or loop
if (reducedMotion.value) return;
if (variant === 'visible') {
drawAnimation.value = 'exit';
return;
@@ -155,6 +243,24 @@ const shrinkIconVariants: Variants = {
transition: { delay: 1.8, duration: 1.5, ease: 'easeInOut' },
}),
};
// Only rendered on the client, so reduced motion is known before the first frame
const mounted = ref(false);
const reducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
onMounted(() => {
if (reducedMotion.value) {
drawAnimation.value = 'exit';
showHandles.value = false;
scaleDownAnimation.value = 'small';
iconGridAnimation.value = 'showIcons';
}
mounted.value = true;
});
/** Skips straight to the end state when the user prefers reduced motion. */
const initial = <T,>(value: T) => (reducedMotion.value ? false : value);
</script>
<template>
@@ -167,6 +273,7 @@ const shrinkIconVariants: Variants = {
v-bind="preventFocusProps"
>
<motion.svg
v-if="mounted"
xmlns="http://www.w3.org/2000/svg"
viewBox="-12 -12 48 48"
fill="none"
@@ -209,7 +316,7 @@ const shrinkIconVariants: Variants = {
<MotionLucideIcon
v-for="(icon, index) in icons"
size="2"
initial="initial"
:initial="initial('initial')"
:key="icon.name"
:variants="iconAnimationVariants"
:animate="iconGridAnimation"
@@ -223,15 +330,15 @@ const shrinkIconVariants: Variants = {
<motion.g
:variants="scaleDownVariants"
:animate="scaleDownAnimation"
initial="hidden"
:initial="initial('hidden')"
@animation-complete="onAnimationComplete"
v-bind="preventFocusProps"
>
<motion.path
d="M14 12C14 9.79086 12.2091 8 10 8C7.79086 8 6 9.79086 6 12C6 16.4183 9.58172 20 14 20C18.4183 20 22 16.4183 22 12C22 8.446 20.455 5.25285 18 3.05557"
:style="{ stroke: 'var(--vp-c-gray-1)' }"
:d="LOGO_PATH_OUTER"
:style="{ stroke: reducedMotion ? 'var(--vp-c-text-1)' : 'var(--vp-c-gray-1)' }"
:animate="drawAnimation"
initial="hidden"
:initial="initial('hidden')"
:variants="draw"
:custom="1"
@animation-complete="onAnimationComplete"
@@ -239,10 +346,10 @@ const shrinkIconVariants: Variants = {
/>
<motion.path
d="M10 12C10 14.2091 11.7909 16 14 16C16.2091 16 18 14.2091 18 12C18 7.58172 14.4183 4 10 4C5.58172 4 2 7.58172 2 12C2 15.5841 3.57127 18.8012 6.06253 21"
:style="{ stroke: 'var(--vp-c-gray-1)' }"
:d="LOGO_PATH_INNER"
:style="{ stroke: reducedMotion ? 'var(--vp-c-brand)' : 'var(--vp-c-gray-1)' }"
:animate="drawAnimation"
initial="hidden"
:initial="initial('hidden')"
:variants="draw"
:custom="0"
v-bind="preventFocusProps"
@@ -253,80 +360,36 @@ const shrinkIconVariants: Variants = {
class="svg-preview-control-path-marker-group"
:style="{ stroke: 'var(--vp-c-text-1)' }"
stroke-width="0.125"
:initial="{ opacity: 1 }"
:animate="showHandles ? { opacity: 1 } : { opacity: 0 }"
:transition="{ delay: 0, duration: 0.2 }"
:initial="initial({ opacity: 1 })"
:animate="{ opacity: showHandles ? 1 : 0 }"
:transition="{ duration: 0.2 }"
v-bind="preventFocusProps"
>
<motion.path
d="M14 12C14 9.79086 12.2091 8 10 8C7.79086 8 6 9.79086 6 12C6 16.4183 9.58172 20 14 20C18.4183 20 22 16.4183 22 12C22 8.446 20.455 5.25285 18 3.05557"
:initial="{ opacity: 0 }"
v-for="d in [LOGO_PATH_OUTER, LOGO_PATH_INNER]"
:key="d"
:d="d"
:initial="initial({ opacity: 0 })"
:animate="{ opacity: 1 }"
:transition="{ delay: 1.6, duration: 1.5 }"
v-bind="preventFocusProps"
/>
<motion.path
d="M10 12C10 14.2091 11.7909 16 14 16C16.2091 16 18 14.2091 18 12C18 7.58172 14.4183 4 10 4C5.58172 4 2 7.58172 2 12C2 15.5841 3.57127 18.8012 6.06253 21"
:initial="{ opacity: 0 }"
:animate="{ opacity: 1 }"
:transition="{ delay: 1.6, duration: 1.5 }"
v-bind="preventFocusProps"
/>
<motion.g
:initial="{ opacity: 0 }"
:d="VERTICES"
:initial="initial({ opacity: 0 })"
:animate="{ opacity: 1 }"
:transition="{ delay: 0.2, duration: 0.3 }"
v-bind="preventFocusProps"
>
<path
d="M14 12h.01M10 8h.01M10 8h.01M6 12h.01M6 12h.01M14 20h.01M14 20h.01M22 12h.01M22 12h.01M18 3.05557h.01M10 12h.01M14 16h.01M14 16h.01M18 12h.01M18 12h.01M10 4h.01M10 4h.01M2 12h.01M2 12h.01M6.06253 21h.01"
>
</path>
</motion.g>
<motion.circle
:initial="{ opacity: 0, scale: 0.2 }"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0, duration: 0.8 }"
cx="14"
cy="12"
r="0.5"
v-bind="preventFocusProps"
/>
<motion.circle
:initial="{ opacity: 0, scale: 0.2 }"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0, duration: 0.8 }"
cx="14"
cy="12"
v-for="[cx, cy] in MARKERS"
:key="`${cx},${cy}`"
:cx="cx"
:cy="cy"
r="0.5"
v-bind="preventFocusProps"
/>
<motion.circle
:initial="{ opacity: 0, scale: 0.2 }"
:initial="initial({ opacity: 0, scale: 0.2 })"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0, duration: 0.8 }"
cx="18"
cy="3.05557"
r="0.5"
v-bind="preventFocusProps"
/>
<motion.circle
:initial="{ opacity: 0, scale: 0.2 }"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0, duration: 0.8 }"
cx="10"
cy="12"
r="0.5"
v-bind="preventFocusProps"
/>
<motion.circle
:initial="{ opacity: 0, scale: 0.2 }"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0, duration: 0.8 }"
cx="6.06253"
cy="21"
r="0.5"
:transition="{ duration: 0.8 }"
v-bind="preventFocusProps"
/>
</motion.g>
@@ -335,221 +398,30 @@ const shrinkIconVariants: Variants = {
stroke-width="0.12"
stroke="#FFF"
stroke-opacity="0.3"
:initial="{ opacity: 1 }"
:animate="showHandles ? { opacity: 1 } : { opacity: 0 }"
:transition="{ delay: 0, duration: 0.6 }"
:initial="initial({ opacity: 1 })"
:animate="{ opacity: showHandles ? 1 : 0 }"
:transition="{ duration: 0.6 }"
v-bind="preventFocusProps"
>
<motion.g
:initial="{ opacity: 0, scale: 0.2 }"
v-for="(group, index) in HANDLE_GROUPS"
:key="index"
:initial="initial({ opacity: 0, scale: 0.2 })"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0.2, duration: 0.3 }"
:transition="{ delay: group.delay, duration: 0.3 }"
v-bind="preventFocusProps"
>
<path d="M14 12 14 9.79086"></path>
<circle
cy="9.79086"
cx="14"
r="0.25"
></circle>
</motion.g>
<motion.g
:initial="{ opacity: 0, scale: 0.2 }"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0.4, duration: 0.3 }"
v-bind="preventFocusProps"
>
<path d="M10 8 12.2091 8"></path>
<circle
cy="8"
cx="12.2091"
r="0.25"
></circle>
<path d="M10 8 7.79086 8"></path>
<circle
cy="8"
cx="7.79086"
r="0.25"
></circle>
</motion.g>
<motion.g
:initial="{ opacity: 0, scale: 0.2 }"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0.6, duration: 0.3 }"
v-bind="preventFocusProps"
>
<path d="M6 12 6 9.79086"></path>
<circle
cy="9.79086"
cx="6"
r="0.25"
></circle>
<path d="M6 12 6 16.4183"></path>
<circle
cy="16.4183"
cx="6"
r="0.25"
></circle>
</motion.g>
<motion.g
:initial="{ opacity: 0, scale: 0.2 }"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0.8, duration: 0.3 }"
v-bind="preventFocusProps"
>
<path d="M14 20 9.58172 20"></path>
<circle
cy="20"
cx="9.58172"
r="0.25"
></circle>
<path d="M14 20 18.4183 20"></path>
<circle
cy="20"
cx="18.4183"
r="0.25"
></circle>
</motion.g>
<motion.g
:initial="{ opacity: 0, scale: 0.2 }"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 1, duration: 0.3 }"
v-bind="preventFocusProps"
>
<path d="M22 12 22 16.4183"></path>
<circle
cy="16.4183"
cx="22"
r="0.25"
></circle>
<path d="M22 12 22 8.446"></path>
<circle
cy="8.446"
cx="22"
r="0.25"
></circle>
</motion.g>
<motion.g
:initial="{ opacity: 0, scale: 0.2 }"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 1.2, duration: 0.3 }"
v-bind="preventFocusProps"
>
<path d="M18 3.05557 20.455 5.25285"></path>
<circle
cy="5.25285"
cx="20.455"
r="0.25"
></circle>
</motion.g>
<motion.g
:initial="{ opacity: 0, scale: 0.2 }"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0.2, duration: 0.3 }"
v-bind="preventFocusProps"
>
<path d="M10 12 10 14.2091"></path>
<circle
cy="14.2091"
cx="10"
r="0.25"
></circle>
</motion.g>
<motion.g
:initial="{ opacity: 0, scale: 0.2 }"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0.4, duration: 0.3 }"
v-bind="preventFocusProps"
>
<path d="M14 16 11.7909 16"></path>
<circle
cy="16"
cx="11.7909"
r="0.25"
></circle>
<path d="M14 16 16.2091 16"></path>
<circle
cy="16"
cx="16.2091"
r="0.25"
></circle>
</motion.g>
<motion.g
:initial="{ opacity: 0, scale: 0.2 }"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0.6, duration: 0.3 }"
v-bind="preventFocusProps"
>
<path d="M18 12 18 14.2091"></path>
<circle
cy="14.2091"
cx="18"
r="0.25"
></circle>
<path d="M18 12 18 7.58172"></path>
<circle
cy="7.58172"
cx="18"
r="0.25"
></circle>
</motion.g>
<motion.g
:initial="{ opacity: 0, scale: 0.2 }"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0.8, duration: 0.3 }"
v-bind="preventFocusProps"
>
<path d="M10 4 14.4183 4"></path>
<circle
cy="4"
cx="14.4183"
r="0.25"
></circle>
<path d="M10 4 5.58172 4"></path>
<circle
cy="4"
cx="5.58172"
r="0.25"
></circle>
</motion.g>
<motion.g
:initial="{ opacity: 0, scale: 0.2 }"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 1, duration: 0.3 }"
v-bind="preventFocusProps"
>
<path d="M2 12 2 7.58172"></path>
<circle
cy="7.58172"
cx="2"
r="0.25"
></circle>
<path d="M2 12 2 15.5841"></path>
<circle
cy="15.5841"
cx="2"
r="0.25"
></circle>
</motion.g>
<motion.g
:initial="{ opacity: 0, scale: 0.2 }"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 1.2, duration: 0.3 }"
v-bind="preventFocusProps"
>
<path d="M6.06253 21 3.57127 18.8012"></path>
<circle
cy="18.8012"
cx="3.57127"
r="0.25"
></circle>
<template
v-for="[x1, y1, x2, y2] in group.segments"
:key="`${x2},${y2}`"
>
<path :d="`M${x1} ${y1} ${x2} ${y2}`" />
<circle
:cx="x2"
:cy="y2"
r="0.25"
/>
</template>
</motion.g>
</motion.g>
</motion.svg>
@@ -1,4 +1,4 @@
import iconNodes from '../../../data/iconNodes';
import iconNodes from '../../../data/iconNodes/index.ts';
const pickIcons = [
'bot',
@@ -1,5 +1,5 @@
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
import iconsMetaData from '../../../data/iconMetaData';
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories.ts';
import iconsMetaData from '../../../data/iconMetaData.ts';
const loader = {
async load() {
@@ -17,11 +17,11 @@ function onCategoriesClick() {
}
const categoriesIsActive = computed(() => {
return isActive(page.value.relativePath, '/icons/categories');
return isActive(page.value.relativePath, '', '/icons/categories');
});
const overviewIsActive = computed(() => {
return isActive(page.value.relativePath, '/icons/');
return isActive(page.value.relativePath, '', '/icons/');
});
const headers = computed(() => {
@@ -6,7 +6,7 @@ import Calendar from '../../../data/iconDetails/calendar.ts';
import Clock from '../../../data/iconDetails/clock.ts';
import Bug from '../../../data/iconDetails/bug.ts';
import Rocket from '../../../data/iconDetails/rocket.ts';
import TriangleAlert from '../../../data/iconDetails/triangle-alert.ts';
import TriangleExclamationPoint from '../../../data/iconDetails/triangle-exclamation-point.ts';
import PartyPopper from '../../../data/iconDetails/party-popper.ts';
import Scissors from '../../../data/iconDetails/scissors.ts';
import Copy from '../../../data/iconDetails/copy.ts';
@@ -41,7 +41,7 @@ const CalendarIcon = createLucideIcon('calendar', Calendar.iconNode);
const ClockIcon = createLucideIcon('clock', Clock.iconNode);
const BugIcon = createLucideIcon('bug', Bug.iconNode);
const RocketIcon = createLucideIcon('rocket', Rocket.iconNode);
const AlertTriangleIcon = createLucideIcon('alert-triangle', TriangleAlert.iconNode);
const TriangleExclamationPointIcon = createLucideIcon('triangle-exclamation-point', TriangleExclamationPoint.iconNode);
const ScissorsIcon = createLucideIcon('scissors', Scissors.iconNode);
const CopyIcon = createLucideIcon('copy', Copy.iconNode);
const SaveIcon = createLucideIcon('save', Save.iconNode);
@@ -128,7 +128,7 @@ const prettyName = props.name
>
<div class="placeholder"></div>
<div class="badge badge-red">
<AlertTriangleIcon :size="20" />
<TriangleExclamationPointIcon :size="20" />
Alert
</div>
</div>
@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { DefaultTheme } from 'vitepress/theme'
import { ref, watch, onMounted } from 'vue'
import { useAside } from 'vitepress/dist/client/theme-default/composables/aside.js'
import { useMediaQuery } from '@vueuse/core'
import { useData } from 'vitepress/dist/client/theme-default/composables/data.js'
const { page } = useData()
@@ -12,7 +12,7 @@ const props = defineProps<{
const carbonOptions = props.carbonAds
const { isAsideEnabled } = useAside()
const isAsideEnabled = useMediaQuery('(min-width: 80rem)')
const container = ref()
let isInitialized = false
@@ -1,12 +1,12 @@
<script setup lang="ts">
import { useData } from 'vitepress';
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar.js';
import { useLayout } from 'vitepress/theme';
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue';
import { computed } from 'vue';
import SurveyAskOverlay from '../SurveyAskOverlay.vue';
const { theme } = useData();
const { hasSidebar } = useSidebar();
const { hasSidebar } = useLayout();
const githubLink = computed(
() => theme.value.socialLinks.find(({ icon }) => icon === 'github').link,
@@ -2,31 +2,33 @@
import { ref } from 'vue'
// This component can be rendered more than once on a page (e.g. a hidden
// copy inside `.content-body`). DocSearch mounts into the first `#docsearch`
// in the document, so only a single instance may ever render the search box.
// copy inside `.content-body`). DocSearch mounts into `#vp-docsearch`, so only
// a single instance may ever render the search box.
const loaded = ref(false)
// Shared so that any instance (e.g. the visible input) can open the modal
// rendered by the owning instance.
const openRequest = ref<{ target: 'search'; nonce: number } | null>(null)
let openNonce = 0
// Every instance registers the hotkeys; only handle each event once.
let lastHandledEvent: KeyboardEvent | null = null
</script>
<script lang="ts" setup>
import '@docsearch/css/dist/style.css'
import {
computed,
defineAsyncComponent,
onMounted,
onUnmounted,
} from 'vue'
import { computed, defineAsyncComponent, onMounted } from 'vue'
import { onKeyStroke } from '@vueuse/core'
import type { DefaultTheme } from 'vitepress/theme'
import { useData } from 'vitepress'
import FakeInput from '../base/FakeInput.vue'
const VPAlgoliaSearchBox = defineAsyncComponent(() => import('vitepress/dist/client/theme-default/components/VPAlgoliaSearchBox.vue'));
const VPAlgoliaSearchBox = defineAsyncComponent(
() => import('vitepress/dist/client/theme-default/components/VPAlgoliaSearchBox.vue'),
)
const { theme } = useData<DefaultTheme.Config>()
const algolia = computed(
() =>
(theme.value.search?.options ?? theme.value.algolia) as DefaultTheme.AlgoliaSearchOptions,
const algoliaOptions = computed(
() => (theme.value.search?.options ?? {}) as DefaultTheme.AlgoliaSearchOptions,
)
// whether this instance is the one rendering the (hidden) search box
@@ -36,47 +38,39 @@ const shortcutText = ref('')
// to avoid loading the docsearch js upfront (which is more than 1/3 of the
// payload), we delay initializing it until the user has actually clicked or
// hit the hotkey to invoke it.
const preconnect = () => {
onMounted(() => {
shortcutText.value = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? '⌘K' : 'Ctrl+K'
const id = 'VPAlgoliaPreconnect'
if (document.getElementById(id)) return
if (document.getElementById(id) || !algoliaOptions.value.appId) return
const rIC = window.requestIdleCallback || setTimeout
rIC(() => {
const preconnect = document.createElement('link')
preconnect.id = id
preconnect.rel = 'preconnect'
preconnect.href = `https://${algolia.value.appId}-dsn.algolia.net`
preconnect.href = `https://${algoliaOptions.value.appId}-dsn.algolia.net`
preconnect.crossOrigin = ''
document.head.appendChild(preconnect)
})
}
})
onMounted(() => {
preconnect()
shortcutText.value = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? '⌘K' : 'Ctrl+K'
// once docsearch is initialized it handles the hotkey itself
const handleSearchHotKey = (event: KeyboardEvent) => {
if (loaded.value) return remove()
if (
(event.key.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) ||
(!isEditingContent(event) && event.key === '/')
) {
event.preventDefault()
open()
remove()
}
onKeyStroke('k', (event) => {
if (event === lastHandledEvent) return
if (event.ctrlKey || event.metaKey) {
lastHandledEvent = event
event.preventDefault()
open()
}
})
const remove = () => {
window.removeEventListener('keydown', handleSearchHotKey)
onKeyStroke('/', (event) => {
if (event === lastHandledEvent) return
if (!isEditingContent(event)) {
lastHandledEvent = event
event.preventDefault()
open()
}
window.addEventListener('keydown', handleSearchHotKey)
onUnmounted(remove)
})
function open() {
@@ -84,23 +78,8 @@ function open() {
loaded.value = true
isOwner.value = true
}
setTimeout(poll, 16)
}
function poll() {
// programmatically open the search box after initialize
const e = new Event('keydown') as any
e.key = 'k'
e.metaKey = true
window.dispatchEvent(e)
setTimeout(() => {
if (!document.querySelector('.DocSearch-Modal')) {
poll()
}
}, 16)
// The search box queues the request until DocSearch is ready.
openRequest.value = { target: 'search', nonce: ++openNonce }
}
function isEditingContent(event: KeyboardEvent): boolean {
@@ -129,7 +108,7 @@ function isEditingContent(event: KeyboardEvent): boolean {
<!-- docsearch renders its own button here; we only need its modal -->
<div v-if="isOwner" class="docsearch-host">
<VPAlgoliaSearchBox :algolia="algolia" />
<VPAlgoliaSearchBox :algolia-options="algoliaOptions" :open-request="openRequest" />
</div>
</div>
</template>
@@ -1,6 +1,6 @@
import packageDataList from '../../../data/packageData.json';
import thirdPartyPackages from '../../../data/packageData.thirdParty.json';
import fetchPackages from '../../../lib/fetchPackages';
import packageDataList from '../../../data/packageData.json' with { type: 'json' };
import thirdPartyPackages from '../../../data/packageData.thirdParty.json' with { type: 'json' };
import fetchPackages from '../../../lib/fetchPackages.ts';
const loader = {
async load() {
@@ -1,5 +1,5 @@
import iconNodes from '../../../data/iconNodes';
import fetchPackages from '../../../lib/fetchPackages';
import iconNodes from '../../../data/iconNodes/index.ts';
import fetchPackages from '../../../lib/fetchPackages.ts';
const NPM_DOWNLOADS_API = 'https://api.npmjs.org/downloads/point/last-week';
@@ -117,8 +117,6 @@ function scrollTrack(direction: 1 | -1) {
.tiers {
padding-top: 96px;
padding-bottom: 48px;
/* The track bleeds to the right edge of the viewport. */
overflow-x: clip;
}
.tiers-header {
@@ -165,7 +163,6 @@ function scrollTrack(direction: 1 | -1) {
.track-wrapper {
position: relative;
margin-right: min(-24px, calc((1152px - 100vw) / 2));
}
.track {
@@ -178,12 +175,16 @@ function scrollTrack(direction: 1 | -1) {
padding-bottom: 12px;
padding-right: 24px;
scroll-padding-right: 24px;
scrollbar-width: thin;
scrollbar-width: none;
scroll-snap-align: center;
scroll-padding-left: 24px;
scroll-padding-right: 24px;
}
.track::-webkit-scrollbar {
display: none;
}
.track:focus-visible {
outline: 2px solid var(--vp-c-brand-1);
outline-offset: 4px;
@@ -1,12 +1,9 @@
import { useFetch } from '@vueuse/core';
const useFetchCategories = () =>
useFetch<Record<string, string[]>>(
`${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/categories`,
{
immediate:
typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('search'),
},
).json();
useFetch<Record<string, string[]>>('/api/categories', {
immediate:
typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('search'),
}).json();
export default useFetchCategories;
@@ -1,12 +1,9 @@
import { useFetch } from '@vueuse/core';
const useFetchTags = () =>
useFetch<Record<string, string[]>>(
`${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/tags`,
{
immediate:
typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('search'),
},
).json();
useFetch<Record<string, string[]>>('/api/tags', {
immediate:
typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('search'),
}).json();
export default useFetchTags;
+2 -2
View File
@@ -1,6 +1,6 @@
import { type PageData } from 'vitepress';
import getStructuredData, { getFaqStructuredData } from './getStructuredData';
import { createGeneralOGImage, createIconOGImage } from './createOGImage';
import getStructuredData, { getFaqStructuredData } from './getStructuredData.ts';
import { createGeneralOGImage, createIconOGImage } from './createOGImage.ts';
export async function transformPageData(pageData: PageData) {
pageData.frontmatter.head ??= [];
+3 -10
View File
@@ -2,6 +2,7 @@
The Lucide docs website is built with Vitepress: https://vitepress.dev/
This is Markdown-based documentation powered by Vue.
For API endpoints, we use Nitro: https://nitro.unjs.io/ to create a custom API server.
## Development
@@ -11,10 +12,7 @@ pnpm install
```
```sh
# Start docs dev server
pnpm docs:dev
# Start api dev server
# Start dev server
pnpm dev
```
@@ -22,12 +20,7 @@ pnpm dev
```sh
# Build docs
pnpm docs:build
```
```sh
# Build api
pnpm build:api
pnpm build
```
## Components
+11
View File
@@ -0,0 +1,11 @@
import { defineHandler } from 'nitro/h3';
import iconMetaData from '~/.vitepress/data/iconMetaData';
export default defineHandler((event) => {
event.res.headers.set('Cache-Control', 'public, max-age=86400');
event.res.headers.set('Access-Control-Allow-Origin', '*');
return Object.fromEntries(
Object.entries(iconMetaData).map(([name, { categories }]) => [name, categories]),
);
});
@@ -1,8 +1,8 @@
import { eventHandler, setResponseHeader } from 'h3';
import iconNodes from '../../data/iconNodes/index.ts';
import iconMetaData from '../../data/iconMetaData';
import releaseMeta from '../../data/releaseMetaData.json';
import categories from '../../data/categoriesData.json';
import { defineHandler } from 'nitro/h3';
import iconNodes from '~/.vitepress/data/iconNodes/index.ts';
import iconMetaData from '~/.vitepress/data/iconMetaData';
import releaseMeta from '~/.vitepress/data/releaseMetaData.json';
import categories from '~/.vitepress/data/categoriesData.json';
const dataResponse = {
icons: Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
@@ -32,9 +32,9 @@ const dataResponse = {
categories,
};
export default eventHandler((event) => {
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
export default defineHandler((event) => {
event.res.headers.set('Cache-Control', 'public, max-age=86400');
event.res.headers.set('Access-Control-Allow-Origin', '*');
return dataResponse;
});
@@ -1,12 +1,12 @@
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
import { defineHandler } from 'nitro/h3';
import { renderToString, renderToStaticMarkup } from 'react-dom/server';
import React from 'react';
import SvgPreview from '../../lib/SvgPreview/index.tsx';
import iconNodes from '../../data/iconNodes';
import SvgPreview from '~/.vitepress/lib/SvgPreview/index.tsx';
import iconNodes from '~/.vitepress/data/iconNodes';
import createLucideIcon from 'lucide-react/src/createLucideIcon';
import Backdrop from '../../lib/SvgPreview/Backdrop.tsx';
import Backdrop from '~/.vitepress/lib/SvgPreview/Backdrop.tsx';
export default eventHandler((event) => {
export default defineHandler((event) => {
const { params } = event.context;
const pathData = params.data.split('/');
@@ -46,8 +46,8 @@ export default eventHandler((event) => {
);
}
defaultContentType(event, 'image/svg+xml');
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
event.res.headers.set('Content-Type', 'image/svg+xml');
event.res.headers.set('Cache-Control', 'public,max-age=31536000');
return renderToString(
<SvgPreview
@@ -1,11 +1,11 @@
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
import { defineHandler } from 'nitro/h3';
import { renderToString, renderToStaticMarkup } from 'react-dom/server';
import React from 'react';
import Diff from '../../../lib/SvgPreview/Diff.tsx';
import iconNodes from '../../../data/iconNodes';
import Diff from '~/.vitepress/lib/SvgPreview/Diff.tsx';
import iconNodes from '~/.vitepress/data/iconNodes';
import createLucideIcon from 'lucide-react/src/createLucideIcon';
export default eventHandler((event) => {
export default defineHandler((event) => {
const { params } = event.context;
const pathData = params.data.split('/');
@@ -34,8 +34,8 @@ export default eventHandler((event) => {
.replace(/<svg[^>]*>|<\/svg>/g, '')
: '';
defaultContentType(event, 'image/svg+xml');
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
event.res.headers.set('Content-Type', 'image/svg+xml');
event.res.headers.set('Cache-Control', 'public,max-age=31536000');
return renderToString(
<Diff
@@ -1,11 +1,11 @@
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
import iconNodes from '../../../data/iconNodes';
import { defineHandler } from 'nitro/h3';
import iconNodes from '~/.vitepress/data/iconNodes';
import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import createLucideIcon from 'lucide-react/src/createLucideIcon';
import sharp from 'sharp';
export default eventHandler(async (event) => {
export default defineHandler(async (event) => {
const { params = {} } = event.context;
const imageSize = 96;
const name = params.data.split('/').at(-3);
@@ -42,8 +42,8 @@ export default eventHandler(async (event) => {
const pngBuffer = await sharp(Buffer.from(svg)).png().toBuffer();
defaultContentType(event, 'image/svg+xml');
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
event.res.headers.set('Content-Type', 'image/svg+xml');
event.res.headers.set('Cache-Control', 'public,max-age=31536000');
return `
<svg xmlns="http://www.w3.org/2000/svg" width="${imageSize}" height="${prevSvg ? imageSize * 2 : imageSize}" viewBox="0 0 ${imageSize} ${prevSvg ? imageSize * 2 : imageSize}">
@@ -1,11 +1,11 @@
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
import { defineHandler } from 'nitro/h3';
import { renderToString } from 'react-dom/server';
import React from 'react';
import Icon from 'lucide-react/src/Icon';
import type { IconNode } from 'lucide-react/src/types';
import { parseSync } from 'svgson';
export default eventHandler((event) => {
export default defineHandler((event) => {
const { params } = event.context;
const [strokeWidth, svgData] = params.data.split('/');
@@ -16,8 +16,8 @@ export default eventHandler((event) => {
({ name, attributes }) => [name, attributes],
) as IconNode;
defaultContentType(event, 'image/svg+xml');
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
event.res.headers.set('Content-Type', 'image/svg+xml');
event.res.headers.set('Cache-Control', 'public,max-age=31536000');
return renderToString(
<Icon
@@ -1,9 +1,9 @@
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
import { defineHandler } from 'nitro/h3';
import { renderToString } from 'react-dom/server';
import React from 'react';
import Diff from '../../../lib/SvgPreview/Diff.tsx';
import Diff from '~/.vitepress/lib/SvgPreview/Diff.tsx';
export default eventHandler((event) => {
export default defineHandler((event) => {
const { params } = event.context;
const pathData = params.data.split('/');
@@ -41,8 +41,8 @@ export default eventHandler((event) => {
return '';
}
defaultContentType(event, 'image/svg+xml');
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
event.res.headers.set('Content-Type', 'image/svg+xml');
event.res.headers.set('Cache-Control', 'public,max-age=31536000');
return renderToString(
<Diff
@@ -1,13 +1,13 @@
import { eventHandler, getQuery, setResponseHeader } from 'h3';
import iconNodes from '../../data/iconNodes';
import { defineHandler, getQuery } from 'nitro/h3';
import iconNodes from '~/.vitepress/data/iconNodes';
export default eventHandler((event) => {
export default defineHandler((event) => {
const query = getQuery(event);
const withUniqueKeys = query.withUniqueKeys === 'true';
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
event.res.headers.set('Cache-Control', 'public, max-age=86400');
event.res.headers.set('Access-Control-Allow-Origin', '*');
if (withUniqueKeys) {
return iconNodes;
@@ -1,21 +1,19 @@
import { eventHandler, getQuery, setResponseHeader, createError } from 'h3';
import iconNodes from '../../data/iconNodes';
import { defineHandler, getQuery, HTTPError } from 'nitro/h3';
import iconNodes from '~/.vitepress/data/iconNodes';
import createLucideIcon from 'lucide-react/src/createLucideIcon';
import { renderToString } from 'react-dom/server';
import { createElement } from 'react';
export default eventHandler((event) => {
export default defineHandler((event) => {
const { params } = event.context;
const iconNode = iconNodes[params.iconName];
if (iconNode == null) {
const error = createError({
statusCode: 404,
throw new HTTPError({
status: 404,
message: `Icon "${params.iconName}" not found`,
});
return sendError(event, error);
}
const query = getQuery<Record<string, string | undefined>>(event);
@@ -39,9 +37,9 @@ export default eventHandler((event) => {
),
).toString('utf8');
defaultContentType(event, 'image/svg+xml');
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
event.res.headers.set('Content-Type', 'image/svg+xml');
event.res.headers.set('Cache-Control', 'public,max-age=31536000');
event.res.headers.set('Access-Control-Allow-Origin', '*');
return svg;
});
@@ -1,10 +1,10 @@
import { eventHandler, setResponseHeader } from 'h3';
import iconMetaData from '../../../data/lab/iconMetaData';
import iconPopularity from '../../../data/lab/iconPopularity';
import iconNodes from '../../../data/lab/iconNodes';
import { defineHandler } from 'nitro/h3';
import iconMetaData from '~/.vitepress/data/lab/iconMetaData';
import iconPopularity from '~/.vitepress/data/lab/iconPopularity';
import iconNodes from '~/.vitepress/data/lab/iconNodes';
export default eventHandler((event) => {
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
export default defineHandler((event) => {
event.res.headers.set('Access-Control-Allow-Origin', '*');
return Object.fromEntries(
Object.entries(iconMetaData).map(([iconName, iconData]) => {
+14
View File
@@ -0,0 +1,14 @@
import { defineHandler } from 'nitro/h3';
import iconMetaData from '~/.vitepress/data/lab/iconMetaData';
export default defineHandler((event) => {
event.res.headers.set('Cache-Control', 'public, max-age=86400');
event.res.headers.set('Access-Control-Allow-Origin', '*');
for (const iconDetail in iconMetaData) {
// Remove $schema from the response
delete iconMetaData[iconDetail]['$schema'];
}
return iconMetaData;
});
@@ -1,14 +1,14 @@
import { eventHandler, getQuery, setResponseHeader } from 'h3';
import iconNodes from '../../../data/lab/iconNodes';
import { defineHandler, getQuery } from 'nitro/h3';
import iconNodes from '~/.vitepress/data/lab/iconNodes';
type IconNodeWithKeys = [elementName: string, attrs: Record<string, string> & { key?: string }][];
export default eventHandler((event) => {
export default defineHandler((event) => {
const query = getQuery(event);
const withUniqueKeys = query.withUniqueKeys === 'true';
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
event.res.headers.set('Cache-Control', 'public, max-age=86400');
event.res.headers.set('Access-Control-Allow-Origin', '*');
if (withUniqueKeys) {
return iconNodes;
+3
View File
@@ -0,0 +1,3 @@
export default async function render() {
return '';
}
+9
View File
@@ -0,0 +1,9 @@
import { defineHandler } from 'nitro/h3';
import iconMetaData from '~/.vitepress/data/iconMetaData';
export default defineHandler((event) => {
event.res.headers.set('Cache-Control', 'public, max-age=86400');
event.res.headers.set('Access-Control-Allow-Origin', '*');
return Object.fromEntries(Object.entries(iconMetaData).map(([name, { tags }]) => [name, tags]));
});
@@ -3,7 +3,7 @@ description: Learn the best practices for writing use cases for Lucide icons.
---
<script setup>
import { batteryLow, creditCard, crop, handshake, heading2, microchip, search, squareLetterP, triangleAlert, wifiOff } from '~/.vitepress/data/iconNodes';
import { batteryLow, creditCard, crop, handshake, heading2, microchip, search, squareLetterP, triangleExclamationPoint, wifiOff } from '~/.vitepress/data/iconNodes';
import LucideIcon from '~/.vitepress/theme/components/base/LucideIcon.vue';
</script>
@@ -120,10 +120,10 @@ The period adds **unnecessary punctuation** and makes entries inconsistent.
Use plain text only. Formatting belongs in documentation, not metadata values.
:::: example
::: do <LucideIcon name="triangle-alert" :iconNode="triangleAlert" /> Warning users about a destructive action
::: do <LucideIcon name="triangle-exclamation-point" :iconNode="triangleExclamationPoint" /> Warning users about a destructive action
This works in search, generated pages, and other places that use metadata.
:::
::: dont <LucideIcon name="triangle-alert" :iconNode="triangleAlert" /> <span>**Warning** users about a destructive action ⚠️</span>
::: dont <LucideIcon name="triangle-exclamation-point" :iconNode="triangleExclamationPoint" /> <span>**Warning** users about a destructive action ⚠️</span>
Markdown and emoji can **show up in generated UI** and make metadata **harder to reuse**.
:::
::::
+1 -1
View File
@@ -31,7 +31,7 @@ You can then reference the registered icons by name:
```html [app.html]
<svg lucideIcon="square-check"></svg>
<svg lucideIcon="circle-alert"></svg>
<svg lucideIcon="circle-exclamation-point"></svg>
```
## How names are resolved
+2 -2
View File
@@ -1,6 +1,6 @@
<script setup>
import OverviewLink from '../../.vitepress/theme/components/base/OverviewLink.vue'
import OverviewLinkGrid from '../../.vitepress/theme/components/base/OverviewLinkGrid.vue'
import OverviewLink from '~/.vitepress/theme/components/base/OverviewLink.vue'
import OverviewLinkGrid from '~/.vitepress/theme/components/base/OverviewLinkGrid.vue'
</script>
# Getting started
+2 -2
View File
@@ -1,5 +1,5 @@
import { getAllCategoryFiles } from '../.vitepress/lib/categories';
import iconMetaData from '../.vitepress/data/iconMetaData';
import { getAllCategoryFiles } from '../.vitepress/lib/categories.ts';
import iconMetaData from '../.vitepress/data/iconMetaData.ts';
export default {
async load() {
+1 -1
View File
@@ -1,4 +1,4 @@
import createCodeExamples from '../.vitepress/lib/codeExamples/createCodeExamples';
import createCodeExamples from '../.vitepress/lib/codeExamples/createCodeExamples.ts';
export default {
async load() {
+1 -1
View File
@@ -1,4 +1,4 @@
import * as iconDetails from '../.vitepress/data/iconDetails';
import * as iconDetails from '../.vitepress/data/iconDetails/index.ts';
export default {
async load() {
+1 -1
View File
@@ -1,4 +1,4 @@
import createCodeExamples from '../../.vitepress/lib/codeExamples/createLabCodeExamples';
import createCodeExamples from '../../.vitepress/lib/codeExamples/createLabCodeExamples.ts';
export default {
async load() {
+4 -11
View File
@@ -1,15 +1,8 @@
export default defineNitroConfig({
import { defineConfig } from 'nitro';
export default defineConfig({
compatibilityDate: '2026-02-26',
preset: 'vercel',
srcDir: '.vitepress',
serverDir: './',
ignore: ['.vitepress/plugins'],
routeRules: {
'/api/**': { cors: false },
},
esbuild: {
options: {
jsxFactory: 'React.createElement',
jsxFragment: 'React.Fragment',
},
},
});
+8 -12
View File
@@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"docs:dev": "pnpm run /^prebuild:.*/ && vitepress dev",
"dev": "pnpm run /^prebuild:.*/ && vitepress dev",
"docs:build": "pnpm run /^prebuild:.*/ && vitepress build",
"docs:preview": "vitepress preview",
"build:docs": "NODE_OPTIONS=--max-old-space-size=8192 vitepress build",
@@ -21,9 +21,8 @@
"prebuild:writeLabIconNodes": "node ./scripts/writeLabIconNodes.mts",
"prebuild:writeLabIconMetaIndex": "node ./scripts/writeLabIconMetaIndex.mts",
"postbuild:vercelJson": "node ./scripts/writeVercelOutput.mts",
"dev": "nitropack dev",
"prebuild:api": "nitropack prepare",
"build:api": "NODE_OPTIONS=--max-old-space-size=8192 nitropack build",
"dev:api": "nitro dev",
"build:api": "NODE_OPTIONS=--max-old-space-size=8192 nitro build",
"build": "pnpm run /^prebuild:.*/ && pnpm run build:api && pnpm run build:docs && pnpm postbuild:vercelJson",
"preview": "node .output/server/index.mjs",
"typecheck": "vue-tsc --noEmit"
@@ -31,7 +30,6 @@
"author": "Eric Fennis",
"license": "ISC",
"devDependencies": {
"@docsearch/js": "3.8.2",
"@lucide/build-icons": "workspace:*",
"@lucide/helpers": "workspace:*",
"@lucide/shared": "workspace:*",
@@ -39,27 +37,25 @@
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@types/semver": "^7.8.0",
"h3": "^1.15.11",
"markdown-it": "^15.0.3",
"markdown-it-container": "^4.0.0",
"nitropack": "^2.13.4",
"nitro": "3.0.261103-rc",
"rollup-plugin-copy": "^3.5.0",
"semver": "^7.8.5",
"sharp": "^0.35.5",
"shiki": "^4.4.3",
"simple-git": "^4.0.1",
"shiki": "^4.5.0",
"simple-git": "^4.0.2",
"sitemap": "^9.0.1",
"svg-path-commander": "^2.3.3",
"svg-pathdata": "^9.0.0",
"svgson": "^5.3.1",
"typescript": "^6.0.3",
"vitepress": "^1.6.4",
"vitepress": "2.0.0-alpha.21",
"vitepress-plugin-group-icons": "^1.7.6",
"vitepress-plugin-llms": "^1.13.5",
"vue-tsc": "^3.3.11"
},
"dependencies": {
"@docsearch/css": "3.8.2",
"@floating-ui/vue": "^2.0.1",
"@headlessui/vue": "^1.7.23",
"@lucide/vue": "workspace:*",
@@ -73,7 +69,7 @@
"lodash-es": "^4.18.1",
"lucide": "workspace:*",
"lucide-react": "workspace:*",
"motion-v": "2.4.0",
"motion-v": "2.11.0",
"react": "19.3.1",
"react-dom": "19.3.1",
"sandpack-vue3": "3.1.12",
+2 -2
View File
@@ -35,7 +35,7 @@ const writeIconFiles = Object.entries(iconMetaData).map(async ([iconName, metaDa
import iconNode from '../iconNodes/${iconName}.node.json' with { type: 'json' };
import releaseData from '../releaseMetadata/${iconName}.json' with { type: 'json' };
import popularity from '../iconPopularity/${iconName}.json' with { type: 'json' };
import { IconMetaData, IconNode } from '~/.vitepress/theme/types';
import type { IconMetaData, IconNode } from '~/.vitepress/theme/types.ts';
const metaData: IconMetaData = ${JSON.stringify({ tags, categories, contributors, aliases, deprecated, deprecationReason, toBeRemovedInVersion })};
@@ -61,7 +61,7 @@ export default iconDetails;
await fs.promises.writeFile(location, contents, 'utf-8');
await fs.promises.appendFile(
indexFile,
`export { default as ${toCamelCase(iconName)} } from './${iconName}';\n`,
`export { default as ${toCamelCase(iconName)} } from './${iconName}.ts';\n`,
'utf-8',
);
});
+1 -1
View File
@@ -47,7 +47,7 @@ try {
await fs.promises.writeFile(
iconIndexFile,
`\
import type { IconNode } from '../../theme/types';
import type { IconNode } from '../../theme/types.ts';
${iconIndexFileImports.join('\n')}
${iconIndexFileNodes.join('\n')}
+7 -1
View File
@@ -1,6 +1,12 @@
{
"extends": "./.nitro/types/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": false,
"allowJs": true,
"resolveJsonModule": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react",
"jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment",
+62 -7
View File
@@ -12,8 +12,68 @@
"permanent": true
},
{
"source": "/docs/:slug(lucide-?.*)",
"destination": "/guide/:slug",
"source": "/docs/lucide-vue-next",
"destination": "/guide/vue",
"permanent": true
},
{
"source": "/guide/packages/lucide-vue-next",
"destination": "/guide/vue",
"permanent": true
},
{
"source": "/guide/vue-next",
"destination": "/guide/vue",
"permanent": true
},
{
"source": "/docs/lucide",
"destination": "/guide/lucide",
"permanent": true
},
{
"source": "/docs/lucide-:pkg",
"destination": "/guide/:pkg",
"permanent": true
},
{
"source": "/guide/lucide-:pkg",
"destination": "/guide/:pkg",
"permanent": true
},
{
"source": "/guide/packages/lucide-:pkg/",
"destination": "/guide/:pkg",
"permanent": true
},
{
"source": "/guide/advanced/accessibility",
"destination": "/how-to/accessibility",
"permanent": true
},
{
"source": "/guide/basics/:slug",
"destination": "/guide/react/basics/:slug",
"permanent": true
},
{
"source": "/guide/advanced/:slug",
"destination": "/guide/react/advanced/:slug",
"permanent": true
},
{
"source": "/guide/design/figma-guide",
"destination": "/contribute/icons/figma-guide",
"permanent": true
},
{
"source": "/guide/design/:slug(icon-design-guide|icon-properties)",
"destination": "/contribute/icons/design-principles",
"permanent": true
},
{
"source": "/icons/lab/:name(carton-off|briefcase-plus)",
"destination": "/icons/:name",
"permanent": true
},
{
@@ -47,11 +107,6 @@
"destination": "/how-to/accessibility",
"permanent": true
},
{
"source": "/guide/advanced/accessibility",
"destination": "/how-to/accessibility",
"permanent": true
},
{
"source": "/guide/advanced",
"destination": "/guide/react/advanced",
+3 -1
View File
@@ -20,6 +20,8 @@
"investigate",
"magnifying glass",
"lens",
"zoom"
"zoom",
"inspection",
"visual search"
]
}
@@ -15,5 +15,10 @@
"categories": [
"account",
"social"
],
"aliases": [
{
"name": "badge-alert"
}
]
}

Before

Width:  |  Height:  |  Size: 471 B

After

Width:  |  Height:  |  Size: 471 B

@@ -12,5 +12,10 @@
],
"categories": [
"$extends:battery"
],
"aliases": [
{
"name": "battery-warning"
}
]
}

Before

Width:  |  Height:  |  Size: 393 B

After

Width:  |  Height:  |  Size: 393 B

@@ -15,5 +15,10 @@
],
"categories": [
"$extends:book"
],
"aliases": [
{
"name": "book-alert"
}
]
}

Before

Width:  |  Height:  |  Size: 359 B

After

Width:  |  Height:  |  Size: 359 B

@@ -16,6 +16,9 @@
"name": "alert-circle",
"deprecationReason": "alias.name",
"deprecated": true
},
{
"name": "circle-alert"
}
]
}

Before

Width:  |  Height:  |  Size: 332 B

After

Width:  |  Height:  |  Size: 332 B

+23
View File
@@ -0,0 +1,23 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"n2duc"
],
"use-cases": [
"Indicating a submission or payment that is still being verified",
"Showing a deployment, upload, or backup that is finishing up",
"Marking a task as partially completed or awaiting final approval",
"Displaying a step in a progress tracker that is in progress"
],
"tags": [
"$group:check",
"pending",
"progress",
"processing",
"verifying",
"loading"
],
"categories": [
"notifications"
]
}
+18
View File
@@ -0,0 +1,18 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 2a10 10 0 017.38 16.75" />
<path d="m16 9-5.5 5.5L8 12" />
<path d="M2.5 8.875a10 10 0 00-.5 3" />
<path d="M2.83 16a10 10 0 002.43 3.4" />
<path d="M4.636 5.235a10 10 0 01.891-.857" />
<path d="M8.644 21.42a10 10 0 007.631-.38" />
</svg>

After

Width:  |  Height:  |  Size: 465 B

@@ -13,5 +13,10 @@
],
"categories": [
"$extends:clock"
],
"aliases": [
{
"name": "clock-alert"
}
]
}

Before

Width:  |  Height:  |  Size: 332 B

After

Width:  |  Height:  |  Size: 332 B

@@ -19,5 +19,10 @@
"development",
"notifications",
"weather"
],
"aliases": [
{
"name": "cloud-alert"
}
]
}

Before

Width:  |  Height:  |  Size: 331 B

After

Width:  |  Height:  |  Size: 331 B

+27
View File
@@ -0,0 +1,27 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"$extends:grid-3x3",
"gridalchemy"
],
"use-cases": [
"Hiding grid lines on a canvas or artboard",
"Indicating that snap to grid is turned off",
"Hiding the cell grid in spreadsheets"
],
"tags": [
"$extends:grid-3x3",
"$group:off",
"guides",
"snap",
"canvas",
"spreadsheet",
"cells",
"data table",
"diagram",
"overlay"
],
"categories": [
"$extends:grid-3x3"
]
}
+21
View File
@@ -0,0 +1,21 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M15 9.344V3" />
<path d="m2 2 20 20" />
<path d="M20.414 20.414A2 2 0 0119 21H5a2 2 0 01-2-2V5a2 2 0 01.586-1.414" />
<path d="M20.656 15H21" />
<path d="M21 9h-6.344" />
<path d="M3 15h12v6" />
<path d="M3 9h6v12" />
<path d="M8.656 3H19a2 2 0 012 2v10.343" />
<path d="M9 3v.344" />
</svg>

After

Width:  |  Height:  |  Size: 520 B

@@ -13,5 +13,13 @@
"categories": [
"mail",
"notifications"
],
"aliases": [
{
"name": "mail-warning"
},
{
"name": "mail-alert"
}
]
}

Before

Width:  |  Height:  |  Size: 393 B

After

Width:  |  Height:  |  Size: 393 B

+30
View File
@@ -0,0 +1,30 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"Prsenapati09",
"rrod497"
],
"use-cases": [
"Representing tropical fruit in food and grocery apps",
"Indicating produce sections in shopping interfaces",
"Labeling tropical fruit filters in recipe and nutrition apps",
"Highlighting seasonal fruit in marketplaces"
],
"tags": [
"fruit",
"food",
"tropical",
"summer",
"exotic",
"healthy",
"ripe",
"yellow",
"juice",
"smoothie",
"snack",
"produce"
],
"categories": [
"food-beverage"
]
}
+14
View File
@@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m17 6 3-3" />
<path d="M7 22a4 4 0 01-2.62-7.023A2 2 0 005 13.383V10a8 8 0 1116 0A12 12 0 019 22z" />
</svg>

After

Width:  |  Height:  |  Size: 323 B

@@ -17,5 +17,13 @@
"categories": [
"$extends:message-circle",
"notifications"
],
"aliases": [
{
"name": "message-circle-warning"
},
{
"name": "message-circle-alert"
}
]
}

Before

Width:  |  Height:  |  Size: 396 B

After

Width:  |  Height:  |  Size: 396 B

@@ -16,5 +16,13 @@
"categories": [
"$extends:message-square",
"notifications"
],
"aliases": [
{
"name": "message-square-warning"
},
{
"name": "message-square-alert"
}
]
}

Before

Width:  |  Height:  |  Size: 388 B

After

Width:  |  Height:  |  Size: 388 B

+38
View File
@@ -0,0 +1,38 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"jamiemlaw",
"jguddas"
],
"use-cases": [
"Picking a transport type in journey planning or racing apps",
"Highlighting a delivery driver on a map when they ride a motorbike"
],
"tags": [
"scooter",
"two-wheeler",
"commute",
"urban",
"city",
"courier",
"motorbike",
"moto",
"motorcycle",
"transport",
"vehicle",
"drive",
"ride",
"race",
"racing",
"trip",
"journey",
"delivery",
"off-road",
"dirt bike",
"trail",
"enduro"
],
"categories": [
"transportation"
]
}
+17
View File
@@ -0,0 +1,17 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m18.16 14.12-2.927-8.447A1 1 0 0 0 14.288 5H13" />
<path d="M19 10a7 7 0 0 0-6.928 6" />
<path d="M2 10h5.5a3 3 0 0 1 2.747 1.795l1.669 3.803A1 1 0 0 1 11.01 17H8" />
<circle cx="19" cy="17" r="3" />
<circle cx="5" cy="17" r="3" />
</svg>

After

Width:  |  Height:  |  Size: 459 B

@@ -18,6 +18,9 @@
"name": "alert-octagon",
"deprecationReason": "alias.name",
"deprecated": true
},
{
"name": "octagon-alert"
}
]
}

Before

Width:  |  Height:  |  Size: 510 B

After

Width:  |  Height:  |  Size: 510 B

+32
View File
@@ -0,0 +1,32 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"ilyavred",
"jamiemlaw"
],
"use-cases": [
"Setting the opacity of layers or objects in graphic editors",
"Adjusting the opacity of fills, strokes, or other object attributes",
"Adjusting the alpha channel in color pickers",
"Indicating that a layer or element is semi-transparent"
],
"tags": [
"opacity",
"opaque",
"transparency",
"visibility",
"fade",
"lens",
"glass",
"blur",
"filter",
"clear",
"mask",
"cover",
"gradient"
],
"categories": [
"design",
"photography"
]
}
+25
View File
@@ -0,0 +1,25 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 3h.01" />
<path d="M12 9h.01" />
<path d="M15 6h.01" />
<path d="M18 3h.01" />
<path d="M21 6v13a2 2 0 0 1-2 2H6" />
<path d="M3 12h.01" />
<path d="M3 18h.01" />
<path d="M3 6h.01" />
<path d="M6 15h.01" />
<path d="M6 3h.01" />
<path d="M6 9h.01" />
<path d="M9 12h.01" />
<path d="M9 6h.01" />
</svg>

After

Width:  |  Height:  |  Size: 544 B

+19
View File
@@ -0,0 +1,19 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"$extends:package"
],
"use-cases": [
"Editing recipient addresses or package contents in shipping systems",
"Modifying customer orders before shipment",
"Updating product listings in inventory or e-commerce platforms",
"Editing software package manifests or metadata"
],
"tags": [
"$extends:package",
"$group:pen"
],
"categories": [
"$extends:package"
]
}
+17
View File
@@ -0,0 +1,17 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 14.34V12l8.71-5" />
<path d="M16.511 21.141a2 2 0 0 0 .854-.506l4.013-4.009a1 1 0 1 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .599.626z" />
<path d="M21 9.114V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l5.087 2.907" />
<path d="M3.29 7 12 12" />
<path d="m7.5 4.27 9 5.15" />
</svg>

After

Width:  |  Height:  |  Size: 564 B

+24
View File
@@ -0,0 +1,24 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"$extends:package"
],
"use-cases": [
"Marking an unknown or unrecognized package in a package registry",
"Indicating a dependency that is still being resolved in a package manager",
"Indicating a lost or missing delivery package",
"Showing a shipment with an unknown delivery status"
],
"tags": [
"$extends:package",
"$group:question-mark",
"unresolved",
"placeholder",
"pending",
"lost",
"dependency"
],
"categories": [
"$extends:package"
]
}
+18
View File
@@ -0,0 +1,18 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 22V12l8.71-5" />
<path d="M17 15.27c.2-.4.5-.8.9-1a2.1 2.1 0 0 1 2.6.4c.3.4.5.8.5 1.3 0 1.3-2 2-2 2" />
<path d="M19 21.99h.01" />
<path d="M21 10.342V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l2.33-1.332" />
<path d="M3.29 7 12 12" />
<path d="m7.5 4.27 9 5.15" />
</svg>

After

Width:  |  Height:  |  Size: 550 B

+8 -1
View File
@@ -21,7 +21,14 @@
"packed",
"archive",
"zip",
"module"
"module",
"shipping",
"recipient",
"inventory",
"logistics",
"tracking",
"order",
"product"
],
"categories": [
"files",
@@ -15,5 +15,10 @@
],
"categories": [
"$extends:search"
],
"aliases": [
{
"name": "search-alert"
}
]
}

Before

Width:  |  Height:  |  Size: 322 B

After

Width:  |  Height:  |  Size: 322 B

@@ -59,5 +59,10 @@
"development",
"notifications",
"gaming"
],
"aliases": [
{
"name": "shield-alert"
}
]
}

Before

Width:  |  Height:  |  Size: 435 B

After

Width:  |  Height:  |  Size: 435 B

+20
View File
@@ -0,0 +1,20 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"$extends:square-dashed",
"domarku"
],
"use-cases": [
"Searching within a selected region",
"Finding content inside a placeholder area",
"Scanning a marquee selection",
"Zooming a selected region"
],
"tags": [
"$extends:square-dashed",
"$group:search"
],
"categories": [
"$extends:square-dashed"
]
}
+23
View File
@@ -0,0 +1,23 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M14 3h1" />
<path d="M19 3a2 2 0 012 2" />
<path d="m21 21-1.875-1.875" />
<path d="M21 9v1" />
<path d="M3 14v1" />
<path d="M3 9v1" />
<path d="M5 21a2 2 0 01-2-2" />
<path d="M5 3a2 2 0 00-2 2" />
<path d="M9 21h1" />
<path d="M9 3h1" />
<circle cx="17" cy="17" r="3" />
</svg>

After

Width:  |  Height:  |  Size: 513 B

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