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
182 changed files with 3751 additions and 2383 deletions
@@ -298,11 +298,11 @@ Link text should describe the destination or concept.
**Do:**
> See the [Icon Design Guide](./design-principles) for exact dimensions and spacing requirements.
> See the [Icon Design Specification](./specification) for exact dimensions and spacing requirements.
**Don't:**
> For more information, [click here](./design-principles).
> For more information, [click here](./specification).
Avoid generic link text such as:
+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];
+5
View File
@@ -60,6 +60,11 @@ export const resourcesSidebar = [
link: '/contribute/icons/design-principles',
desc: 'Learn the visual principles that shape Lucide icons with side-by-side examples.',
},
{
text: 'Design specification',
link: '/contribute/icons/specification',
desc: 'Review the technical requirements every Lucide icon must meet.',
},
{
text: 'Naming conventions',
link: '/contribute/icons/naming-conventions',
@@ -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
+23 -58
View File
@@ -8,9 +8,7 @@ Lucide is a consistent icon set, not a collection of unrelated icons. Every icon
This guide explains the visual rules behind Lucide icons and how to use them when designing new icons.
::: info Requirement levels
This guide uses normative terms such as **must**, **should**, and **may** as defined in [RFC 2119](https://www.rfc-editor.org/info/rfc2119/).
:::
For the full rule list, see the [icon design specification](./specification)
## 1. Use a 24 × 24-pixel canvas
@@ -96,9 +94,7 @@ Don't use **inside or outside** strokes. They change how line joins appear, crea
## 7. Use consistent corner radii
Corner radius depends on the size and angle of the element.
### 7.1. Sharp corners **should** be rounded unless the geometry of the icon requires otherwise
Almost every sharp corner **should** be rounded.
:::: example
::: do ../../images/border-radius-correct.svg?raw=true
@@ -109,44 +105,29 @@ Don't use **sharp corners**.
:::
::::
### 7.2. Elements at least 8 pixels wide or tall **should** use a 2-pixel corner radius for 90° corners
Corner radius depends on the size of the element and the angle. For 90° angles:
:::: example
::: do ../../images/2px-border-radius.svg?raw=true
Use a **2-pixel corner radius** for shapes that are at least 8 pixels wide or tall.
:::
::::
### 7.3. Elements smaller than 8 pixels **should** use a 1-pixel corner radius for 90° corners
Use a **1-pixel corner radius** for shapes smaller than 8 pixels.
:::: example
::: do ../../images/1px-border-radius.svg?raw=true
Use a **1-pixel corner radius** for shapes smaller than 8 pixels.
:::
::::
### 7.4. Diagonal lines meeting at a 90° angle **should** use a radius that preserves pixel-grid alignment
Diagonal lines that meet at a right angle usually need a **2.41 pixel** radius (1+√2) to stay aligned to the grid.
There are exceptions:
:::: example
::: do ../../images/border-radius-90deg.svg?raw=true
Diagonal lines that meet at a right angle usually need a **2.41 pixel** radius (1+√2) to stay aligned to the grid.
:::
::::
### 7.5. Other acute corners **should** use an appropriate amount of rounding based on their geometry
Even **very sharp corners** usually need a small amount of rounding. The exact amount depends on the shape.
:::: example
::: do ../../images/border-radius-arbitrary.svg?raw=true
Even **very sharp corners** usually need a small amount of rounding. The exact amount depends on the shape.
:::
::::
### 7.6. Corners where lines cross **must not** be rounded
Corners where multiple lines meet **must not** be rounded. Keep corners sharp where multiple lines meet so the icon scales well.
Avoid rounding corners **where multiple lines meet**.
:::: example
::: do ../../images/border-radius-line-join.svg?raw=true
@@ -157,15 +138,9 @@ Don't round corners where more than two lines meet. This can make the icon scale
:::
::::
### 7.7. Non-outlined arrows **must not** be rounded
Non-outlined arrows **must not** be rounded.
If the arrow is an outline shape, it **should** be rounded.
## 8. Keep at least 2 pixels between elements
### 8.1. Distinct elements **must** have at least 2 pixels of visual spacing between them
Distinct elements **must** have at least 2 pixels of visual spacing between them.
:::: example
::: do ../../images/2px-element-spacing.svg?raw=true
@@ -176,9 +151,7 @@ Don't use gaps smaller than **2 pixels** between distinct elements.
:::
::::
### 8.2. Inner gaps **should** be at least 2 pixels wide
The 2-pixel gap rule also applies inside shapes. Most inner gaps **should** be at least 2 pixels wide.
This also applies inside shapes. Most inner gaps **should** be at least 2 pixels wide.
:::: example
::: do ../../images/2px-inner-spacing.svg?raw=true
@@ -200,9 +173,7 @@ If the circle **overlaps** the shape, the gap should be **wider**.
:::
::::
### 8.3. Visual spacing **should** remain consistent when elements connect or intersect
Spacing **should** remain visually consistent where elements connect or intersect. Elements **must not** connect without keeping at least 2 pixels of spacing, and **should not** terminate with an abrupt cut.
The 2 pixel spacing also applies when elements visually connect or intersect.
:::: example
::: dont ../../images/2px-element-spacing-connected.svg?raw=true
@@ -213,13 +184,9 @@ Don't use **abrupt cuts** where one element meets another.
:::
::::
### 8.4. Off-variants **may** omit the 2-pixel gap rule
The `-off` variants have their own design language and **may** omit the 2-pixel gap rule.
## 9. Aim for consistent visual weight
Icons **should** have a similar visual weight when viewed alongside each other.
Icons should have a similar visual weight when viewed alongside each other.
:::: example
@@ -241,7 +208,7 @@ Place your icon next to `circle` or `square`, then blur both icons. Your icon sh
## 10. Balance icons visually
Icons **should** be visually centered within the canvas.
Icons should look centered and balanced within the canvas.
:::: example
::: do ../../images/visually-centered-do.svg?raw=true
@@ -254,7 +221,7 @@ This icon feels shifted toward the top left.
:::: example
::: do ../../images/visually-centered-symmetrical-do.svg?raw=true
Symmetrical icons **should** remain geometrically centered.
Symmetrical icons should remain geometrically centered.
:::
::: dont ../../images/visually-centered-symmetrical-dont.svg?raw=true
Don't place symmetrical icons off-center for no good reason.
@@ -262,14 +229,14 @@ Don't place symmetrical icons off-center for no good reason.
::::
::: tip
Geometric centering does not always look centered. Asymmetrical icons **may** be moved slightly off center so they look visually centered.
Geometric centering does not always look centered. Asymmetrical shapes may need a small adjustment.
Compare your icon with `circle` or `square` side by side and stacked vertically. If it looks shifted, adjust it.
:::
## 11. Keep density low
Icons **should** have a level of detail similar to other Lucide icons.
Lucide icons should have a similar level of detail.
:::: example
::: do ../../images/density-optimal.svg?raw=true
@@ -290,7 +257,7 @@ View the icon at its intended size and blur it. Areas that turn dark may have to
## 12. Use smooth, simple curves
Curves **should** be smooth and as simple as the shape allows. Arcs and quadratic Bézier curves **should** be preferred where possible. Cubic Bézier curves **may** be used where necessary.
Curves should be smooth and as simple as the shape allows.
:::: example
::: do ../../images/curvature-smooth.svg?raw=true
@@ -309,7 +276,7 @@ Avoid extra control points. They make shapes harder to edit and can create small
## 13. Design for the pixel grid
Lucide icons **should** look sharp at small sizes, including on low-density displays. Coordinates and centers of arcs and other geometric elements **should** align to the pixel grid where possible.
Lucide icons should look sharp at small sizes, including on low-density displays.
:::: example
::: do ../../images/pixel-perfection-ideal.svg?raw=true
@@ -339,14 +306,12 @@ Avoid placing diagonal lines off-grid. They will appear blurry and optically wid
::::
::: tip
Pixel alignment **should** support the icon. Off-grid geometry **may** be used when required for visual balance, recognizable geometry, or smooth curvature. Pixel alignment **should not** take precedence over the visual quality of the icon.
Pixel alignment should support the icon. Do not let it harm shape recognition, smooth curves, or visual balance.
:::
## 14. Reuse established shapes
### 14.1. Variants **should** preserve base icon geometry
When creating a variant, the base icon geometry, placement, and orientation **should** be preserved unless the new icon requires a change.
When creating a variant, keep the base icon geometry unless the new idea needs a change.
:::: example
::: do ../../images/established-shapes-base-do.svg?raw=true
@@ -357,7 +322,7 @@ Don't change the **geometry, placement, or orientation** of base shapes.
:::
::::
### 14.2. Related icons **should** share shapes and visual patterns when possible
Related icons should share shapes and visual patterns when possible.
:::: example
::: do ../../images/established-shapes-sub-do.svg?raw=true
@@ -368,7 +333,7 @@ Don't redesign **existing elements**.
:::
::::
### 14.3. Common modifiers and elements **should** look consistent across the library
Common modifiers and elements should look consistent across the library.
:::: example
::: do ../../images/established-shapes-consistency-do.svg?raw=true
@@ -382,7 +347,7 @@ Don't use **irregular placement or size** compared to existing icons.
::: tip
Preserving the base shape makes related icons work better when users switch between them.
Existing geometry **may** be modified when necessary to satisfy other Lucide design language rules. Consistency with an existing icon **should not** override clarity, balance, or readability.
Consistency with an existing icon should not override clarity, balance, or readability. Adjust the shape when needed.
:::
## Summary
+95
View File
@@ -0,0 +1,95 @@
---
description: Rules Lucide icons must follow.
---
# Icon design specification
This page lists the rules Lucide icons must follow.
For examples and guidance, see the [Lucide design language](./design-principles).
## Requirement levels
These terms show how strictly each rule applies:
- **Must** and **must not** mark required rules.
- **Should** and **should not** mark rules that can flex when needed to make a better icon.
Any exception should be intentional and fit the [Lucide design language](./design-principles).
## Canvas
1. Icons **must** use a 24 × 24-pixel canvas.
2. The canvas **must** be square.
3. Icon strokes **must** remain at least 1 pixel from the edge of the canvas.
## Strokes
1. Strokes **must** be 2 pixels wide.
2. Strokes **must** use round line joins.
3. Open paths **must** use round line caps.
4. Strokes **must** be centered on their paths.
## Corners
1. Sharp corners **should** be rounded unless the geometry of the icon requires otherwise.
2. Elements at least 8 pixels wide or tall **should** use a 2-pixel corner radius for 90° corners.
3. Elements smaller than 8 pixels **should** use a 1-pixel corner radius for 90° corners.
4. Diagonal lines meeting at a 90° angle **should** use a radius that preserves pixel-grid alignment. This radius is typically approximately 2.41 pixels.
5. Acute corners **should** use an appropriate amount of rounding based on their geometry.
6. Corners where multiple lines meet **should** remain sharp when rounding would cause the icon to render inconsistently at different sizes.
## Spacing
1. Distinct elements **must** have at least 2 pixels of visual separation.
2. Gaps between distinct elements **must not** be smaller than 2 pixels.
3. Inner gaps **should** be at least 2 pixels wide.
4. Spacing **should** remain visually consistent where elements connect or intersect.
5. Elements **should not** terminate with an abrupt cut where another element visually continues or intersects them.
## Visual weight
1. Icons **should** have a visual weight similar to the `circle` and `square` icons.
2. Icons **should not** appear substantially larger, smaller, heavier, or lighter than comparable Lucide icons.
## Alignment and balance
1. Icons **should** be visually centered within the canvas.
2. Asymmetrical icons **may** be moved slightly off center so they look visually centered.
3. Symmetrical icons **should** be geometrically centered.
## Visual density
1. Icons **should** have a level of detail similar to other Lucide icons.
2. Details **should** be simplified when they cause an icon to appear substantially denser than comparable icons.
3. Details that are not necessary for recognition **should** be omitted.
## Curves
1. Continuous curves **should** have smooth curvature.
2. Curves **should not** contain unintended or abrupt changes in curvature.
3. Arcs and quadratic Bézier curves **should** be preferred where they can accurately represent the intended geometry.
4. Cubic Bézier curves **may** be used where necessary.
5. Control points of adjoining cubic Bézier curves **should** be aligned where required to maintain continuous curvature.
6. Curves **should not** contain unnecessary control points.
## Pixel alignment
1. Coordinates **should** align to the pixel grid where possible.
2. Centers of arcs and other geometric elements **should** align to the pixel grid where possible.
3. Off-grid geometry **may** be used when required for visual balance, recognizable geometry, or smooth curvature.
4. Pixel alignment **should not** take precedence over the visual quality of the icon.
## Shared geometry
1. Variants of an existing icon **should** preserve the geometry, placement, and orientation of the base icon.
2. Elements that already exist in other Lucide icons **should** reuse established geometry where applicable.
3. Related icons **should** use consistent representations of shared elements.
4. Common modifiers and add-ons **should** use consistent geometry, size, and placement across related icons.
5. Existing geometry **may** be modified when necessary to satisfy other Lucide design language rules.
## Priority of requirements
When two non-required rules conflict, choose visual clarity and consistency with the Lucide design language.
Rules marked **must** or **must not** can only be broken when the project defines an explicit exception.
+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

+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

+25
View File
@@ -0,0 +1,25 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"karsa-mistmere"
],
"use-cases": [
"Indicating package delivery to a home or residence",
"Representing packages left at a recipient's door",
"Tracking doorstep or unattended deliveries",
"Selecting a delivery location or drop-off point"
],
"tags": [
"delivery",
"parcel",
"doorstep",
"shipping",
"drop-off",
"courier"
],
"categories": [
"home",
"food-beverage",
"transportation"
]
}
+17
View File
@@ -0,0 +1,17 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M18 13v3" />
<path d="M19 9V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16" />
<path d="M2 21h8" />
<path d="M9 12h.01" />
<rect x="14" y="13" width="8" height="8" rx="1" />
</svg>

After

Width:  |  Height:  |  Size: 387 B

+43
View File
@@ -0,0 +1,43 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"danielbayley",
"karsa-mistmere"
],
"use-cases": [
"Indicating water sources on maps",
"Controlling faucets in smart home apps",
"Representing kitchen or bathroom plumbing fixtures",
"Showing drinking water amenities in travel apps"
],
"tags": [
"tap",
"spigot",
"water",
"drinking",
"fresh",
"source",
"pipes",
"plumbing",
"plumber",
"sink",
"basin",
"kitchen",
"bathroom",
"washroom",
"fixture",
"valve",
"flow",
"drinkable",
"potable",
"amenities",
"utilities",
"hydration"
],
"categories": [
"food-beverage",
"home",
"navigation",
"travel"
]
}

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