Compare commits

...
Author SHA1 Message Date
Azuzulagithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Karsa
5a92b9ba26 feat(icons): added printer-3d icon (#4414)
* Added icons/printer3d.svg

* Added icons/printer3d.json

* Update icons/printer3d.json

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

* Rename printer3d.svg to printer-3d.svg

* Rename printer3d.json to printer-3d.json

* Modify SVG paths for printer-3D icon

* Update icons/printer-3d.svg

* Update printer-3d.json

* Update printer-3d.json

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Karsa <contact@karsa.org>
2026-09-30 00:08:38 +02:00
Tanvir MahinandKarsa 66d8f9fc39 feat(icons): added bangladeshi-taka icon (#4869)
* feat(icons): added `bangladeshi-taka` icon

* chore(metadata): updated `bangladeshi-taka` metadata

* chore(metadata): updated use cases and tags

* Apply suggestion from @karsa-mistmere

---------

Co-authored-by: Karsa <contact@karsa.org>
2026-09-24 15:16:39 +02:00
e042fec3b5 fix(packages): declare @types/react as an optional peer dependency (#4892)
`lucide-react` and `lucide-react-native` both ship `.d.ts` files that
import React types (`ForwardRefExoticComponent`, `SVGProps`,
`RefAttributes`, `ReactNode`), but neither package declares
`@types/react`. Under a hoisted `node_modules` this resolves by
accident, so the gap is invisible in normal testing.

It does not resolve under a strict layout — most notably pnpm's global
virtual store (`enableGlobalVirtualStore`), where each package is linked
from a store path outside the consuming repo. TypeScript resolves a
package's imports relative to where that package physically lives and
ignores `NODE_PATH`, so `react` resolves to the untyped
`react/index.js`. Every icon's props silently degrade to `any`, and
consumers hit implicit-any errors on their own callbacks under
`noImplicitAny`. `skipLibCheck: true` hides the errors inside the
`.d.ts` itself, which is why this survives most typechecks.

Declaring the typings package as a peer dependency (rather than a hard
dependency) lets it dedupe against whatever `@types/react` the consumer
already has. `peerDependenciesMeta` marks it optional so JS-only
consumers get no unmet-peer warning.

Verified by packing each package and installing the tarball into a
scratch consumer with `pnpm --virtual-store-dir` pointing outside the
consumer, then typechecking with `skipLibCheck: false` and a
`@ts-expect-error` probe on a nonexistent icon prop — a positive type
assertion keeps passing when types widen to `any`, so it would not
catch a regression. Before the change the probe reports
`TS2578: Unused '@ts-expect-error' directive` plus `TS7016` inside the
shipped `.d.ts`; after it, `tsc` is clean and `--traceResolution`
shows `react` resolving to `@types/react/index.d.ts` instead of
`react/index.js`. Existing peer ranges are unchanged and the lockfile
is unaffected.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-09-24 15:11:17 +02:00
Karsa c870b3c54a feat(icons): added letters icon (#4779)
* Added icons/letters.svg

* Added icons/letters.json

* Updated icons/letters.json
2026-09-24 13:40:20 +02:00
Jiacheng Jianggithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Karsa
f53e5bfff0 feat(icons): add house-cog icon (#4904)
* feat(icons): add house-cog icon

* Apply suggestion from @github-actions[bot]

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

* Apply suggestion from @github-actions[bot]

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

* Apply suggestion from @github-actions[bot]

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

* fix(icons): address house-cog review feedback

* Update icons/house-cog.json

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

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Karsa <contact@karsa.org>
2026-09-24 07:42:30 +02:00
Eric Fennisandcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> f06ac67e33 chore(typchecking): More typecheck jobs for all packages (#4885)
* Add typecheck jobs

* update lockfile

* Fix types

* Fix lucide solid types

* Fix type lucide-react-native

* fix: align react-native svg mock children typing

* fix: format build font spec

* fix: restore shared workflow and RN class context

* fix: restore lucide-react build typecheck

* fix: keep react build generating icons

* fix: preserve builder color prop types

* test: cover RN provider class merge

* Revert 711255b

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-09-22 13:49:09 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> b0b1a4f8ab chore(deps): bump the vue-deps group with 3 updates (#4893)
Bumps the vue-deps group with 3 updates: [@vitejs/plugin-vue](https://github.com/vitejs/vite-plugin-vue/tree/HEAD/packages/plugin-vue), [@vue/test-utils](https://github.com/vuejs/test-utils) and [vue](https://github.com/vuejs/core).


Updates `@vitejs/plugin-vue` from 6.0.8 to 6.0.9
- [Release notes](https://github.com/vitejs/vite-plugin-vue/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-vue/blob/main/packages/plugin-vue/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-vue/commits/plugin-vue@6.0.9/packages/plugin-vue)

Updates `@vue/test-utils` from 2.5.0 to 2.5.1
- [Release notes](https://github.com/vuejs/test-utils/releases)
- [Commits](https://github.com/vuejs/test-utils/compare/v2.5.0...v2.5.1)

Updates `vue` from 3.5.42 to 3.5.43
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/compare/v3.5.42...v3.5.43)

---
updated-dependencies:
- dependency-name: "@vitejs/plugin-vue"
  dependency-version: 6.0.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: vue-deps
- dependency-name: "@vue/test-utils"
  dependency-version: 2.5.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: vue-deps
- dependency-name: vue
  dependency-version: 3.5.43
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: vue-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-22 07:54:14 +02:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
3a37bfa5cd chore(deps-dev): bump the angular-deps group across 1 directory with 14 updates (#4895)
* chore(deps-dev): bump the angular-deps group across 1 directory with 14 updates

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

| Package | From | To |
| --- | --- | --- |
| [@angular-eslint/builder](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/builder) | `22.2.0` | `22.5.0` |
| [@angular-eslint/schematics](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/schematics) | `22.2.0` | `22.5.0` |
| [@angular/build](https://github.com/angular/angular-cli) | `22.1.7` | `22.1.8` |
| [@angular/cli](https://github.com/angular/angular-cli) | `22.1.7` | `22.1.8` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `22.1.5` | `22.1.7` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `22.1.5` | `22.1.7` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `22.1.5` | `22.1.7` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `22.1.5` | `22.1.7` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `22.1.5` | `22.1.7` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `22.1.5` | `22.1.7` |
| [@angular/platform-server](https://github.com/angular/angular/tree/HEAD/packages/platform-server) | `22.1.5` | `22.1.7` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `22.1.5` | `22.1.7` |
| [@vitest/browser-playwright](https://github.com/vitest-dev/vitest/tree/HEAD/packages/browser-playwright) | `4.1.10` | `5.0.1` |
| [angular-eslint](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/angular-eslint) | `22.2.0` | `22.5.0` |



Updates `@angular-eslint/builder` from 22.2.0 to 22.5.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.5.0/packages/builder)

Updates `@angular-eslint/schematics` from 22.2.0 to 22.5.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.5.0/packages/schematics)

Updates `@angular/build` from 22.1.7 to 22.1.8
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v22.1.7...v22.1.8)

Updates `@angular/cli` from 22.1.7 to 22.1.8
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v22.1.7...v22.1.8)

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

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

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

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

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

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

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

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

Updates `@vitest/browser-playwright` from 4.1.10 to 5.0.1
- [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.1/packages/browser-playwright)

Updates `angular-eslint` from 22.2.0 to 22.5.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.5.0/packages/angular-eslint)

---
updated-dependencies:
- dependency-name: "@angular-eslint/builder"
  dependency-version: 22.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: angular-deps
- dependency-name: "@angular-eslint/schematics"
  dependency-version: 22.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: angular-deps
- dependency-name: "@angular/build"
  dependency-version: 22.1.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/cli"
  dependency-version: 22.1.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/common"
  dependency-version: 22.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/compiler"
  dependency-version: 22.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/compiler-cli"
  dependency-version: 22.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/core"
  dependency-version: 22.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/forms"
  dependency-version: 22.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/platform-browser"
  dependency-version: 22.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/platform-server"
  dependency-version: 22.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/router"
  dependency-version: 22.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@vitest/browser-playwright"
  dependency-version: 5.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: angular-deps
- dependency-name: angular-eslint
  dependency-version: 22.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: angular-deps
...

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

* fix: keep vitest browser provider on v4

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-09-22 07:53:16 +02:00
Karsa 16fb55f52c fix(icons): changed mail-pen (#4899)
adjusted the off grid pen element
2026-09-22 07:52:34 +02:00
Jakob GuddasandEric Fennis 951813ce76 fix(icons): changed map-pinned icon (#4880)
* Updated icons/map-pinned.svg

* Updated icons/map-pinned.json

* Updated icons/map-pinned.svg

* Updated icons/map-pinned.json

* Updated icons/map-pinned.svg

* Updated icons/map-pinned.json

* Update map-pinned.json

* Update map-pinned.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-09-19 12:26:19 +02:00
ba6751ac45 feat(site): Better icon detail page and add unreleased flag (#4877)
* setup icon popularity call

* Adjust sort button

* Adjust sorting

* Adds aliases

* Format code

* Potential fix for pull request finding

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

* Potential fix for pull request finding

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

* Potential fix for pull request finding

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

* Potential fix for pull request finding

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

* Potential fix for pull request finding

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

* Potential fix for pull request finding

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

* Add check for env variable

* Potential fix for pull request finding

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

* Potential fix for pull request finding

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

* Improve styling on mobile

* Better icon detail page

* Update detail page

* feat: make edit button more visible

* Fix types

* Add unreleased version tag

* Adjust icon detail

* Added edit button

* format code

* Replace pencil sparkles

* Revert changes

* Fix types

* Add docs checks

* Fix types

* Fix awaiting release

* revert icons changes

* Format code

* Fix type issues

* Update CarbonAdOverlay to not display

Disable CarbonAdOverlay by setting drawerOpen to false.

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

* Fix types

* Fix condition for showing awaiting release icons

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

* Format code

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jakob Guddas <github@jguddas.de>
2026-09-18 14:22:47 +02:00
99c1037550 test(packages/shared): cover buildLucideIconForReact (#4872)
buildLucideIconForReact is the entry point both lucide-react and
lucide-react-native use to build an icon, and its whole job is renaming
the hyphenated SVG attributes to the spellings React expects. Nothing
tested that rename directly: the other build helpers have specs in
packages/icons/tests, and this one had none anywhere in the repo.

The spec pins the five renames it applies, checks that attributes React
already accepts are left alone, that aria-hidden stays hyphenated, that
vector-effect is renamed on child nodes for a non-scaling stroke, and
that a caller's own attributeNames are merged without displacing the
React spellings.

Verified that the spec is not vacuous: removing the class -> className
entry from the rename map fails two of the five tests.

Co-authored-by: Vugar Bakhishov <vugarbbakhishov-hub@users.noreply.github.com>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-09-18 14:08:40 +02:00
Eric Fennis e32a4a2c10 Fix type lucide-vue (#4883) 2026-09-18 12:29:14 +02:00
Oleg Bezrukavnikov 5d9cc1580a fix(packages/react-native): pass testID to the rendered Svg element (#4881)
The Icon component destructured `testID` out of its props and re-emitted it as
`data-testid`, which is a web attribute. react-native-svg only forwards `testID`
to the native view (see extractProps) and drops `data-testid`, so
`<Icon testID="my-icon" />` rendered an element that no React Native test query
or UI automation tool can find. `LucideProps` extends `SvgProps`, so the prop
type checks and the failure is silent.

v0 spread `testID` straight onto `Svg`, so this is a regression for React Native
consumers. An explicitly passed `data-testid` still reaches the element through
the remaining props, so nothing is lost for react-native-web users.

The react-native-svg test mock now maps `testID` onto `data-testid`, so the
DOM based test suite identifies elements the way React Native does. Every
existing `testID` test fails against the previous behaviour.
2026-09-18 11:57:55 +02:00
Nathan De PachtereandEric Fennis d9f72b2525 feat(icons): added line-dot-bottom-vertical icon (#3857)
* Added icons/line-dot-bottom-vertical.svg

* Added icons/line-dot-bottom-vertical.json

* Update line-dot-bottom-vertical.json

* Format code

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-09-17 16:30:55 +02:00
Nathan De Pachteregithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot Autofix powered by AIEric Fennis
0d0408c43b feat(icons): added line-dot-top-vertical icon (#3856)
* Added icons/line-dot-top-vertical.svg

* Added icons/line-dot-top-vertical.json

* Update icons/line-dot-top-vertical.json

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

* Remove unused tags from line-dot-top-vertical.json

Removed unused tags from the line-dot-top-vertical icon.

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

* Update line-dot-top-vertical.json

* format code

---------

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: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-17 16:30:01 +02:00
Daniel BayleyandEric Fennis 55d30bda04 feat(icons): tweak card-sim chip (#3649)
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-09-17 15:21:22 +02:00
Liang XuandLiang Xu f90f10a24a feat(packages): export __iconNode data across framework packages (#4761)
Fixes #4644

Signed-off-by: Liang Xu <lx3133584@users.noreply.github.com>
Co-authored-by: Liang Xu <lx3133584@users.noreply.github.com>
2026-09-17 14:58:48 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> f3e36fb0d7 chore(deps-dev): bump react-native (#4673)
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.76.9 to 0.87.1
- [Release notes](https://github.com/react/react-native/releases)
- [Changelog](https://github.com/react/react-native/blob/main/CHANGELOG-0.7x.md)
- [Commits](https://github.com/react/react-native/commits/v0.87.1/packages/react-native)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-17 14:56:09 +02:00
KarsaandEric Fennis 4faa47c0b0 fix(packages/svelte,solid): fix shared type imports in Solid and Svelte (#4846)
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-09-17 14:30:42 +02:00
Nathan De Pachteregithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot Autofix powered by AIEric Fennis
e593a44ad2 feat(icons): added line-dot-left-horizontal icon (#3855)
* Added icons/line-dot-left-horizontal.svg

* Added icons/line-dot-left-horizontal.json

* Update icons/line-dot-left-horizontal.json

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

* Add use cases to line-dot-left-horizontal.json

Added use cases for line-dot-left-horizontal icon.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.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: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-17 13:41:06 +02:00
dba8fb7932 feat(icons): added square-sparkles icon (#3610)
* Added icons/square-sparkles.svg

* Added icons/square-sparkles.json

* apply design from @karsa-mistmere

* Add use-cases field to square-sparkles.json

* Add use cases for square sparkles icon

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

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-17 13:40:38 +02:00
Tylergithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Claude Sonnet 5Eric Fennis
a87cecebe3 feat(icons): added briefcase-plus icon (#4757)
* feat(icons): added `briefcase-plus` icon

* Update icons/briefcase-plus.json

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

* Update icons/briefcase-plus.json

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

* Update icons/briefcase-plus.json

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

* chore(lab): remove `briefcase-plus` from lab

Icon has been promoted to icons/, no longer needed in lab.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Updated briefcase-plus icon

* Update contributors in briefcase-plus.json

Added 'jguddas' to contributors list in briefcase-plus.json

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-09-17 09:25:14 +02:00
psj.devgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot Autofix powered by AIPeter Jamrozinski
3b9ea6d087 feat(icons): added square-dashed-plus (#4849)
* feat(icons): added square-dashed-plus

* Update icons/square-dashed-plus.json

Suggested via PR AI

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

* Update icons/square-dashed-plus.json

Suggested by PR AI

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

* Update icons/square-dashed-plus.json

Suggestion made by PR AI

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

* Revise use cases for square dashed plus icon

AI PR Suggestion

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

---------

Co-authored-by: Peter Jamrozinski <peter.jamrozinski@johnsonfit.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-17 09:21:20 +02:00
Jakob Guddas 076b52527f fix(icons): changed broccoli icon (#4871)
* Updated icons/broccoli.svg

* Updated icons/broccoli.json
2026-09-16 10:18:44 +02:00
Karsa 58ef6830c6 fix(icons): changed calendar-chevrons-right (#4865) 2026-09-14 21:04:50 +02:00
HPRILLER f032bed22e fix(icons): remove clipped path from save-off (#4848) 2026-09-14 19:58:53 +02:00
conanClaude Opus 5Copilot Autofix powered by AIgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Eric FennisKarsa
266fd1c717 feat(icons): add rotate-cw-clock icon (#3979)
* Added icons/update.svg

* Added icons/update.json

* Updated icons/update.json

* chore(icons): rename update to update-clockwise

* refactor(icons): rename `update-clockwise` to `rotate-cw-clock`

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

* Add use cases for rotate-cw-clock icon

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

* Update icons/rotate-cw-clock.json

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

* Refine use-case descriptions in rotate-cw-clock.json

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

* Potential fix for pull request finding

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

* Update rotate-cw-clock.json

* Update rotate-cw-clock.svg

* Update rotate-cw-clock.json

* Update rotate-cw-clock.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Karsa <contact@karsa.org>
2026-09-14 11:55:42 +02:00
Ethan Hazelgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Eric FennisKarsa
357a50175e feat(icons): added square-dashed-x icon (#4535)
* feat(icons): added square-dashed-x icon

* Update icons/square-dashed-x.json

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

* fix(icons): split square-dashed-x icon into two designs

* Update square-dashed-x.svg

* Update square-dashed-x-corner.svg

* Update square-dashed-x.json

* Update square-dashed-x-corner.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Karsa <contact@karsa.org>
2026-09-14 11:49:49 +02:00
a50c54cfff feat(icons): Added cupcake icon (#3000)
* Added svg and json for cupcake icon

* Update cupcake.svg

* Update cupcake.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Karsa <contact@karsa.org>
2026-09-14 11:45:48 +02:00
Alec Rust bd95db1af8 feat(icons): added tube-lotion icon (#4029)
* Added icons/sunscreen.svg

* Added icons/sunscreen.json

* fix(icons): apply reviewed tube-lotion design and metadata

* fix(icons): correct tube-lotion contributor attribution
2026-09-14 11:39:55 +02:00
Saraj Dhakalgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot Autofix powered by AIEric Fennis
6b474e16d4 feat(icons): added 'nepali-rupee' icon (#4608)
* Add Nepali Rupee icon

* Apply suggestions from code review

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

* refactor: center nepali rupee icon and use perfect circular arc

* Updated with horizontally centered

* Update use cases for Nepali rupee

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@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>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-14 11:36:57 +02:00
Karsa 566032a95d feat(icons): added door-closed-package icon (#4814)
* Added icons/door-closed-package.svg

* Added icons/door-closed-package.json
2026-09-14 11:31:33 +02:00
Karsa 042bf77119 feat(icons): delegated faucet icon from lab (#4764) 2026-09-14 11:30:28 +02:00
Ubaid UllahandKarsa 5b8975c9a3 feat(icons): add lambda icon (#4017)
* feat(icons): added lambda icon

* fix(icons/lambda): fix linting issues

* feat(icons/lambda): clean up metadata

---------

Co-authored-by: Karsa <contact@karsa.org>
2026-09-14 11:17:11 +02:00
Karsa 7eb8afe2f8 feat(icons): added monitor-pc icon (#4791)
* Added icons/monitor-pc.svg

* Added icons/monitor-pc.json
2026-09-14 11:15:28 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 08b2fd2455 chore(deps-dev): bump astro from 7.3.0 to 7.3.2 in the astro-deps group (#4860)
Bumps the astro-deps group with 1 update: [astro](https://github.com/withastro/astro/tree/HEAD/packages/astro).


Updates `astro` from 7.3.0 to 7.3.2
- [Release notes](https://github.com/withastro/astro/releases)
- [Changelog](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md)
- [Commits](https://github.com/withastro/astro/commits/astro@7.3.2/packages/astro)

---
updated-dependencies:
- dependency-name: astro
  dependency-version: 7.3.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: astro-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-14 08:35:43 +02:00
a79b2d131d feat(icons): Added new icons: clef-g, clef-f and clef-c (#4083)
* Added new icons: clef-g, clef-f and clef-c

* rename and design updates from feedback

* fix typo in filename

* Update clef-bass.svg

* Update clef-treble.json

* Update clef-bass.json

* Update clef-alto.json

---------

Co-authored-by: Karsa <contact@karsa.org>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-09-11 13:03:10 +02:00
Dixgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot Autofix powered by AIEric Fennis
b998e2892b feat(icons): added globe-code icon (#3722)
* Added icons/world-wide-web.svg

* Added icons/world-wide-web.json

* Update icons/world-wide-web.json

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

* Rename files

* Add 'use-cases' field to globe-code.json

* Add use cases for globe code icon

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

* Fix JSON formatting in globe-code.json

* Add 'development' category to globe-code.json

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

* Update SVG path for globe code icon

---------

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: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-11 12:43:00 +02:00
Eric FennisandCopilot Autofix powered by AI 5da3ee357d ci(ci.yml): Add dispatch post release (#4847)
* Add dispatch post release

* Refine post-release automation conditions

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-09-11 12:42:26 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 6bbe5ddb07 chore(deps-dev): bump vitest in /integrations/lucide-react/vite (#4844)
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 4.1.10 to 4.1.11.
- [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/v4.1.11/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 4.1.11
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-11 09:26:37 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 4a26fa58e0 chore(deps-dev): bump vitest from 4.1.10 to 4.1.11 (#4845)
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 4.1.10 to 4.1.11.
- [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/v4.1.11/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 4.1.11
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-11 09:26:09 +02:00
Karsa 5d592a96aa fix(packages/vue): fix generated icon declaration types for @lucide/vue (#4841) 2026-09-10 22:35:01 +02:00
195 changed files with 3845 additions and 2333 deletions
+29
View File
@@ -64,6 +64,7 @@ jobs:
contents: write # Required to create the release and tag
outputs:
VERSION: ${{ steps.new-version.outputs.NEW_VERSION }}
RELEASE_URL: ${{ steps.create-release.outputs.url }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
@@ -102,6 +103,7 @@ jobs:
echo '${{ steps.change-log.outputs.CHANGE_LOG }}'
- name: Create Release
id: create-release
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
with:
tag_name: ${{ steps.new-version.outputs.NEW_VERSION }}
@@ -129,3 +131,30 @@ jobs:
with:
version: ${{ inputs.version }}
tag: ${{ inputs.tag }}
post-release:
name: Trigger post-release automation
if: >-
always() &&
github.repository == 'lucide-icons/lucide' &&
(needs.start-release.result == 'success' || needs.dispatch-release.result == 'success')
needs: [create-release, start-release, dispatch-release]
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Dispatch post_release event
env:
GH_TOKEN: ${{ secrets.POST_RELEASE_TOKEN }}
RELEASE_TAG: ${{ needs.create-release.outputs.VERSION || inputs.version }}
RELEASE_URL: ${{ needs.create-release.outputs.RELEASE_URL }}
run: |
if [ -z "$RELEASE_URL" ]; then
RELEASE_URL="https://github.com/lucide-icons/lucide/releases/tag/$RELEASE_TAG"
fi
gh api \
--method POST \
repos/lucide-icons/post-release/dispatches \
--field event_type=post_release \
--field "client_payload[release_tag]=$RELEASE_TAG" \
--field "client_payload[release_url]=$RELEASE_URL"
+55
View File
@@ -0,0 +1,55 @@
name: Docs Checks
on:
pull_request:
paths:
- docs/**
- .github/workflows/docs.yml
- package.json
- pnpm-workspace.yaml
- pnpm-lock.yaml
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Release metadata is generated by diffing every release tag
fetch-depth: 0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build docs
run: pnpm --filter @lucide/docs build
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Release metadata is generated by diffing every release tag
fetch-depth: 0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Generate docs data
run: pnpm --filter @lucide/docs run "/^prebuild:.*/"
- name: Typecheck
run: pnpm --filter @lucide/docs typecheck
+16
View File
@@ -46,3 +46,19 @@ jobs:
- name: Test
run: pnpm --filter @lucide/astro test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: 'package.json'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter @lucide/astro typecheck
+16
View File
@@ -37,3 +37,19 @@ jobs:
with:
name: lucide-font
path: lucide-font
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter build-font typecheck
+16
View File
@@ -29,3 +29,19 @@ jobs:
- name: Build
run: pnpm --filter @lucide/lab build
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter @lucide/lab typecheck
+16
View File
@@ -31,3 +31,19 @@ jobs:
- name: Test
run: pnpm --filter lucide-preact test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter lucide-preact typecheck
+16
View File
@@ -47,3 +47,19 @@ jobs:
- name: Test
run: pnpm --filter lucide-react-native test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter lucide-react-native typecheck
+16
View File
@@ -70,3 +70,19 @@ jobs:
- name: Run integration checks
run: pnpm test:integrations:react
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter lucide-react typecheck
+17 -1
View File
@@ -27,4 +27,20 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Test
run: pnpm --filter lucide-react test
run: pnpm --filter @lucide/shared test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter @lucide/shared typecheck
+16
View File
@@ -47,3 +47,19 @@ jobs:
- name: Test
run: pnpm --filter lucide-solid test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter lucide-solid typecheck
+16
View File
@@ -26,3 +26,19 @@ jobs:
- name: Build
run: pnpm --filter lucide-static build
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter lucide-static typecheck
+1 -1
View File
@@ -61,5 +61,5 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test
- name: Typecheck
run: pnpm --filter @lucide/svelte typecheck
+16
View File
@@ -47,3 +47,19 @@ jobs:
- name: Test
run: pnpm --filter @lucide/vue test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test
run: pnpm --filter @lucide/vue typecheck
+16
View File
@@ -46,3 +46,19 @@ jobs:
- name: Test
run: pnpm --filter lucide test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter lucide typecheck
+1
View File
@@ -1,6 +1,7 @@
.DS_Store
.idea
.next
.astro
.obsidian
.now
.cache
+1 -2
View File
@@ -1,13 +1,12 @@
import { eventHandler, setResponseHeader } from 'h3';
import iconNodes from '../../data/iconNodes/index.ts';
import { IconNodeWithKeys } from '../../theme/types';
import iconMetaData from '../../data/iconMetaData';
import releaseMeta from '../../data/releaseMetaData.json';
import categories from '../../data/categoriesData.json';
const dataResponse = {
icons: Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
const newIconNode = iconNode.map(([name, { key, ...attrs }]) => {
return [name, attrs];
});
+1 -2
View File
@@ -1,6 +1,5 @@
import { eventHandler, getQuery, setResponseHeader } from 'h3';
import iconNodes from '../../data/iconNodes';
import { IconNodeWithKeys } from '../../theme/types';
export default eventHandler((event) => {
const query = getQuery(event);
@@ -19,7 +18,7 @@ export default eventHandler((event) => {
return [name, iconNode];
}
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
const newIconNode = iconNode.map(([name, { key, ...attrs }]) => {
return [name, attrs];
});
+1 -1
View File
@@ -97,7 +97,7 @@ export default defineConfig({
{
text: 'Resources',
items: [
...resourcesSidebar[0].items,
...resourcesSidebar[0].items.map(({ text, link }) => ({ text, link })),
{ text: 'How to use icons', link: '/how-to/' },
{ text: 'Contributing icons', link: '/contribute/icons/' },
],
+2 -3
View File
@@ -1,4 +1,3 @@
import { SVGProps } from 'react';
import { getCommands } from './utils';
export type Point = { x: number; y: number };
@@ -15,8 +14,8 @@ export type Path = {
};
export type PathProps<
RequiredProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
NeverProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
RequiredProps extends keyof React.SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
NeverProps extends keyof React.SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
> = Required<Pick<React.SVGProps<SVGElement & SVGRectElement & SVGCircleElement>, RequiredProps>> &
Omit<
React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>,
+1 -4
View File
@@ -53,10 +53,7 @@ export const getCommands = (src: string) =>
getNodes(src)
.map(convertToPathNode)
.flatMap(({ d, name }, idx) =>
new SVGPathData(d)
.toAbs()
// @ts-expect-error `commands` is typed as a union of command types svgson does not narrow
.commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })),
new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })),
);
const getPaths = (src: string) => {
+3 -3
View File
@@ -4,7 +4,7 @@ import { Category, IconEntity } from '../theme/types';
const directory = path.join(process.cwd(), '../categories');
export function getAllCategoryFiles(): Category[] {
export function getAllCategoryFiles(): Omit<Category, 'iconCount'>[] {
const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json');
return fileNames.map((fileName) => {
@@ -21,9 +21,9 @@ export function getAllCategoryFiles(): Category[] {
}
export function mapCategoryIconCount(
categories: Category[],
categories: Omit<Category, 'iconCount'>[],
icons: { categories: IconEntity['categories'] }[],
) {
): Category[] {
return categories.map((category) => ({
...category,
iconCount: icons.reduce(
+2 -1
View File
@@ -1,4 +1,5 @@
import { createLucideIcon } from 'lucide-react/src/lucide-react';
import React from 'react';
import createLucideIcon from 'lucide-react/src/createLucideIcon';
import { type LucideProps, type IconNode } from 'lucide-react/src/types';
import { IconEntity } from '../theme/types';
import { renderToStaticMarkup } from 'react-dom/server';
+2
View File
@@ -20,6 +20,8 @@ export async function getData(name: string) {
const iconNode = iconNodes[name];
const releaseData = releaseMeta?.[name] ?? {
fromFork: false,
awaitingRelease: false,
createdRelease: {
version: '0.0.0',
date: DATE_OF_FORK,
+1 -2
View File
@@ -1,7 +1,6 @@
import { readFileSync } from 'node:fs';
import { dirname, isAbsolute, resolve } from 'node:path';
import type MarkdownIt from 'markdown-it';
import type { RenderRule } from 'markdown-it/lib/renderer.mjs';
import type { MarkdownIt, RenderRule } from './types';
import container from 'markdown-it-container';
type ContainerArgs = [typeof container, string, { render: RenderRule }];
+1 -3
View File
@@ -1,6 +1,4 @@
import type MarkdownIt from 'markdown-it';
import type { RenderRule } from 'markdown-it/lib/renderer.mjs';
import type Token from 'markdown-it/lib/token.mjs';
import type { MarkdownIt, RenderRule, Token } from './types';
import container from 'markdown-it-container';
import sandpackTheme from '../theme/sandpackTheme.json';
+1 -1
View File
@@ -16,7 +16,7 @@
* />
*/
import type MarkdownIt from 'markdown-it';
import type { MarkdownIt } from './types';
type SnackParams = Record<string, string>;
+7
View File
@@ -0,0 +1,7 @@
import type { MarkdownRenderer } from 'vitepress';
// VitePress 1.x renders with markdown-it 14, so derive the plugin types from its renderer
// instead of the markdown-it 15 package installed in docs.
export type MarkdownIt = MarkdownRenderer;
export type RenderRule = NonNullable<MarkdownIt['renderer']['rules'][string]>;
export type Token = Parameters<RenderRule>[0][number];
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { useLocalStorage } from '@vueuse/core';
import IconButton from './base/IconButton.vue';
import Button from './base/Button.vue';
import { x } from '../../data/iconNodes';
import Icon from '@lucide/vue/src/Icon';
import { computed, onMounted, ref } from 'vue';
@@ -65,16 +65,17 @@ function hidePopup() {
}"
class="ask-overlay"
>
<IconButton
<Button
@click="hidePopup"
class="hide-button"
>
<Icon
:iconNode="x"
name="x"
:size="20"
absoluteStrokeWidth
/>
</IconButton>
</Button>
<h2 class="title">
We value your feedback!
@@ -1,7 +1,38 @@
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{
href?: string;
}>()
const isExternal = computed(() => {
return props.href?.startsWith('http') ?? false
})
const component = computed(() => {
return props.href ? 'a' : 'button'
})
const target = computed(() => {
return isExternal.value ? '_blank' : undefined
})
const rel = computed(() => {
return isExternal.value ? 'noreferrer noopener' : undefined
})
</script>
<template>
<button v-bind="$attrs" class="icon-button">
<component
:is="component"
v-bind="$attrs"
:href="href"
:target="target"
:rel="rel"
class="icon-button"
>
<slot />
</button>
</component>
</template>
<style scoped>
@@ -16,12 +47,21 @@
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
border-radius: 6px;
background-color: var(--vp-c-bg-alt);
color: inherit;
text-decoration: none;
/* width: 56px;
height: 56px; */
font-size: 24px;
gap: 8px;
font-size: 16px;
font-weight: 500;
transition: color 0.1s, border-color 0.1s, background-color 0.1s;
}
.icon-button span {
padding: 0 4px;
display: block;
}
.icon-button:hover {
border-color: var(--vp-button-alt-hover-border);
color: var(--vp-button-alt-hover-text);
@@ -14,7 +14,7 @@ export interface InputProps {
import { ref, onMounted, nextTick, watch } from 'vue';
import Icon from '@lucide/vue/src/Icon';
import { x } from '../../../data/iconNodes';
import IconButton from './IconButton.vue';
import Button from './Button.vue';
const props = withDefaults(defineProps<InputProps>(), {
type: 'text',
@@ -68,9 +68,9 @@ defineExpose({
ref="input"
:value="modelValue"
v-bind="$attrs"
@input="$emit('update:modelValue', $event.target.value)"
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
/>
<IconButton
<Button
@click="onClear"
v-if="type === 'search' && modelValue"
class="clear-button"
@@ -79,9 +79,10 @@ defineExpose({
>
<Icon
:iconNode="x"
name="x"
:size="20"
/>
</IconButton>
</Button>
<kbd
v-if="shortcut"
class="shortcut"
@@ -46,6 +46,7 @@ const value = computed({
<template #icon>
<Icon
:iconNode="search"
name="search"
class="search-icon"
/>
</template>
@@ -9,7 +9,8 @@ import RocketDuotoneIcon from '../home/RocketDuotoneIcon.ts';
<p>
<Icon
:iconNode="RocketDuotoneIcon"
size="18"
name="rocket-duotone"
:size="18"
class="rocket-icon"
/>
<VPLink
@@ -0,0 +1,74 @@
<script setup lang="ts">
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue'
const props = defineProps<{
tabs: string[];
}>();
</script>
<template>
<TabGroup>
<TabList class="tab-list">
<template v-for="(tab, index) in props.tabs" :key="index">
<Tab :id="`tab-${index}`" class="tab">{{ tab }}</Tab>
</template>
</TabList>
<TabPanels>
<template v-for="(_, name) in $slots">
<TabPanel>
<slot :name="name" />
</TabPanel>
</template>
</TabPanels>
</TabGroup>
</template>
<style lang="css" scoped>
.tab-list {
display: flex;
gap: 8px;
margin-bottom: 32px;
border-bottom: 1px solid var(--vp-c-divider);
justify-content: center;
}
.tab {
display: block;
line-height: 32px;
font-size: 16px;
padding: 10px 12px;
font-weight: 500;
color: var(--vp-c-text-2);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: color 0.5s;
border-bottom: 2px solid transparent;
}
.tab[aria-selected="true"] {
color: var(--vp-c-text-1);
transition: color 0.25s;
border-bottom: 2px solid var(--vp-c-brand);
}
@media (min-width: 860px) {
.tab-list {
justify-content: flex-start;
}
.tab {
font-size: 18px;
padding: 10px 16px;
}
}
@media (min-width: 960px) {
.tab {
}
}
@media (min-width: 1152px) {
.tab {
}
}
</style>
@@ -34,7 +34,7 @@ const percentage = computed<string>(() => `${((Number(props.modelValue) - props.
type="range"
v-bind="$attrs"
v-bind:value="modelValue"
v-on:input="$emit('update:modelValue', Number($event.target.value))"
v-on:input="$emit('update:modelValue', Number(($event.target as HTMLInputElement).value))"
:min="min"
:max="max"
:step="step"
@@ -1,16 +1,17 @@
<script setup lang="ts">
import { rotateCw } from '../../../data/iconNodes';
import Icon from '@lucide/vue/src/Icon';
import IconButton from './IconButton.vue';
import Button from './Button.vue';
</script>
<template>
<IconButton class="reset-button">
<Button class="reset-button">
<Icon
:size="20"
:iconNode="rotateCw"
name="rotate-cw"
/>
</IconButton>
</Button>
</template>
<style scoped>
@@ -27,6 +27,7 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
<slot name="end-icon">
<Icon
:iconNode="chevronsUpDown"
name="chevrons-up-down"
class="chevron-icon"
aria-hidden="true"
/>
@@ -57,6 +58,7 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
>
<Icon
:iconNode="check"
name="check"
class="check"
aria-hidden="true"
/>
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { type FunctionalComponent } from 'vue';
import { type LucideProps } from '@lucide/vue';
import { type LucideProps } from '@lucide/vue/src/types';
import { Sandpack } from 'sandpack-vue3';
defineProps<FunctionalComponent<LucideProps>>();
@@ -10,7 +10,14 @@ import { useLocalStorage } from '@vueuse/core';
const { page } = useData();
const router = useRouter();
const frameworks = [
interface Framework {
name: string;
icon: string;
iconDark?: string;
value: string;
}
const frameworks: Framework[] = [
{ name: 'Vanilla', icon: '/framework-logos/js.svg', value: '/guide/lucide/' },
{ name: 'React', icon: '/framework-logos/react.svg', value: '/guide/react/' },
{ name: 'Vue', icon: '/framework-logos/vue.svg', value: '/guide/vue/' },
@@ -48,7 +55,7 @@ const selected = computed(() => {
return current || (fallbackFramework.value?.value ? fallbackFramework.value : frameworks[0]);
});
function onSelectFramework(item: { name: string; icon: string; iconDark?: string; value: string }) {
function onSelectFramework(item: Framework) {
fallbackFramework.value = item;
if (item.value !== router.route.path) {
const likeRoute = router.route.path.replace(selected.value.value, item.value);
@@ -2,9 +2,12 @@
import { ref, shallowRef } from 'vue';
import { data } from './HomeHeroIconsCard.data';
import { useRouter } from 'vitepress';
import { motion, Variants, useScroll, useTransform } from 'motion-v';
import { motion, useScroll, useTransform, type Options as MotionOptions } from 'motion-v';
import LucideIcon from '../base/LucideIcon.vue';
// motion-v re-exports framer-motion's `Variants`, which doesn't match the `variants` prop of its components
type Variants = MotionOptions['variants'];
const emit = defineEmits(['animation-complete']);
const MotionLucideIcon = motion.create(LucideIcon);
@@ -129,7 +132,7 @@ const onAnimationComplete = (item) => {
const randomIndex = ref(Math.floor(Math.random() * 64));
const iconAnimationVariants = {
const iconAnimationVariants: Variants = {
initial: {
animationName: 'end',
opacity: 0,
@@ -149,7 +152,7 @@ const iconAnimationVariants = {
const shrinkIconAnimation = ref('initial');
const shrinkIconVariants = {
const shrinkIconVariants: Variants = {
initial: { strokeWidth: 2 },
shrinkIcons: (index) => ({
animationName: 'shrinkIcons',
@@ -69,7 +69,7 @@ const pickIcons = [
'stethoscope',
];
export default {
const loader = {
async load() {
const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode }));
@@ -96,3 +96,8 @@ export default {
};
},
};
declare const data: Awaited<ReturnType<typeof loader.load>>;
export { data };
export default loader;
@@ -1,4 +1,4 @@
export default {
const loader = {
async load() {
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest')
.then((res) => {
@@ -16,3 +16,8 @@ export default {
};
},
};
declare const data: Awaited<ReturnType<typeof loader.load>>;
export { data };
export default loader;
@@ -15,10 +15,11 @@ import RocketDuotoneIcon from './RocketDuotoneIcon.ts';
class="badge-special confetti-button animate"
href="/guide/version-1"
>
<Icon :iconNode="RocketDuotoneIcon" />
<Icon :iconNode="RocketDuotoneIcon" name="rocket-duotone" />
Version 1
<Icon
:iconNode="moveRight"
name="move-right"
class=""
/>
</Badge>
@@ -41,8 +41,8 @@ const sizeCssVar = useCssVar(
)
syncRef(color, colorCssVar)
syncRef(strokeWidth, strokeWidthCssVar)
syncRef(size, sizeCssVar)
syncRef(strokeWidth, strokeWidthCssVar, { transform: { ltr: String, rtl: Number } })
syncRef(size, sizeCssVar, { transform: { ltr: String, rtl: Number } })
function resetStyle () {
color.value = 'currentColor'
@@ -1,4 +1,4 @@
export default {
const loader = {
async load() {
return {
packages: [
@@ -61,3 +61,8 @@ export default {
};
},
};
declare const data: Awaited<ReturnType<typeof loader.load>>;
export { data };
export default loader;
@@ -2,7 +2,7 @@
import { computed } from 'vue';
const props = defineProps<{
headingLevel: 1 | 2 | 3 | 4 | 5 | 6,
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6,
}>()
const headingElement = computed(() => `h${props.headingLevel ?? 2}`)
@@ -1,4 +1,6 @@
const RocketDuotoneIcon = [
import type { LucideIconNode } from '@lucide/vue/src/types';
const RocketDuotoneIcon: LucideIconNode[] = [
[
'path',
{
@@ -16,6 +18,6 @@ const RocketDuotoneIcon = [
'clip-rule': 'evenodd',
},
],
] as const;
];
export default RocketDuotoneIcon;
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { useData } from 'vitepress';
import { useSessionStorage } from '@vueuse/core';
import IconButton from '../base/IconButton.vue';
import Button from '../base/Button.vue';
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue';
import { x } from '../../../data/iconNodes';
import Icon from '@lucide/vue/src/Icon';
@@ -40,16 +40,17 @@ onMounted(() => {
class="floating-ad"
v-if="theme.carbonAds"
>
<IconButton
<Button
@click="hideAd"
class="hide-button"
>
<Icon
:iconNode="x"
name="x"
:size="20"
absoluteStrokeWidth
/>
</IconButton>
</Button>
<VPDocAsideCarbonAds :carbon-ads="theme.carbonAds" overlay />
</div>
</template>
@@ -1,14 +1,17 @@
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
import iconsMetaData from '../../../data/iconMetaData';
export default {
const loader = {
async load() {
let categories = getAllCategoryFiles();
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData));
const categories = mapCategoryIconCount(getAllCategoryFiles(), Object.values(iconsMetaData));
return {
categories,
};
},
};
declare const data: Awaited<ReturnType<typeof loader.load>>;
export { data };
export default loader;
@@ -2,7 +2,7 @@
import { ref, computed, watch } from 'vue'
import { useData } from 'vitepress'
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
import { isActive } from 'vitepress/dist/client/shared'
import { isActive } from 'vitepress/dist/client/shared.js'
import { useActiveAnchor } from '../../composables/useActiveAnchor'
import { data } from './CategoryList.data'
import CategoryListItem from './CategoryListItem.vue'
@@ -0,0 +1,29 @@
<script setup lang="ts">
import { pencilSparkles } from '../../../data/iconNodes';
import { IconEntity } from '../../types';
import getStudioLink from '../../utils/getStudioLink';
import Button from '../base/Button.vue';
import Icon from '@lucide/vue/src/Icon.ts';
defineProps<{
icon: IconEntity
}>()
</script>
<template>
<Button
:href="getStudioLink(icon.name, icon.iconNode, 'icon-info')"
target="_blank"
rel="noopener noreferrer"
>
<Icon :iconNode="pencilSparkles" name="pencil-sparkles" :size="24" />
<span class="text">Edit in studio</span>
</Button>
</template>
<style scoped>
.text {
margin: 0 4px;
}
</style>
@@ -30,10 +30,15 @@ const props = defineProps<{
.contributors {
display: flex;
flex-direction: row;
align-items: center;
align-items: start;
/* justify-content: flex-end; */
gap: 16px;
gap: 2px;
}
.contributors .label {
margin-top: 12px;
}
.avatar-group {
display: flex;
}
@@ -16,19 +16,17 @@ const tags = computed(() => {
</script>
<template>
<!-- <PageContainer class="overview"> -->
<IconPreview
:name="$params.name"
:iconNode="$params.iconNode"
class="preview"
customizable
/>
<div class="details">
<h1 class="title">
{{ $params.name }}
</h1>
</div>
<!-- </PageContainer> -->
<IconPreview
:name="$params.name"
:iconNode="$params.iconNode"
class="preview"
customizable
/>
<div class="details">
<h1 class="title">
{{ $params.name }}
</h1>
</div>
</template>
<style scoped>
@@ -6,7 +6,11 @@ import useConfetti from '../../composables/useConfetti';
const { animate, confetti } = useConfetti();
const slots = useSlots();
const copiedText = computed(() => slots.default?.()[0].children);
const copiedText = computed(() => {
const children = slots.default?.()[0].children;
return typeof children === 'string' ? children : '';
});
function copyText() {
navigator.clipboard.writeText(copiedText.value);
@@ -25,6 +29,7 @@ function copyText() {
<slot />
<Icon
:iconNode="copy"
name="copy"
:size="20"
class="copy-icon"
/>
@@ -2,7 +2,7 @@
import type { IconEntity } from '../../types';
import { computed } from 'vue';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
import IconButton from '../base/IconButton.vue';
import Button from '../base/Button.vue';
import IconContributors from './IconContributors.vue';
import IconPreview from './IconPreview.vue';
import { x, expand } from '../../../data/iconNodes';
@@ -12,6 +12,7 @@ import Badge from '../base/Badge.vue';
import { computedAsync } from '@vueuse/core';
import { satisfies } from 'semver';
import { useExternalLibs } from '../../composables/useExternalLibs';
import EditInStudioButton from './EditInStudioButton.vue';
const props = defineProps<{
iconName: string | null;
@@ -70,12 +71,19 @@ const Expand = createLucideIcon('Expand', expand);
<div class="overlay-panel">
<nav class="overlay-menu">
<Badge
v-if="icon.createdRelease"
v-if="icon.awaitingRelease"
class="version awaiting-release"
>Unreleased</Badge
>
<Badge
v-else-if="icon.createdRelease"
class="version"
:href="releaseTagLink(icon.createdRelease.version)"
>v{{ icon.createdRelease.version }}</Badge
>
<IconButton
{{ icon.createdRelease.version }}
</Badge>
<EditInStudioButton :icon="icon"/>
<Button
@click="
go(
icon.externalLibrary
@@ -85,10 +93,10 @@ const Expand = createLucideIcon('Expand', expand);
"
>
<component :is="Expand" />
</IconButton>
<IconButton @click="onClose">
</Button>
<Button @click="onClose">
<component :is="CloseIcon" />
</IconButton>
</Button>
</nav>
<IconPreview
id="previewer"
@@ -200,7 +208,14 @@ const Expand = createLucideIcon('Expand', expand);
margin-right: 24px;
}
.version.awaiting-release {
color: var(--vp-c-danger-3);
}
.contributors {
justify-content: flex-end;
position: absolute;
bottom: 24px;
right: 24px;
}
</style>
@@ -10,30 +10,36 @@ import { computed } from 'vue';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
import { diamond } from '../../../data/iconNodes';
import deprecationReasonTemplate from '../../../../../tools/build-icons/utils/deprecationReasonTemplate.ts';
import type { IconDeprecationReason } from '../../../../../tools/build-icons/types.ts';
import IconTags from "./IconTags.vue"
import getStudioLink from '../../utils/getStudioLink';
const props = defineProps<{
icon: IconEntity;
popoverPosition?: 'top' | 'bottom';
showEditButton?: boolean;
}>();
const { go } = useRouter();
const { page } = useData();
const tags = computed(() => {
if (!props.icon || !props?.icon?.tags) return [];
return props.icon.tags.join(' • ');
});
const DiamondIcon = createLucideIcon('Diamond', diamond);
const deprecatedTitle = computed(() => {
if (!props.icon.deprecationReason) return '';
return deprecationReasonTemplate(props.icon.deprecationReason, {
return deprecationReasonTemplate(props.icon.deprecationReason as IconDeprecationReason, {
componentName: props.icon.name,
iconName: props.icon.name,
toBeRemovedInVersion: props.icon.toBeRemovedInVersion,
});
});
const iconPath = computed(() => {
if (props.icon.externalLibrary) {
return `icons/${props.icon.externalLibrary}/${props.icon.name}`;
}
return `icons/${props.icon.name}`;
});
</script>
<template>
@@ -60,14 +66,7 @@ const deprecatedTitle = computed(() => {
Deprecated
</Badge>
</div>
<div
class="tags-scroller"
v-if="tags.length"
>
<p class="icon-tags horizontal-scroller">
{{ tags }}
</p>
</div>
<IconTags :tags="props.icon.tags" />
<div class="group">
<Badge
v-for="category in icon.categories"
@@ -80,26 +79,10 @@ const deprecatedTitle = computed(() => {
<div class="group buttons">
<VPButton
v-if="
!page?.relativePath?.startsWith?.(
icon.externalLibrary
? `icons/${icon.externalLibrary}/${icon.name}`
: `icons/${icon.name}`,
)
"
:href="
icon.externalLibrary
? `/icons/${icon.externalLibrary}/${icon.name}`
: `/icons/${icon.name}`
"
v-if="!page?.relativePath?.startsWith?.(iconPath)"
:href="iconPath"
text="See in action"
@click="
go(
icon.externalLibrary
? `/icons/${icon.externalLibrary}/${icon.name}`
: `/icons/${icon.name}`,
)
"
@click="go(iconPath)"
/>
<CopySVGButton
:name="icon.name"
@@ -115,6 +98,38 @@ const deprecatedTitle = computed(() => {
</template>
<style scoped>
.studio-edit-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 0 14px;
min-height: 40px;
border: 1px solid var(--vp-button-alt-border);
border-radius: 999px;
background-color: var(--vp-button-alt-bg);
color: var(--vp-button-alt-text);
font-size: 15px;
font-weight: 600;
line-height: 1;
text-decoration: none;
transition:
border-color 0.2s,
color 0.2s,
background-color 0.2s,
transform 0.2s;
margin-left: 0;
margin-inline-start: 0;
}
.studio-edit-button:hover,
.studio-edit-button:focus-visible {
border-color: var(--vp-button-alt-hover-border);
color: var(--vp-button-alt-hover-text);
background-color: var(--vp-button-alt-hover-bg);
transform: translateY(-1px);
}
.group {
display: flex;
flex-wrap: wrap;
@@ -156,66 +171,6 @@ const deprecatedTitle = computed(() => {
background-color: var(--vp-c-brand-2);
}
.icon-tags {
font-size: 16px;
color: var(--vp-c-text-2);
font-weight: 500;
line-height: 28px;
white-space: nowrap;
position: absolute;
top: 0;
left: 0;
right: 0;
margin-top: 0;
margin-bottom: 0;
}
.tags-scroller {
position: relative;
max-width: 100%;
width: 100%;
height: 28px;
padding: 8px 0 16px;
margin-bottom: 16px;
margin-top: 8px;
align-items: center;
--gradient-background: var(--tags-gradient-background, var(--vp-c-bg-elv));
}
.horizontal-scroller {
overflow-x: scroll;
/* Hide Scrollbar */
-ms-overflow-style: none;
scrollbar-width: none;
scrollbar-width: thin; /* can also be normal, or none, to not render scrollbar */
scrollbar-color: var(--vp-c-text-4) transparent; /* foreground background */
}
.horizontal-scroller::-webkit-scrollbar {
width: 0;
display: none;
}
.horizontal-scroller::-webkit-scrollbar-track {
background: transparent;
}
.horizontal-scroller::-webkit-scrollbar-thumb {
background: transparent;
border: none;
}
.tags-scroller::after {
content: '';
position: absolute;
bottom: 0;
width: 32px;
height: 100%;
/* Background Gradient left to right */
background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, var(--gradient-background) 100%);
right: 0;
pointer-events: none;
}
.buttons {
margin-top: 24px;
}
@@ -3,7 +3,6 @@ import type { IconEntity } from '../../types';
import { computed, ref } from 'vue';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
import { useIconStyleContext } from '../../composables/useIconStyle';
import { squarePen } from '../../../data/iconNodes';
const props = defineProps<{
name: IconEntity['name'];
@@ -11,6 +10,8 @@ const props = defineProps<{
customizable?: boolean;
}>();
const activeSize = ref('1000%');
const { size, color, strokeWidth, absoluteStrokeWidth } = useIconStyleContext();
const previewIcon = ref();
@@ -20,31 +21,6 @@ const iconComponent = computed(() => {
if (!props.name || !props.iconNode) return null;
return createLucideIcon(props.name, props.iconNode);
});
const SquarePenIcon = createLucideIcon('SquarePen', squarePen);
const innerSvg = computed(() => {
if (!props.iconNode) return '';
const string = props.iconNode
.map(([tag, attrs]) => {
const attributes = Object.entries(attrs)
.map(([key, value]) => `${key}="${value}"`)
.join(' ');
return `<${tag} ${attributes}/>`;
})
.join('');
return string
.replace(/>[\r\n ]+</g, '><')
.replace(/(<.*?>)|\s+/g, (m, $1) => $1 || ' ')
.trim();
});
const base64InnerSvg = computed(() => {
if (typeof window === 'undefined') return '';
return btoa(innerSvg.value);
});
</script>
<template>
@@ -55,6 +31,8 @@ const base64InnerSvg = computed(() => {
:size="size"
:color="color"
:strokeWidth="absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth"
class="preview-icon"
:data-size="activeSize"
/>
<svg
class="icon-grid"
@@ -62,6 +40,7 @@ const base64InnerSvg = computed(() => {
fill="none"
stroke-width="0.1"
xmlns="http://www.w3.org/2000/svg"
:class="{ show: activeSize === '1000%' }"
>
<g
:key="`grid-${i}`"
@@ -83,16 +62,17 @@ const base64InnerSvg = computed(() => {
/>
</g>
</svg>
<a
:href="`https://studio.lucide.dev/edit?value=${encodeURIComponent(base64InnerSvg)}&name=${name}&utm_source=lucide.dev&utm_medium=icon-detail-preview`"
target="_blank"
rel="noopener noreferrer"
class="studio-button"
title="Open in Lucide Studio"
aria-label="Open in Lucide Studio"
>
<SquarePenIcon :size="14" />
</a>
<div class="size-buttons" :class="{ show: activeSize !== '1000%' }">
<button class="size-button" @click="activeSize = '1000%'" :class="{ active: activeSize === '1000%' }">
1000%
</button>
<button class="size-button" @click="activeSize = '200%'" :class="{ active: activeSize === '200%' }">
200%
</button>
<button class="size-button" @click="activeSize = '100%'" :class="{ active: activeSize === '100%' }">
100%
</button>
</div>
</div>
</template>
@@ -104,56 +84,87 @@ const base64InnerSvg = computed(() => {
top: 0;
left: 0;
stroke: var(--vp-c-divider);
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
.icon-grid.show {
opacity: 1;
}
.icon-container {
height: 100%;
aspect-ratio: 1/1;
position: relative;
background: var(--vp-c-bg-alt);
border-radius: 8px;
display: flex;
overflow: hidden;
flex-shrink: 0;
}
.icon-container > :deep(svg:not(.icon-grid)) {
.icon-container:hover .size-buttons {
opacity: 1;
}
.icon-container > :deep(svg.preview-icon) {
width: 100%;
height: 100%;
position: relative;
z-index: 1;
color: var(--vp-c-neutral);
opacity: 0.8;
transition: all 0.3s;
margin: auto;
}
.studio-button {
position: absolute;
bottom: 8px;
right: 8px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 5px;
display: flex;
align-items: center;
justify-content: center;
color: var(--vp-c-text-2);
opacity: 0;
transition: opacity 0.2s, color 0.2s, background-color 0.2s;
z-index: 2;
.icon-container > :deep(svg.preview-icon[data-size='1000%']) {
width: 100%;
height: 100%;
}
.icon-container:hover .studio-button,
.studio-button:focus-visible {
opacity: 1;
.icon-container > :deep(svg.preview-icon[data-size='200%']) {
width: 48px;
height: 48px;
}
.studio-button:hover,
.studio-button:focus-visible {
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
.icon-container > :deep(svg.preview-icon[data-size='100%']) {
width: 24px;
height: 24px;
}
.icon-component.customizable {
will-change: width, height, stroke-width, stroke;
/* color: var(--customize-color, currentColor);
stroke-width: var(--customize-strokeWidth, 2); */
}
.size-buttons {
position: absolute;
bottom: 8px;
left: 50%;
padding: 4px;
border-radius: 12px;
transform: translateX(-50%);
display: flex;
z-index: 99;
background: var(--vp-code-tab-bg);
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
.size-buttons.show {
opacity: 1;
}
.size-button {
color: var(--vp-code-tab-text-color);
padding: 2px 8px;
}
.size-button.active {
background: var(--vp-c-neutral-inverse);
box-shadow: var(--vp-shadow-1);
border-radius: 4px;
color: var(--vp-code-tab-active-text-color);
text-decoration: none;
}
</style>
@@ -70,7 +70,6 @@ const prettyName = props.name
class="showcase"
v-if="iconComponent"
>
<h2 class="title">See this icon in action</h2>
<div class="showcase-grid">
<div class="showcase-item column">
<div class="placeholder"></div>
@@ -236,18 +235,17 @@ const prettyName = props.name
.showcase-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 48px;
margin-inline-start: 24px;
margin-block-start: 48px;
gap: 32px;
background: var(--vp-code-block-bg);
padding: 32px;
border-radius: 8px;
}
.showcase-item {
padding: 24px;
border-radius: 8px;
background-color: var(--vp-c-bg);
box-shadow:
var(--vp-shadow-4),
-24px -24px 0 var(--vp-c-bg-soft);
box-shadow: var(--vp-shadow-2);
display: flex;
align-items: center;
justify-content: center;
@@ -0,0 +1,110 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
const MAX_VISIBLE_TAGS = 5;
const props = defineProps<{
tags?: string[];
}>();
const tags = computed(() => props.tags ?? []);
const expanded = ref(false);
const hiddenCount = computed(() => Math.max(tags.value.length - MAX_VISIBLE_TAGS, 0));
const showAll = computed(() => expanded.value || hiddenCount.value === 0);
// The overlay reuses this component when switching icons, so collapse again
watch(tags, () => {
expanded.value = false;
});
</script>
<template>
<ul
v-if="tags.length"
class="icon-tags"
>
<!-- v-show keeps hidden tags in the rendered HTML so crawlers can index them -->
<li
v-for="(tag, index) in tags"
v-show="showAll || index < MAX_VISIBLE_TAGS"
:key="tag"
class="tag"
>
{{ tag }}
</li>
<li
v-if="!showAll"
class="more"
>
<button
type="button"
class="more-button"
:aria-label="`Show ${hiddenCount} more tags`"
@click="expanded = true"
>
+{{ hiddenCount }}
</button>
</li>
</ul>
</template>
<style scoped>
.icon-tags {
font-size: 16px;
color: var(--vp-c-text-2);
font-weight: 500;
line-height: 28px;
display: flex;
flex-wrap: wrap;
align-items: center;
list-style-type: disc;
gap: 4px 8px;
/* Clip bullets hanging before tags that start a wrapped line, padding leaves room for the focus ring */
overflow: hidden;
padding: 4px;
margin: 4px -4px 12px;
}
.tag {
margin-top: 0;
padding-left: 0px;
padding-right: 12px;
list-style-position: outside;
white-space: nowrap;
}
.tag:first-child {
list-style: none;
margin-left: 0;
padding-left: 0;
}
.more {
margin-top: 0;
list-style: none;
}
.more-button {
font: inherit;
color: var(--vp-c-text-2);
font-size: 12px;
padding: 0 8px;
margin-left: -4px;
border-radius: 16px;
background-color: var(--vp-c-bg-alt);
transition:
color 0.25s,
background-color 0.25s;
}
.more-button:hover,
.more-button:focus-visible {
color: var(--vp-c-text-1);
background-color: var(--vp-c-default-soft);
}
.more-button:focus-visible {
outline: 2px solid var(--vp-c-brand-1);
outline-offset: 2px;
}
</style>
@@ -163,7 +163,7 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
function handleCloseDrawer() {
setActiveIconName('');
const url = new URL(window.location);
const url = new URL(window.location.href);
url.pathname = '/icons/categories';
if (searchQueryDebounced.value) {
@@ -35,7 +35,6 @@ const SORTING = [
},
]
const initialGridItems = computed(() => {
if (containerWidth.value === 0) return 120;
@@ -72,6 +71,8 @@ const sortedIcons = computed(() => {
return [...props.icons].sort((a, b) => (b.popularity || 0) - (a.popularity || 0));
case 'release-date':
return [...props.icons].sort((a, b) => {
if (a.awaitingRelease !== b.awaitingRelease) return a.awaitingRelease ? -1 : 1;
const aDate = a.createdRelease?.date ? new Date(a.createdRelease.date).getTime() : 0;
const bDate = b.createdRelease?.date ? new Date(b.createdRelease.date).getTime() : 0;
return bDate - aDate;
@@ -172,7 +173,7 @@ watch(searchQueryDebounced, () => {
function handleCloseDrawer() {
setActiveIconName('');
const url = new URL(window.location);
const url = new URL(window.location.href);
url.pathname = '/icons/';
if (searchQueryDebounced.value) {
@@ -207,6 +208,7 @@ function handleCloseDrawer() {
<template #start-icon>
<Icon
:iconNode="listSortDescending"
name="list-sort-descending"
class="chevron-icon"
aria-hidden="true"
/>
@@ -39,7 +39,7 @@ interface Placeholder {
finePrint?: string;
}
const brandPlaceholders: Placeholder[] = shallowReadonly([
const brandPlaceholders: readonly Placeholder[] = shallowReadonly([
{
title: 'Boooo! What a scary brand logo!',
message:
@@ -103,7 +103,7 @@ const brandPlaceholders: Placeholder[] = shallowReadonly([
},
]);
const notFoundPlaceholders: Omit<Placeholder, 'title'>[] = shallowReadonly([
const notFoundPlaceholders: readonly Omit<Placeholder, 'title'>[] = shallowReadonly([
{
message: 'We’ve looked for this icon for a bird’s eye view, but could not find it.',
icon: markRaw(bird),
@@ -130,7 +130,7 @@ const notFoundPlaceholders: Omit<Placeholder, 'title'>[] = shallowReadonly([
},
]);
function randomItem<T>(arr: T[]): T {
function randomItem<T>(arr: readonly T[]): T {
return arr[Math.floor(Math.random() * arr.length)];
}
@@ -9,7 +9,6 @@ defineProps<{
<template>
<section class="related-icons">
<h2 class="title">More icons like this</h2>
<IconGrid :icons="icons" />
</section>
</template>
@@ -34,8 +34,8 @@ const sizeCssVar = useCssVar('--customize-size', props.rootEl?.value ?? document
});
syncRef(color, colorCssVar, { direction: 'ltr' });
syncRef(strokeWidth, strokeWidthCssVar, { direction: 'ltr' });
syncRef(size, sizeCssVar, { direction: 'ltr' });
syncRef(strokeWidth, strokeWidthCssVar, { direction: 'ltr', transform: { ltr: String } });
syncRef(size, sizeCssVar, { direction: 'ltr', transform: { ltr: String } });
function resetStyle() {
color.value = STYLE_DEFAULTS.color;
@@ -61,6 +61,50 @@
}
}
.search-bar :deep(.select-button) {
padding: 11px 14px;
background-color: var(--vp-c-bg-alt);
color: var(--vp-c-text-2);
flex-shrink: 0;
flex: 1;
font-weight: 500;
cursor: pointer;
}
.search-bar :deep(.select-button):hover,
.search-bar :deep(.select-button):focus {
border-color: var(--vp-c-brand);
background: var(--vp-c-bg-alt);
}
.search-bar :deep(.select-options) {
min-width: 160px;
}
.search-bar :deep(.select-option) {
padding-right: 36px;
padding-left: 8px;
}
.search-bar :deep(.select-icon) {
display: none;
}
.search-bar :deep(.select-button) .select-text {
display: none;
}
@media (min-width: 640px) {
.search-bar :deep(.select-button) {
padding: 11px 16px 11px 16px;
}
.search-bar :deep(.select-button) .select-text {
display: block;
}
}
@media (min-width: 960px) {
.search-bar {
padding-top: 32px;
@@ -68,4 +112,7 @@
top: calc(var(--vp-layout-top-height, 0px) + 64px);
}
}
</style>
@@ -1,6 +1,6 @@
<script setup lang="ts">
import VPHero from 'vitepress/dist/client/theme-default/components/VPHero.vue'
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
import { useData } from 'vitepress/dist/client/theme-default/composables/data.js'
import FakeInput from '../base/FakeInput.vue';
import { useRouter } from 'vitepress';
import { data } from '../home/HomeHeroIconsCard.data'
@@ -41,7 +41,7 @@ import {
sparkles,
} from '../../../data/iconNodes';
import createElement from 'lucide/src/createElement';
import { IconNode } from 'lucide';
import { IconNode } from 'lucide/src/types';
const icons: Component[] = [
heart, star, zap, code, feather, cloud, sun, moon, camera, music,
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { useData } from 'vitepress';
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar';
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar.js';
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue';
import { computed } from 'vue';
import SurveyAskOverlay from '../SurveyAskOverlay.vue';
@@ -2,7 +2,7 @@ import packageDataList from '../../../data/packageData.json';
import thirdPartyPackages from '../../../data/packageData.thirdParty.json';
import fetchPackages from '../../../lib/fetchPackages';
export default {
const loader = {
async load() {
const packageJsonList = await fetchPackages();
@@ -29,3 +29,8 @@ export default {
};
},
};
declare const data: Awaited<ReturnType<typeof loader.load>>;
export { data };
export default loader;
@@ -1,5 +1,5 @@
import { onMounted, onUpdated, onUnmounted } from 'vue';
import { throttleAndDebounce } from 'vitepress/dist/client/theme-default/support/utils';
import { throttleAndDebounce } from 'vitepress/dist/client/theme-default/support/utils.js';
/*
* This file is compied and adjusted from vitepress/dist/client/theme-default/composables/useActiveAnchor.ts
@@ -1,10 +1,10 @@
import Fuse from 'fuse.js';
import Fuse, { type FuseOptionKeyObject } from 'fuse.js';
import { shallowRef, computed, Ref } from 'vue';
const useSearch = <T>(
query: Ref<string>,
collection: Ref<T[]>,
keys: Fuse.FuseOptionKeyObject<T>[] = [],
keys: FuseOptionKeyObject<T>[] = [],
) => {
const index = shallowRef(
new Fuse(collection.value, {
+4
View File
@@ -152,6 +152,10 @@ html {
.VPHomeHero .container {
flex-direction: row;
}
.VPDoc:has(.vp-doc[class*=' _icons_']) {
padding: 32px 32px 0;
}
}
.VPNavBarHamburger .container > span {
+11 -2
View File
@@ -1,11 +1,17 @@
export type IconNode = [elementName: string, attrs: Record<string, string>][];
export type IconNodeWithKeys = [elementName: string, attrs: Record<string, string>, key: string][];
export interface Alias {
name: string;
deprecationReason: string;
deprecated: boolean;
}
export interface IconMetaData {
tags: string[];
categories: string[];
contributors: string[];
aliases?: string[];
aliases?: Alias[];
deprecated?: boolean;
deprecationReason?: string;
toBeRemovedInVersion?: string;
@@ -18,6 +24,8 @@ export interface IconEntity extends IconMetaData {
externalLibrary?: ExternalLibs;
createdRelease?: Release;
changedRelease?: Release;
fromFork?: boolean;
awaitingRelease?: boolean;
popularity?: number;
}
@@ -43,7 +51,8 @@ export interface PackageItem {
packageDirname?: string;
description: string;
icon: string;
iconDark: string;
iconDark?: string;
iconClass?: string;
shields: Shield[];
source: string;
documentation: string;
+2 -2
View File
@@ -1,5 +1,5 @@
const chunkArray = <ArrayType>(stream: ArrayType, size: number) => {
return stream.reduce<ArrayType[][]>(
const chunkArray = <ItemType>(stream: ItemType[], size: number) => {
return stream.reduce<ItemType[][]>(
(chunks, item, idx, arr) =>
idx % size == 0 ? [...chunks, arr.slice(idx, idx + size)] : chunks,
[],
@@ -0,0 +1,25 @@
import type { IconNode } from '../types';
function getInnerSvg(iconNode: IconNode) {
return iconNode
.map(([tag, attrs]) => {
const attributes = Object.entries(attrs)
.map(([key, value]) => `${key}="${value}"`)
.join(' ');
return `<${tag} ${attributes}/>`;
})
.join('')
.replace(/>[\r\n ]+</g, '><')
.replace(/(<.*?>)|\s+/g, (m, $1) => $1 || ' ')
.trim();
}
export default function getStudioLink(
name: string,
iconNode: IconNode,
utmMedium = 'icon-detail-preview',
) {
const base64InnerSvg = btoa(getInnerSvg(iconNode ?? []));
return `https://studio.lucide.dev/edit?value=${encodeURIComponent(base64InnerSvg)}&name=${name}&utm_source=lucide.dev&utm_medium=${utmMedium}`;
}
+5
View File
@@ -1,3 +1,4 @@
/// <reference types="vitepress/client" />
import { type IconNode } from '@lucide/vue/src/types';
import Vue from 'vue';
@@ -31,7 +32,11 @@ declare module '*.node.json' {
}
declare global {
// Defined by VitePress at build time, used by its VPDocAsideCarbonAds component
const __CARBON__: boolean;
interface Window {
_carbonads?: unknown;
ExpoSnack?: {
/**
* Initialize all snack players on the page
+101 -93
View File
@@ -12,6 +12,7 @@ import { computed } from 'vue'
import { useData } from 'vitepress'
import IconPreview from '~/.vitepress/theme/components/icons/IconPreview.vue'
import IconPreviewSmall from '~/.vitepress/theme/components/icons/IconPreviewSmall.vue'
import EditInStudioButton from '~/.vitepress/theme/components/icons/EditInStudioButton.vue'
import IconInfo from '~/.vitepress/theme/components/icons/IconInfo.vue'
import IconContributors from '~/.vitepress/theme/components/icons/IconContributors.vue'
import IconShowcase from '~/.vitepress/theme/components/icons/IconShowcase.vue'
@@ -19,9 +20,11 @@ import RelatedIcons from '~/.vitepress/theme/components/icons/RelatedIcons.vue'
import CodeGroup from '~/.vitepress/theme/components/base/CodeGroup.vue'
import Badge from '~/.vitepress/theme/components/base/Badge.vue'
import Label from '~/.vitepress/theme/components/base/Label.vue'
import PageTabs from '~/.vitepress/theme/components/base/PageTabs.vue'
import { data } from './codeExamples.data'
import { toCamelCase, toPascalCase } from '@lucide/shared'
import { satisfies } from 'semver'
import CarbonAdOverlay from '~/.vitepress/theme/components/icons/CarbonAdOverlay.vue';
const { params } = useData()
@@ -49,75 +52,92 @@ function releaseTagLink(version) {
}
</script>
<div :class="$style.layout">
<div :class="$style.iconPreviews">
<div class="layout">
<div class="actions">
<EditInStudioButton :icon="params" />
</div>
<div class="iconPreviews">
<IconPreview
id="previewer"
:name="params.name"
:iconNode="params.iconNode"
:class="$style.preview"
/>
<IconPreviewSmall
:name="params.name"
:iconNode="params.iconNode"
:class="$style.smallPreview"
class="preview"
/>
</div>
<div >
<div :class="$style.info">
<div>
<div class="info">
<IconInfo :icon="params" />
<div :class="$style.meta">
<div class="meta">
<div
v-if="params.createdRelease?.version"
:class="$style.version"
v-if="params.awaitingRelease"
class="version"
>
<Label>Created:</Label>
<Badge class="awaiting-release">Unreleased</Badge>
</div>
<div
v-else-if="params.createdRelease?.version"
class="version"
>
<Label>Created:</Label>
<Badge
:href="releaseTagLink(params.createdRelease.version)"
>
v{{params.createdRelease.version}}
{{params.createdRelease.version}}
</Badge>
</div>
<div
v-if="params.changedRelease?.version"
:class="$style.version"
v-if="!params.awaitingRelease && params.changedRelease?.version"
class="version"
>
<Label>Last changed:</Label>
<Badge
:href="releaseTagLink(params.changedRelease.version)"
>
v{{params.changedRelease.version}}
{{params.changedRelease.version}}
</Badge>
</div>
<IconContributors :icon="params" :class="$style.contributors"/>
<IconContributors :icon="params" class="contributors"/>
</div>
</div>
</div>
</div>
<PageTabs
:tabs="['More like this', 'Code examples', 'See in action']"
>
<template #tab-0>
<RelatedIcons
v-if="params.relatedIcons"
:icons="params.relatedIcons"
/>
</template>
<template #tab-1>
<CodeGroup
:groups="tabs"
groupName="icon-code-example"
:class="$style.code"
class="code"
>
<div
class="blocks"
v-html="codeExample"
/>
</CodeGroup>
</div>
</div>
</template>
<div class="icon-page-sections">
<IconShowcase
:name="params.name"
:iconNode="params.iconNode"
/>
<template #tab-2>
<IconShowcase
:name="params.name"
:iconNode="params.iconNode"
/>
</template>
</PageTabs>
<RelatedIcons
v-if="params.relatedIcons"
:icons="params.relatedIcons"
/>
</div>
<CarbonAdOverlay :drawerOpen="false" />
<style module>
<style scoped>
.preview {
grid-area: preview;
margin-bottom: 24px;
@@ -130,31 +150,52 @@ function releaseTagLink(version) {
align-items: flex-start;
}
.actions {
text-align: right;
display: flex;
justify-content: flex-end;
margin: -12px -12px 24px 0;
}
.meta {
margin-left: auto;
margin-top: 24px;
display: flex;
gap: 24px;
align-items: flex-start;
}
.info {
--tags-gradient-background: var(--vp-c-bg);
}
.icon-info {
text-align: center;
}
.version .label {
margin-top: 12px;
}
.badge {
margin: 12px 0;
}
.version, .contributors {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: flex-start;
margin-bottom: 0px;
justify-content: flex-start;
gap: 4px;
}
.version:first-child {
margin-bottom: 8px;
.version .awaiting-release {
color: var(--vp-c-danger-3);
}
.iconPreviews {
display: flex;
justify-content: flex-start;
justify-content: center;
gap: 24px;
}
@@ -178,6 +219,13 @@ function releaseTagLink(version) {
.iconPreviews {
flex-direction: column;
justify-content: flex-start;
}
.actions {
position: absolute;
right: 0;
margin: 0;
}
.smallPreview {
@@ -185,61 +233,6 @@ function releaseTagLink(version) {
align-items: center;
}
}
@media (min-width: 860px) {
.info {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.meta {
border-left: 1px solid var(--vp-c-divider);
padding-left: 16px;
margin-top: 0;
}
.version, .contributors {
flex-direction: column;
}
}
@media (min-width: 960px) {
.info {
display: block;
justify-content: space-between;
align-items: flex-start;
}
.meta {
border-left: none;
padding-left: 0;
margin-top: 24px;
}
.version, .contributors {
flex-direction: row;
}
}
@media (min-width: 1152px) {
.info {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.meta {
border-left: 1px solid var(--vp-c-divider);
padding-left: 16px;
margin-top: 0;
}
.version, .contributors {
flex-direction: row;
margin-bottom: 8px;
}
}
</style>
<style>
@@ -249,4 +242,19 @@ function releaseTagLink(version) {
margin-block-end: 64px;
padding-top: 32px;
}
.tab-list {
margin: 24px -24px 24px;
}
@media (min-width: 640px) {
.tab-list {
margin: 24px 0 24px;
}
.iconPreviews {
justify-content: flex-start;
}
}
</style>
+22 -11
View File
@@ -3,17 +3,28 @@ import * as iconDetails from '../.vitepress/data/iconDetails';
export default {
async load() {
return {
icons: Object.entries(iconDetails).map(
([, { name, iconNode, popularity, createdRelease, aliases = [] }]) => ({
name,
iconNode,
popularity: popularity?.count ?? 0,
aliases: aliases
.map((alias) => (typeof alias === 'string' ? alias : alias?.name))
.filter((alias): alias is string => Boolean(alias)),
createdRelease,
}),
),
icons: Object.entries(iconDetails)
.map(
([
,
{ name, iconNode, popularity, createdRelease, awaitingRelease, aliases = [], fromFork },
]) => ({
name,
iconNode,
popularity: popularity?.count ?? 0,
aliases: aliases
.map((alias) => (typeof alias === 'string' ? alias : alias?.name))
.filter((alias): alias is string => Boolean(alias)),
createdRelease,
awaitingRelease,
fromFork,
}),
// We hide icons that are not released yet, we make exceptions for preview environments
)
.filter(
({ awaitingRelease }) =>
!awaitingRelease || process.env.VITE_SHOW_AWAITING_RELEASE === 'true',
),
};
},
};
+1 -2
View File
@@ -1,11 +1,10 @@
import iconNodes from '../../.vitepress/data/lab/iconNodes';
import iconPopularity from '../../.vitepress/data/lab/iconPopularity';
import iconMetaData from '../../.vitepress/data/lab/iconMetaData';
import { type IconEntity } from '../../.vitepress/theme/types';
export default {
paths: async () => {
return (Object.entries(iconNodes) as unknown as IconEntity[]).map(([name, iconNode]) => {
return Object.entries(iconNodes).map(([name, iconNode]) => {
const iconDetails = iconMetaData[name];
delete iconDetails['$schema'];
+7 -4
View File
@@ -15,7 +15,7 @@
"prebuild:categoriesJson": "node ./scripts/writeCategoriesMetadata.mts",
"prebuild:relatedIcons": "node ./scripts/writeIconRelatedIcons.mts",
"prebuild:iconDetails": "node ./scripts/writeIconDetails.mts",
"prebuild:iconPopularity": "node ./scripts/writeiconPopularity.mts && node ./scripts/writeiconPopularityIndex.mts",
"prebuild:iconPopularity": "node --env-file-if-exists=.env ./scripts/writeiconPopularity.mts && node ./scripts/writeiconPopularityIndex.mts",
"prebuild:brandStopwords": "node ./scripts/writeBrandStopwords.mts",
"prebuild:writeLabIconNodes": "node ./scripts/writeLabIconNodes.mts",
"prebuild:writeLabIconMetaIndex": "node ./scripts/writeLabIconMetaIndex.mts",
@@ -24,7 +24,8 @@
"prebuild:api": "nitropack prepare",
"build:api": "NODE_OPTIONS=--max-old-space-size=8192 nitropack build",
"build": "pnpm run /^prebuild:.*/ && pnpm run build:api && pnpm run build:docs && pnpm postbuild:vercelJson",
"preview": "node .output/server/index.mjs"
"preview": "node .output/server/index.mjs",
"typecheck": "vue-tsc --noEmit"
},
"author": "Eric Fennis",
"license": "ISC",
@@ -47,9 +48,11 @@
"svg-path-commander": "^2.3.3",
"svg-pathdata": "^9.0.0",
"svgson": "^5.3.1",
"typescript": "^6.0.3",
"vitepress": "^1.6.4",
"vitepress-plugin-group-icons": "^1.7.6",
"vitepress-plugin-llms": "^1.13.5"
"vitepress-plugin-llms": "^1.13.5",
"vue-tsc": "^3.3.11"
},
"dependencies": {
"@floating-ui/vue": "^2.0.1",
@@ -68,6 +71,6 @@
"react": "18.3.1",
"react-dom": "18.3.1",
"sandpack-vue3": "3.1.12",
"vue": "^3.5.42"
"vue": "^3.5.43"
}
}
+3 -2
View File
@@ -27,12 +27,13 @@ import iconNode from '../iconNodes/${iconName}.node.json' with { type: 'json' };
import metaData from '../../../../icons/${iconName}.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';
const { tags, categories, contributors, aliases, deprecated, deprecationReason, toBeRemovedInVersion } = metaData;
const { tags, categories, contributors, aliases, deprecated, deprecationReason, toBeRemovedInVersion }: IconMetaData = metaData;
const iconDetails = {
name: '${iconName}',
iconNode,
iconNode: iconNode as IconNode,
contributors,
tags,
categories,
+10 -3
View File
@@ -20,6 +20,7 @@ if (!fs.existsSync(iconNodesDirectory)) {
const iconIndexFile = path.resolve(iconNodesDirectory, `index.ts`);
const iconIndexFileImports = [];
const iconIndexFileNodes = [];
const iconIndexFileExports = [];
const iconIndexFileDefaultExports = [];
@@ -32,7 +33,10 @@ const writeIconFiles = Object.entries(icons).map(async ([iconName, { children }]
await fs.promises.writeFile(location, output, 'utf-8');
iconIndexFileImports.push(
`import ${toCamelCase(iconName)}Node from './${iconName}.node.json' with { type: "json" };`,
`import ${toCamelCase(iconName)}Json from './${iconName}.node.json' with { type: "json" };`,
);
iconIndexFileNodes.push(
`const ${toCamelCase(iconName)}Node = ${toCamelCase(iconName)}Json as IconNode;`,
);
iconIndexFileExports.push(` ${toCamelCase(iconName)}Node as ${toCamelCase(iconName)},`);
iconIndexFileDefaultExports.push(` '${iconName}': ${toCamelCase(iconName)}Node,`);
@@ -43,16 +47,19 @@ try {
await fs.promises.writeFile(
iconIndexFile,
`\
import type { IconNode } from '../../theme/types';
${iconIndexFileImports.join('\n')}
${iconIndexFileNodes.join('\n')}
export {
${iconIndexFileExports.join('\n')}
}
export default {
${iconIndexFileDefaultExports.join('\n')}
}
`,
} satisfies Record<string, IconNode>;
`,
'utf-8',
);
+10 -3
View File
@@ -20,6 +20,7 @@ if (!fs.existsSync(iconNodesDirectory)) {
const iconIndexFile = path.resolve(iconNodesDirectory, `index.ts`);
const iconIndexFileImports: string[] = [];
const iconIndexFileNodes: string[] = [];
const iconIndexFileExports: string[] = [];
const iconIndexFileDefaultExports: string[] = [];
@@ -32,7 +33,10 @@ const writeIconFiles = Object.entries(icons).map(async ([iconName, { children }]
await fs.promises.writeFile(location, output, 'utf-8');
iconIndexFileImports.push(
`import ${toCamelCase(iconName)}Node from './${iconName}.node.json' with { type: "json" };`,
`import ${toCamelCase(iconName)}Json from './${iconName}.node.json' with { type: "json" };`,
);
iconIndexFileNodes.push(
`const ${toCamelCase(iconName)}Node = ${toCamelCase(iconName)}Json as IconNode;`,
);
iconIndexFileExports.push(` ${toCamelCase(iconName)}Node as ${toCamelCase(iconName)},`);
iconIndexFileDefaultExports.push(` '${iconName}': ${toCamelCase(iconName)}Node,`);
@@ -43,16 +47,19 @@ try {
await fs.promises.writeFile(
iconIndexFile,
`\
import type { IconNode } from '../../../theme/types';
${iconIndexFileImports.join('\n')}
${iconIndexFileNodes.join('\n')}
export {
${iconIndexFileExports.join('\n')}
}
export default {
${iconIndexFileDefaultExports.join('\n')}
}
`,
} satisfies Record<string, IconNode>;
`,
'utf-8',
);
+26 -5
View File
@@ -40,7 +40,21 @@ const fetchAllReleases = async () => {
);
};
const getIconNamesAtTag = async (tag: string) => {
// --full-tree resolves `icons/` from the repo root instead of the cwd (docs/)
const files = await git.raw(['ls-tree', '--full-tree', '--name-only', tag, 'icons/']);
return new Set(
files
.split('\n')
.filter((file) => file.endsWith('.svg'))
.map((file) => path.basename(file, '.svg')),
);
};
const tags = await fetchAllReleases();
const forkIconNames = await getIconNamesAtTag(tags[0]);
const releasedIconNames = await getIconNamesAtTag(tags[tags.length - 1]);
const comparisonsPromises = tags.map(async (tag, index) => {
const previousTag = tags[index - 1];
@@ -150,19 +164,26 @@ try {
const iconName = path.basename(iconJsonFile, '.json');
const metaDir = path.resolve(releaseMetaDataDirectory, `${iconName}.json`);
if (!(iconName in newReleaseMetaData)) {
const metaData = await fs.promises.readFile(path.join(ICONS_DIR, iconJsonFile), 'utf-8');
const iconMetaData = JSON.parse(metaData);
const aliases = iconMetaData.aliases ?? [];
const fromFork = [iconName, ...aliases.map((alias) => alias.name)].some((name) =>
forkIconNames.has(name),
);
const awaitingRelease = !releasedIconNames.has(iconName);
if (!(iconName in newReleaseMetaData) && !fromFork && !awaitingRelease) {
console.error(`Could not find release metadata for icon '${iconName}'.`);
}
const contents = {
fromFork,
awaitingRelease,
...defaultReleaseMetaData,
...(newReleaseMetaData[iconName] ?? {}),
};
const metaData = await fs.promises.readFile(path.join(ICONS_DIR, iconJsonFile), 'utf-8');
const iconMetaData = JSON.parse(metaData);
const aliases = iconMetaData.aliases ?? [];
if (aliases.length) {
aliases.forEach((alias) => {
if (!(alias.name in newReleaseMetaData)) {
+1
View File
@@ -7,6 +7,7 @@
"allowImportingTsExtensions": true,
"allowSyntheticDefaultImports": true,
"noEmit": true,
"skipLibCheck": true,
"paths": {
"~/.vitepress/*": ["./.vitepress/*"]
}
+31
View File
@@ -0,0 +1,31 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"TanvirMahin24",
"karsa-mistmere"
],
"use-cases": [
"Showing product prices in BDT",
"Selecting BDT in multi-currency interfaces",
"Showing BDT exchange rates in currency converters",
"Indicating BDT amounts in invoices and payments"
],
"tags": [
"currency",
"money",
"payment",
"bdt",
"৳",
"currencysymbol",
"cash",
"banknote",
"price",
"remittance",
"ecommerce"
],
"categories": [
"finance",
"shopping",
"travel"
]
}
@@ -9,8 +9,6 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
<rect width="20" height="14" x="2" y="7" rx="2" />
<path d="M15 14H9" />
<path d="M12 11v6" />
<path d="M6 5a2 2 0 0 1 4 0v12a4 4 0 0 0 8 0 2 2 0 0 0-4 0" />
<path d="M6 9h12" />
</svg>

Before

Width:  |  Height:  |  Size: 363 B

After

Width:  |  Height:  |  Size: 296 B

+39
View File
@@ -0,0 +1,39 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"colebemis",
"csandman",
"ericfennis",
"tylerkade",
"jguddas"
],
"use-cases": [
"Indicating you're adding luggage",
"adding a new business or company profile",
"creating or saving a professional portfolio",
"adding a job or role to a user's profile",
"indicating add to briefcase or save for later in a product/service listing"
],
"tags": [
"work",
"bag",
"baggage",
"folder",
"new",
"add",
"create",
"increase",
"briefcase",
"portfolio",
"business",
"career",
"employment",
"professional",
"plus"
],
"categories": [
"transportation",
"files",
"account"
]
}
+16
View File
@@ -0,0 +1,16 @@
<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="M13.354 20H4a2 2 0 01-2-2V8a2 2 0 012-2h16a2 2 0 012 2v3.354" />
<path d="M16 11.354V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v16" />
<path d="M16 17h6" />
<path d="M19 14v6" />
</svg>

After

Width:  |  Height:  |  Size: 392 B

+2 -1
View File
@@ -3,7 +3,8 @@
"contributors": [
"rrod497",
"jamiemlaw",
"karsa-mistmere"
"karsa-mistmere",
"jguddas"
],
"use-cases": [],
"tags": [
+4 -4
View File
@@ -9,8 +9,8 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M10 13a3 3 0 0 1-2.121-5.121" />
<path d="M15.606 14.204c-3.5 1.5-5.899 4.503-8.899 7.503A1 1 0 0 1 6 22c-2 0-4-2-4-4a1 1 0 0 1 .293-.707c1.911-1.911 3.823-3.578 5.347-5.441" />
<path d="M16.573 14.737A4 4 0 0 1 14 11" />
<path d="M7.14 10.907a4 4 0 1 1 2.756-7.43A4 4 0 0 1 16.7 4.48a2 2 0 0 1 2.82 2.82 4 4 0 0 1 1.002 6.805A4 4 0 1 1 13 16" />
<path d="M10 13a3 3 0 01-2.121-5.121" />
<path d="M15.606 14.204c-3.5 1.5-5.899 4.503-8.899 7.503A1 1 0 016 22c-2 0-4-2-4-4a1 1 0 01.293-.707c1.911-1.911 3.823-3.578 5.347-5.441" />
<path d="M16.573 14.737A4 4 0 0114 11" />
<path d="M7.14 10.907a4 4 0 112.756-7.43A4 4 0 0116.7 4.48a2 2 0 012.82 2.82 4 4 0 011.002 6.805 4 4 0 11-7.51 1.59" />
</svg>

Before

Width:  |  Height:  |  Size: 572 B

After

Width:  |  Height:  |  Size: 560 B

+1 -1
View File
@@ -12,7 +12,7 @@
<path d="m13 21 3-3-3-3" />
<path d="M16 2v3" />
<path d="m19 21 3-3-3-3" />
<path d="M21 11.5V5.05a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2L9 21" />
<path d="M21 11.5V5a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h4" />
<path d="M3 9h18" />
<path d="M8 2v3" />
</svg>

Before

Width:  |  Height:  |  Size: 413 B

After

Width:  |  Height:  |  Size: 407 B

+1 -1
View File
@@ -12,5 +12,5 @@
<path d="M12 14v4" />
<path d="M14.172 2a2 2 0 0 1 1.414.586l3.828 3.828A2 2 0 0 1 20 7.828V20a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2z" />
<path d="M8 14h8" />
<rect x="8" y="10" width="8" height="8" rx="1" />
<rect x="8" y="10" width="8" height="8" rx="2" />
</svg>

Before

Width:  |  Height:  |  Size: 433 B

After

Width:  |  Height:  |  Size: 433 B

+30
View File
@@ -0,0 +1,30 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"rrod497"
],
"use-cases": [
"Labeling alto clef notation in sheet music editors",
"Representing viola parts in music composition or practice apps",
"Indicating C clef selection in notation software",
"Supporting clef choices in MIDI editors, instrument emulators, or music education tools"
],
"tags": [
"do",
"c",
"music",
"note",
"key",
"soprano",
"mezzo",
"alto",
"staff",
"pitch",
"baritone",
"major",
"scale"
],
"categories": [
"multimedia"
]
}
+15
View File
@@ -0,0 +1,15 @@
<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="M10 4v16" />
<path d="M14 4.764a3 3 0 1 1-.152 4.327A4 4 0 0 1 10 12a4 4 0 0 1 3.848 2.909A3 3 0 1 1 14 19.236" />
<path d="M6 4v16" />
</svg>

After

Width:  |  Height:  |  Size: 359 B

+29
View File
@@ -0,0 +1,29 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"rrod497"
],
"use-cases": [
"Labeling bass clef notation in sheet music editors",
"Representing bass, cello, trombone, tuba, or lower-pitched instrument parts",
"Indicating left-hand piano notation in music learning apps",
"Supporting clef choices in MIDI editors, instrument emulators, or music education tools"
],
"tags": [
"fa",
"f",
"music",
"note",
"key",
"baritone",
"staff",
"pitch",
"bass",
"subbass",
"major",
"scale"
],
"categories": [
"multimedia"
]
}
+16
View File
@@ -0,0 +1,16 @@
<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="M19 11h.01" />
<path d="M19 6h.01" />
<path d="M5 8c0-4 4-4 4-4 6 0 6 6 6 6 0 7-10 11-10 11" />
<circle cx="7" cy="8" r="2" />
</svg>

After

Width:  |  Height:  |  Size: 352 B

+29
View File
@@ -0,0 +1,29 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"rrod497"
],
"use-cases": [
"Representing music, songs, or audio content with a widely recognized symbol",
"Labeling sheet music, notation tools, or music education resources",
"Indicating melody, vocals, or higher-pitched instrument parts",
"Representing music apps, playlists, concerts, or performance features"
],
"tags": [
"sol",
"g",
"music",
"note",
"key",
"treble",
"staff",
"pitch",
"violin",
"solfege",
"major",
"scale"
],
"categories": [
"multimedia"
]
}
+13
View File
@@ -0,0 +1,13 @@
<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="M10.586 21.414a2 2 0 0 0 3.378-1.791L11.036 4.377a2 2 0 1 1 3.378 1.037C12.414 7.414 7 8 7 13a5 5 0 0 0 5 5 5 4 0 0 0 5-4 3 3 0 0 0-3-3 3 2 0 0 0-3 2" />
</svg>

After

Width:  |  Height:  |  Size: 373 B

+27
View File
@@ -0,0 +1,27 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"AnnaSasDev",
"briz123",
"ajokt123",
"rrod497",
"karsa-mistmere"
],
"use-cases": [
"Representing cupcakes in bakery product catalogs",
"Filtering dessert options in food ordering apps",
"Marking treats for parties or celebrations"
],
"tags": [
"cake",
"dessert",
"food",
"celebration",
"sweet",
"baking",
"cooking"
],
"categories": [
"food-beverage"
]
}
+19
View File
@@ -0,0 +1,19 @@
<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 22v-9" />
<path d="M14 4h1a3 3 0 013 3l-.004.125A4 4 0 0121 11v2" />
<path d="m15.5 22 1.5-9" />
<path d="M21 13a1 1 0 01.919 1.394l-2.74 6.394A2 2 0 0117.34 22H6.659a2 2 0 01-1.838-1.212l-2.74-6.394A1 1 0 013 13z" />
<path d="M3 13v-2a4 4 0 013.003-3.875L6 7a3 3 0 013-3h1" />
<path d="M8.5 22 7 13" />
<circle cx="12" cy="4" r="2" />
</svg>

After

Width:  |  Height:  |  Size: 571 B

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