Compare commits

..

1 Commits

Author SHA1 Message Date
Jakob Guddas
a8f578fa8b fix: fixed async migration issue in generate changed icons comment markup 2025-02-20 21:55:53 +01:00
1203 changed files with 9233 additions and 26125 deletions

View File

@@ -8,10 +8,10 @@ squircle
strikethrough
touchpad
ungroup
pilcrow
toc
# Brands
codepen
codesandbox
dribbble
x.com

View File

@@ -7,10 +7,8 @@ body:
value: |
Before submitting an icon request check if it has already been requested. If there is an open request, please add a 👍.
> [!CAUTION]
> New brand logos are **not** allowed, see our official statement: https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md.
>
> Existing brand icons are being phased out. Consider using https://simpleicons.org, which offers purpose-built SVGs that are also on a 24×24px grid.
**Important note**: No new brand logos are allowed, see https://github.com/lucide-icons/lucide/issues/670.
Existing brand icons will also be phased out. For brand icons, consider using https://simpleicons.org, which offers purpose-built SVGs that are also on a 24×24px grid.
- type: input
id: name
attributes:
@@ -43,9 +41,9 @@ body:
required: true
- label: I have searched existing icons to make sure it does not already exist and I didn't find any.
required: true
- label: I am not requesting a brand logo and the art is not protected by copyright, see more at https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md
- label: I am not requesting a brand logo and the art is not protected by copyright.
required: true
- label: I am not requesting an icon that includes religious, war/violence related, political imagery or hate symbols.
- label: I am not requesting an icon that includes religious, political imagery or hate symbols.
required: true
- label: I have provided appropriate use cases for the icon(s) requested.
required: true

View File

@@ -22,7 +22,6 @@ body:
- label: lucide-svelte
- label: lucide-vue
- label: lucide-vue-next
- label: lucide-astro
- label: Figma plugin
- label: source/main
- label: other/not relevant

View File

@@ -22,7 +22,6 @@ body:
- label: lucide-svelte
- label: lucide-vue
- label: lucide-vue-next
- label: lucide-astro
- label: Figma plugin
- label: all JS packages
- label: site

44
.github/actions/build-and-test.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: 'Build and Test'
description: 'Builds and test a package'
inputs:
name:
description: “Name of the package”
required: true
runs:
using: 'composite'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
- uses: pnpm/action-setup@v2
name: Install pnpm
id: pnpm-install
with:
version: 8
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --filter lucide-preact
- name: Build
run: pnpm --filter lucide-preact build
- name: Test
run: pnpm --filter lucide-preact test

41
.github/actions/check-icons.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: 'Check icons'
description: 'Cross-checks icon and category references in JSON descriptors'
inputs:
name:
description: “Name of the package”
required: true
runs:
using: 'composite'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
- uses: pnpm/action-setup@v2
name: Install pnpm
id: pnpm-install
with:
version: 8
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --filter .
- name: Check icons and categories
run: pnpm checkIcons

6
.github/labeler.yml vendored
View File

@@ -79,12 +79,6 @@
- any-glob-to-any-file:
- 'packages/lucide-solid/*'
# For changes in the lucide astro package
🚀 astro package:
- changed-files:
- any-glob-to-any-file:
- 'packages/astro/*'
# For changes in the lucide static package
🪨 static package:
- changed-files:

View File

@@ -1,18 +1,16 @@
<!-- Thank you for contributing! -->
<!--
PR Title Guidelines:
Please use the format: <type>(<scope>): <short description>
Example: feat(icons): added `camera` icon
Available types: fix, feat, perf, docs, style, refactor, test, chore, ci, build
Common scopes: icons, docs, studio, site, dev
-->
<!-- Insert `closes #issueNumber` here if merging this PR will resolve an existing issue -->
## Description
## What is the purpose of this pull request?
<!-- Please choose one of the following, and put an "x" next to it. -->
- [ ] New Icon
- [ ] Bug fix
- [ ] New Feature
- [ ] Documentation update
- [ ] Other:
### Description
<!-- Please insert your description here and provide info about the "what" this PR is contribution -->
### Icon use case <!-- ONLY for new icons, remove this part if not icon PR -->
@@ -25,12 +23,10 @@ Common scopes: icons, docs, studio, site, dev
### Concept <!-- ONLY for new icons -->
<!-- All of these requirements must be fulfilled. -->
<!-- IMPORTANT! Please read our official statement on brand logos in Lucide: -->
<!-- https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md -->
- [ ] I have provided valid use cases for each icon.
- [ ] I have [not added any brand or logo icon](https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md).
- [ ] I have not added any a brand or logo icon.
- [ ] I have not used any hate symbols.
- [ ] I have not included any religious, war/violence related or political imagery.
- [ ] I have not included any religious or political imagery.
### Author, credits & license<!-- ONLY for new icons. -->
<!-- Please choose one of the following, and put an "x" next to it. -->

View File

@@ -9,18 +9,18 @@ on:
jobs:
create-release:
if: github.repository == 'lucide-icons/lucide' && startsWith(github.event.head_commit.message, 'feat(icons)')
if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest
outputs:
VERSION: ${{ steps.new-version.outputs.NEW_VERSION }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -61,40 +61,8 @@ jobs:
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.new-version.outputs.NEW_VERSION }}
name: Version ${{ steps.new-version.outputs.NEW_VERSION }}
generate_release_notes: true
test-semantic-release:
if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Semantic Release
id: semantic
uses: cycjimmy/semantic-release-action@v4
with:
tag_format: ${version}
branches: |
['new-release-workflow']
extends: |
semantic-release-monorepo
extra_plugins: |
@semantic-release/github
@semantic-release/git
@semantic-release/release-notes-generator
conventional-changelog-conventionalcommits
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Log output
if: steps.semantic.outputs.new_release_published == 'true'
run: |
echo ${{ steps.semantic.outputs.new_release_version }}
echo ${{ steps.semantic.outputs.new_release_major_version }}
echo ${{ steps.semantic.outputs.new_release_minor_version }}
echo ${{ steps.semantic.outputs.new_release_patch_version }}
name: New icons ${{ steps.new-version.outputs.NEW_VERSION }}
body: ${{ steps.change-log.outputs.CHANGE_LOG }}
start-release:
if: github.repository == 'lucide-icons/lucide'

View File

@@ -11,12 +11,12 @@ jobs:
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Check for blocked phrases in issue title
run: |
ISSUE_TITLE=$(jq -r '.issue.title' "$GITHUB_EVENT_PATH")
BLOCKED_PHRASES=("twitter" "whatsapp" "logo" "google" "tiktok" "facebook" "slack" "discord" "bluesky" "spotify" "behance" "pix" "x.com" "telegram")
BLOCKED_PHRASES=("twitter" "whatsapp" "logo" "google" "tiktok" "facebook" "slack" "discord" "bluesky")
# Check title and body for blocked phrases
for PHRASE in "${BLOCKED_PHRASES[@]}"

View File

@@ -1,58 +0,0 @@
name: Icon preview comment
on:
workflow_run:
workflows: ['Pull request icon preview']
types:
- completed
jobs:
upload:
runs-on: ubuntu-latest
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
steps:
- name: 'Download artifact'
uses: actions/github-script@v7
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == "pr_number"
})[0];
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
const fs = require('fs');
fs.writeFileSync('${{github.workspace}}/pr_number.zip', Buffer.from(download.data));
- name: 'Unzip artifact'
run: unzip pr_number.zip
- name: 'Get PR number'
run: echo "number=$(cat NR)" >> $GITHUB_OUTPUT
id: pr-number
- name: Find Comment
uses: peter-evans/find-comment@v2
id: pr-comment
with:
issue-number: ${{ steps.pr-number.outputs.number }}
comment-author: 'github-actions[bot]'
body-includes: Added or changed icons
- name: Create or update comment
uses: peter-evans/create-or-update-comment@v3
with:
comment-id: ${{ steps.pr-comment.outputs.comment-id }}
issue-number: ${{ steps.pr-number.outputs.number }}
body-path: comment-markup.md
edit-mode: replace

View File

@@ -9,5 +9,4 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/labeler@v5

View File

@@ -1,32 +0,0 @@
name: Linting PR
on:
pull_request:
types:
- opened
- edited
- reopened
jobs:
lint-pr-title:
name: PR Title Lint
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
fix
feat
perf
refactor
test
style
docs
ci
build
chore
requireScope: true
ignoreLabels: |
bot

View File

@@ -1,39 +0,0 @@
name: Linting Icons
on:
pull_request:
paths:
- 'icons/*'
jobs:
lint-filenames:
name: Lint Filenames
if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: 'package.json'
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v46
with:
files: icons/*
- name: Generate annotations
run: node ./scripts/lintFilenames.mts
env:
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
lint-aliases:
name: Check Uniqueness of Aliases
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Check Uniqueness of Aliases
run: "! cat <(printf \"%s\\n\" icons/*.json | while read -r name; do basename \"$name\" .json; done) <(jq -cr 'select(.aliases) | .aliases[] | if type==\"string\" then . else .name end' icons/*.json) | sort | uniq -c | grep -ve '^\\s*1 '"

View File

@@ -1,20 +1,20 @@
name: Linting PR
name: Linting
on:
pull_request:
paths-ignore:
- icons/*.svg
branches:
- '**'
jobs:
lint-code:
linting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install Dependencies
run: pnpm install --frozen-lockfile

View File

@@ -12,11 +12,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -27,12 +27,12 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3.8.1
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile

View File

@@ -3,6 +3,7 @@ name: Lucide font checks
on:
pull_request:
paths:
- icons/**
- tools/build-font/**
- pnpm-lock.yaml
@@ -11,11 +12,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -27,7 +28,7 @@ jobs:
run: pnpm build:font
- name: 'Upload to Artifacts'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: lucide-font
path: lucide-font

View File

@@ -14,11 +14,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile

View File

@@ -10,31 +10,15 @@ on:
- pnpm-lock.yaml
jobs:
build:
lucide-react-native:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter lucide-react-native build
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile

View File

@@ -15,11 +15,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -30,12 +30,12 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3.8.1
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile

View File

@@ -10,12 +10,12 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3.8.1
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile

View File

@@ -14,11 +14,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -29,12 +29,12 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3.8.1
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile

View File

@@ -12,11 +12,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile

View File

@@ -1,43 +0,0 @@
name: Lucide Svelte 5 checks
on:
pull_request:
paths:
- packages/svelte/**
- packages/shared/**
- tools/build-icons/**
- tools/rollup-plugins/**
- pnpm-lock.yaml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter @lucide/svelte build
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test
run: pnpm --filter @lucide/svelte test

View File

@@ -14,11 +14,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -29,12 +29,12 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3.8.1
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile

View File

@@ -14,11 +14,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -29,12 +29,12 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3.8.1
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile

View File

@@ -1,11 +1,12 @@
name: Lucide Astro Checks
name: Lucide Vue checks
on:
pull_request:
paths:
- packages/astro/**
- packages/lucide-vue/**
- packages/shared/**
- tools/build-icons/**
- tools/rollup-plugins/**
- pnpm-lock.yaml
jobs:
@@ -16,14 +17,14 @@ jobs:
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version-file: 'package.json'
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter @lucide/astro build
run: pnpm --filter lucide-vue build
test:
runs-on: ubuntu-latest
@@ -32,11 +33,11 @@ jobs:
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3.8.1
with:
node-version-file: 'package.json'
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test
run: pnpm --filter @lucide/astro test
run: pnpm --filter lucide-vue test

View File

@@ -13,11 +13,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -28,12 +28,12 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3.8.1
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile

View File

@@ -1,46 +0,0 @@
name: Pull request icon preview
on:
pull_request:
paths:
- 'icons/*.svg'
jobs:
generate-changed-icons-comment:
name: Generate Changed Icons Comment
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: 'package.json'
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v46
with:
files: icons/*.svg
- uses: actions/setup-node@v4
- name: Install svgson for code preview (safer and faster than installing all deps)
run: npm install svgson@5.3.1 --force
- name: Save PR number
run: |
mkdir -p ./pr
echo ${{ github.event.number }} > ./pr/NR
- name: Generate comment markup
run: node ./scripts/generateChangedIconsCommentMarkup.mts >> ./pr/comment-markup.md
id: comment-markup
env:
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
- uses: actions/upload-artifact@v4
with:
name: pr_number
path: pr/

View File

@@ -1,37 +0,0 @@
name: Pull request tags suggestions
on:
pull_request_target:
paths:
- 'icons/*.json'
jobs:
pull-request-tags-suggestions:
name: Pull Request Tags Suggestions
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
# We checkout the main branch of main repository for security reasons.
# This is to prevent the workflow from running on a forked repository.
- uses: actions/checkout@v4
with:
repository: lucide-icons/lucide
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Generate comment markup
run: node ./scripts/suggestTags.mts
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
PULL_REQUEST_NUMBER: ${{ github.event.number }}
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

123
.github/workflows/pull-request.yml vendored Normal file
View File

@@ -0,0 +1,123 @@
name: Add Changed Icons comment
on:
pull_request_target:
paths:
- 'icons/*'
branches:
- main
- fix-icon-preview
jobs:
lint-filenames:
if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: refs/pull/${{ github.event.pull_request.number }}/merge
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v41
with:
files: icons/*
- name: Generate annotations
run: node ./scripts/lintFilenames.mjs
env:
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
lint-contributors:
if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: refs/pull/${{ github.event.pull_request.number }}/merge
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v41
with:
files: icons/*
- uses: actions/setup-node@v4
- name: Install simple-git (safer and faster than installing all deps)
run: npm install simple-git
- name: Generate annotations
run: node ./scripts/updateContributors.mjs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FETCH_DEPTH: ${{ github.event.pull_request.commits }}
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
- name: Generate annotations
env:
ANNOTATION_SEVERITY: notice
ANNOTATION_TITLE: Contributors have changed!
ANNOTATION_DESCRIPTION: Don't add people who have only performed automatic optimizations.
run: |
git diff --unified=0 -- icons/*.json | # diff icon metadata (unified=0 gives the correct chunk line number)
perl -ne '/^(\+|- |@)/ && print' | # get chunks (lines that start with "+++", "@@", "+ ", "- ")
perl -pe 's/\n/%0A/' | # url encode line breaks (\n -> %0A)
perl -pe 's/%0A(\+\+\+ b\/)/\n\1/g' | # split chunks(one chunk per line)
perl -pe "s/\+\+\+ b\/([^@]*)%0A@@ -(\d+)[^\s]* \+(\d+)[^@]*@@(.*)/::$ANNOTATION_SEVERITY file=\1,line=\2,endLine=\3,title=$ANNOTATION_TITLE::$ANNOTATION_DESCRIPTION%0A\4/"
# Example for the previous substitution
# input: +++ b/icons/accessibility.json%0A@@ -2,0 +3 @@%0A+ "contributors": ["hi"],%0A@@ -13 +14 @@%0A+}%0A
# output: ::$ANNOTATION_SEVERITY file=icons/accessibility.json,line=2,endLine=3,title=$ANNOTATION_TITLE::$ANNOTATION_DESCRIPTION%0A%0A+ "contributors": ["hi"],%0A@@ -13 +14 @@%0A+}%0A
lint-aliases:
name: Check Uniqueness of Aliases
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Check Uniqueness of Aliases
run: "! cat <(printf \"%s\\n\" icons/*.json | while read -r name; do basename \"$name\" .json; done) <(jq -cr 'select(.aliases) | .aliases[] | if type==\"string\" then . else .name end' icons/*.json) | sort | uniq -c | grep -ve '^\\s*1 '"
generate-changed-icons-comment:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: refs/pull/${{ github.event.pull_request.number }}/merge
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v41
with:
files: icons/*.svg
- name: Find Comment
uses: peter-evans/find-comment@v2
id: pr-comment
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: Added or changed icons
- uses: actions/setup-node@v4
- name: Install svgson for code preview (safer and faster than installing all deps)
run: npm install svgson
- name: Generate comment markup
run: node ./scripts/generateChangedIconsCommentMarkup.mjs >> comment-markup.md
id: comment-markup
env:
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
- name: Create or update comment
uses: peter-evans/create-or-update-comment@v3
with:
comment-id: ${{ steps.pr-comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body-path: ./comment-markup.md
edit-mode: replace

View File

@@ -38,8 +38,6 @@ jobs:
if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest
needs: pre-release
permissions:
id-token: write
strategy:
fail-fast: false
matrix:
@@ -48,21 +46,20 @@ jobs:
'lucide',
'lucide-react',
'lucide-react-native',
'lucide-vue',
'lucide-vue-next',
'lucide-angular',
'lucide-preact',
'lucide-solid',
'lucide-svelte',
'@lucide/astro',
'@lucide/svelte',
]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -80,24 +77,20 @@ jobs:
run: pnpm --filter ${{ matrix.package }} test
- name: Publish
run: pnpm --filter ${{ matrix.package }} publish --access public --no-git-checks --ignore-scripts
env:
NPM_CONFIG_PROVENANCE: true
run: pnpm --filter ${{ matrix.package }} publish --no-git-checks --ignore-scripts
lucide-static:
if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest
needs: [pre-release, lucide-font]
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
- uses: pnpm/action-setup@v4
- uses: actions/download-artifact@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -116,8 +109,6 @@ jobs:
- name: Publish
run: pnpm --filter lucide-static publish --no-git-checks
env:
NPM_CONFIG_PROVENANCE: true
lucide-font:
if: github.repository == 'lucide-icons/lucide'
@@ -125,11 +116,11 @@ jobs:
needs: pre-release
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -141,7 +132,7 @@ jobs:
run: pnpm build:font
- name: 'Upload to Artifacts'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: lucide-font
path: lucide-font
@@ -153,14 +144,14 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
- name: Zip font and icons
run: |
zip -r lucide-font-${{ needs.pre-release.outputs.VERSION }}.zip lucide-font
zip -r lucide-icons-${{ needs.pre-release.outputs.VERSION }}.zip icons
- name: Release zip and fonts
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ needs.pre-release.outputs.VERSION }}
files: |

View File

@@ -1,32 +0,0 @@
name: 'Request Review'
on:
pull_request_target:
types: [opened]
paths: icons/*.svg
jobs:
request-review:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: refs/pull/${{ github.event.pull_request.number }}/merge
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v41
with:
files: icons/*.svg
- run: |
while IFS= read -r file; do
jq -r '.contributors[]' "${file%.svg}.json"
done <<< "$CHANGED_FILES" | while read -r contributor; do
gh pr edit "${{ github.event.pull_request.number }}" --add-reviewer "$contributor" || true
done
env:
GH_TOKEN: ${{ github.token }}
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}

1
.gitignore vendored
View File

@@ -4,7 +4,6 @@
.obsidian
.now
.idea
.env
node_modules
dist
build

View File

@@ -1,5 +1,4 @@
{
"$schema": "https://raw.githubusercontent.com/Yash-Singh1/vscode-snippets-json-schema/main/schema.json",
"Lucide SVG": {
"scope": "xml",
"description": "Base SVG with Lucide attributes.",
@@ -52,16 +51,6 @@
],
"body": "<circle cx=\"${2:12}\" cy=\"${3:$2}\" r=\"${1|10,2,.5\" fill=\"currentColor|}\" />"
},
"Squircle": {
"scope": "xml",
"description": "SVG `path` with Lucide defaults.",
"prefix": [
"squircle",
"path",
"<path"
],
"body": "<path d=\"M12 3c7.2 0 9 1.8 9 9s-1.8 9-9 9-9-1.8-9-9 1.8-9 9-9\" />"
},
"Ellipse": {
"scope": "xml",
"description": "SVG `ellipse`.",

View File

@@ -1,71 +0,0 @@
# Our Official Stance on Including Brand Logos in Lucide
## TL;DR
Lucide **does not accept** brand logos, and we do not plan to add them in the future.
This is due to a combination of **legal restrictions**, **design consistency concerns**, and **practical maintenance reasons**.
If you need brand logos, we recommend [Simple Icons](https://simpleicons.org/), which provides an extensive, legally safer collection of brand marks.
---
## 1. Historical Context
This is not a new debate — other icon sets have gone through the same discussion:
- **Material Design Icons** [deprecated all brand icons](https://github.com/Templarian/MaterialDesign/issues/6602) because they didn't fit the style, didn't work well in one color, and often looked out of place in a 24×24px grid.
- **Feather Icons** [came to the same conclusion](https://github.com/feathericons/feather/issues/763): brand logos have their own style, and forcing them into another inevitably leads to aesthetic compromises.
- **Lucide** learned from these examples — we'd rather focus on making a consistent set of non-brand icons that all work together.
## 2. Legal Considerations
Most brand logos:
- Are **protected by trademark or copyright**.
- Have **strict rules** for how they can be used (colors, spacing, proportions, etc.).
- **Don't allow modification** — but we'd have to change them to fit Lucide's style.
This means adding them could:
1. Break copyright or trademark law.
2. Make both you and the Lucide project legally responsible.
3. Force us to review every new request one by one for legal issues — something we simply can't do.
> **Note:** Simple Icons avoids this by keeping logos exactly as the brand provides them — though even they sometimes face [legal challenges](https://github.com/simple-icons/simple-icons/issues/11236).
## 3. Design & Consistency
Lucide is all about **visual consistency**.
Adding brand logos would:
- Break [our own design rules](https://lucide.dev/guide/design/icon-design-guide#icon-design-principles) for shapes, proportions, and stroke.
- Mix two fundamentally different categories of graphics (pictograms vs. corporate logos).
- Create a library where a subset of icons will always look "out of place".
If the logos are not in Lucide's style, why include them in Lucide at all? Better to use them from a dedicated brand icon source.
## 4. Maintenance Burden
Even with our current **"no brand icon requests"** policy, people still request them regularly.
Having any brand icons in the set:
- Makes people think we might add more in the future.
- Leads to repeated requests and the same conversations over and over.
- Wastes maintainer time redirecting people to the same explanation.
Removing them entirely solves this problem.
## 5. Recommended Alternatives
If you need brand icons, try:
- [Simple Icons](https://simpleicons.org/): offers a huge range of brands, in consistent SVG format, using a 24×24 viewBox, the same as ours.
- Official brand asset pages: most major companies provide downloadable SVGs.
You can use these alongside Lucide without bloating our core library.
## Final Words
Lucide is an **icon** set, not a **logo** set.
Logos belong in dedicated logo resources.
We're focusing on what Lucide does best: providing a clean, cohesive, and legally safe collection of open-source icons.

View File

@@ -16,10 +16,10 @@ Guidelines for pull requests:
- __Make your commit messages as descriptive as possible.__ Include as much information as you can. Explain anything that the file diffs themselves wont make apparent.
- __Document your pull request__. Explain your fix, link to the relevant issue, add screenshots when adding new icons.
- __Make sure the target of your pull request is the relevant branch__. Most of bug fixes or new feature should go to the `main` branch.
- __Include only related work__. If your pull request has unrelated commits, it won't be accepted.
- __Make sure the target of your pull request is the relevant branch__. Most of bugfix or new feature should go to the `main` branch.
- __Include only related work__. If your pull request has unrelated commit, it won't be accepted.
### Icon Pull Requests
### Pull Requests Including Icons
#### Guidelines
@@ -27,30 +27,26 @@ Please make sure you follow the icon guidelines, that should be followed to keep
Read it here: [ICON_GUIDELINES](https://lucide.dev/docs/icon-design-guide).
#### Lucide Studio
For formatting and adjusting SVG icons, [@jguddas](https://github.com/jguddas) made a great tool called [Lucide Studio](https://studio.lucide.dev/). It is a web-based SVG editor that allows you to edit and adjust icons in the Lucide style. You can use it to create new icons or modify existing ones.
#### Editor guides
### Editor guides
Here you can find instructions on how to implement the guidelines with different vector graphics editors:
##### [Adobe Illustrator Guide](https://lucide.dev/docs/illustrator-guide)
#### [Adobe Illustrator Guide](https://lucide.dev/docs/illustrator-guide)
You can also [download an Adobe Illustrator template](https://github.com/lucide-icons/lucide/blob/main/docs/public/templates/illustrator_template.ai).
##### [Inkscape Guide](https://lucide.dev/docs/inkscape-guide)
#### [Inkscape Guide](https://lucide.dev/docs/inkscape-guide)
##### [Figma Guide](https://lucide.dev/docs/figma-guide)
#### [Figma Guide](https://lucide.dev/docs/figma-guide)
##### [Affinity Designer Guide](https://lucide.dev/guide/design/affinity-designer-guide)
#### [Affinity Designer Guide](https://lucide.dev/guide/design/affinity-designer-guide)
#### Submitting Multiple Icons
### Submitting Multiple Icons
If you want to submit multiple icons, please separate the icons and group them. That makes reviewing the icons easier and keeps the thread clean and scoped.
So don't submit multiple icons in one PR that have nothing to do with each other.
If you want submit multiple icons, please separate the icons and group them. That makes reviewing the icons easier and keep the thread clean and scoped.
So don't submit multiple icons in one PR that have noting to do with each other.
So for example don't create one PR with icons: `arrow-up`, `bicycle`, `arrow-down`.
Separate them into two PRs; 'pr-01' `arrow`, `arrow-down` and 'pr-02' `bicycle`.
Seperate them by two PRs; 'pr-01' `arrow`, `arrow-down` and 'pr-02' `bicycle`.
## Icon Requests
@@ -81,7 +77,7 @@ To distribute different packages we use [PNPM workspaces](https://pnpm.io/worksp
The configured directory for workspaces is the [packages](https://github.com/lucide-icons/lucide/tree/main/packages) directory, located in the root directory. There you will find all the current packages from lucide.
There are more workspaces defined, see [`pnpm-workspace.yaml`](https://github.com/lucide-icons/lucide/blob/main/pnpm-workspace.yaml).
> Note: One package is not managed by pnpm: **lucide-flutter**, this package is written in Dart and uses pub for publishing.
> Note: One package is not managed by pnpm: **lucide-flutter**, this package is written in Dart and used pub for publishing.
### Generated Code
@@ -131,7 +127,7 @@ When adding new features to for example the icon component for a framework. It i
### Local Testing
To test changes in a local project, you can use `yarn link`, `npm link`, `bun link` or `pnpm link` to link the package. Before you do this make sure you've built the package first.
To test changes in a local project, you can use `yarn link`, `npm link`, `bun link` or `pnpm link` to link the package. Before you do this make sure you builded the package first.
```sh
# in packages/lucide-react
@@ -161,30 +157,6 @@ lucide
The lucide.dev website is using [vitepress](https://vitepress.dev/) to generate the static website. The markdown files are located in the docs directory.
#### Running the Docs Website Locally
To test the docs website locally, follow these steps:
1. **Navigate to the docs directory**
```sh
cd docs
```
2. **Start the Local Development Server**
```sh
pnpm run docs:dev
```
3. **Open the Website Locally**
Vitepress should open with the following format:
VitePress dev server is running at:
- **Local**: `http://localhost:3000/`
- **Network**: `http://192.168.x.x:3000/`
### Guides
Detailed documentation about: installation, guides, packages, design guides etc.
@@ -193,13 +165,15 @@ Detailed documentation about: installation, guides, packages, design guides etc.
All the icons of lucide in SVG format. These will be used as source for all the packages and other distributions for the lucide icons.
### Packages
### packages
Includes all the (npm) packages of lucide.
### Scripts
> Note: One package is not managed by pnpm: **lucide-flutter**, this package is written in Dart and used pub for publishing.
Includes useful scripts to automate certain jobs. Big part of the scripts is the template generation, for example it generates icon components for all the packages. These scripts are usually executed from the "scripts" section in the package.json.
### scripts
Includes usefully scripts to automate certain jobs. Big part of the scripts is the template generation, for example it generates icon components for all the packages. These scripts are usually executed from the "scripts" section in the package.json.
## Documentation
@@ -216,4 +190,4 @@ If you need any help or have problems with you contribution. Please don't hesita
Thank you to all the people who already contributed to Lucide!
<a href="https://github.com/lucide-icons/lucide/graphs/contributors">
<img src="https://opencollective.com/lucide-icons/contributors.svg?width=800" /></a>
<img src="https://opencollective.com/lucide-icons/contributors.svg?width=890" /></a>

26
LICENSE
View File

@@ -1,6 +1,6 @@
ISC License
Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2025.
Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -13,27 +13,3 @@ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---
The MIT License (MIT) (for portions derived from Feather)
Copyright (c) 2013-2023 Cole Bemis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -40,7 +40,6 @@ Lucide is an open-source icon library that provides 1000+ vector (svg) files for
| <img src="https://lucide.dev/framework-logos/preact.svg" alt="Preact logo" width="48"> | **`lucide-preact`** | [![npm](https://img.shields.io/npm/v/lucide-preact)](https://www.npmjs.com/package/lucide-preact) | ![NPM Downloads](https://img.shields.io/npm/dw/lucide-preact) | [Docs](https://lucide.dev/guide/packages/lucide-preact) · [Source](./packages/lucide-preact) |
| <img src="https://lucide.dev/framework-logos/react-native.svg" alt="React Native logo" width="48"> | **`lucide-react-native`** | [![npm](https://img.shields.io/npm/v/lucide-react-native)](https://www.npmjs.com/package/lucide-react-native) | ![NPM Downloads](https://img.shields.io/npm/dw/lucide-react-native) | [Docs](https://lucide.dev/guide/packages/lucide-react-native) · [Source](./packages/lucide-react-native) |
| <img src="https://lucide.dev/framework-logos/angular.svg" alt="Angular logo" width="48"> | **`lucide-angular`** | [![npm](https://img.shields.io/npm/v/lucide-angular)](https://www.npmjs.com/package/lucide-angular) | ![NPM Downloads](https://img.shields.io/npm/dw/lucide-angular) | [Docs](https://lucide.dev/guide/packages/lucide-angular) · [Source](./packages/lucide-angular) |
| <img src="https://lucide.dev/framework-logos/astro.svg" alt="Astro logo" width="48"> | **`@lucide/astro`** | [![npm](https://img.shields.io/npm/v/@lucide/astro)](https://www.npmjs.com/package/@lucide/astro) | ![NPM Downloads](https://img.shields.io/npm/dw/@lucide/astro) | [Docs](https://lucide.dev/guide/packages/lucide-astro) · [Source](./packages/astro) |
| <img src="https://lucide.dev/framework-logos/svg.svg" alt="SVG logo" width="48"> | **`lucide-static`** | [![npm](https://img.shields.io/npm/v/lucide-static)](https://www.npmjs.com/package/lucide-static) | ![NPM Downloads](https://img.shields.io/npm/dw/lucide-static) | [Docs](https://lucide.dev/guide/packages/lucide-static) · [Source](./packages/lucide-static) |
### Figma
@@ -57,12 +56,6 @@ For more info on how to contribute please see the [contribution guidelines](http
Caught a mistake or want to contribute to the documentation? [Edit this page on Github](https://github.com/lucide-icons/lucide/blob/main/README.md)
## About brand logos
Lucide **does not accept** brand logos, and we do not plan to add them in the future. This is due to a combination of **legal restrictions**, **design consistency concerns**, and **practical maintenance reasons**.
[Click here to read our official statement about brand logos in Lucide.](./BRAND_LOGOS_STATEMENT.md)
## Community
Join the community on our [Discord](https://discord.gg/EH6nSts) server!
@@ -76,9 +69,7 @@ Lucide is totally free for commercial use and personal use, this software is lic
Thank you to all the people who contributed to Lucide!
<a href="https://github.com/lucide-icons/lucide/graphs/contributors">
<img src="https://opencollective.com/lucide-icons/contributors.svg?width=800" />
</a>
<img src="https://opencollective.com/lucide-icons/contributors.svg?width=890" /></a>
## Sponsors
@@ -90,5 +81,5 @@ Thank you to all the people who contributed to Lucide!
### Awesome backers 🍺
<a href="https://www.scipress.io?utm_source=lucide"><img src="docs/public/sponsors/scipress.svg" width="180" alt="Scipress sponsor badge" /></a>
<a href="https://github.com/pdfme/pdfme"><img src="docs/public/sponsors/pdfme.svg" width="180" alt="pdfme sponsor badge" /></a>
<a href="https://www.fina.money/"><img src="docs/public/sponsors/fina-money.png" width="180" alt="Fina Money sponsor badge" /></a>

View File

@@ -13,12 +13,10 @@ export default eventHandler((event) => {
const data = pathData.at(-1).slice(0, -4);
const [name] = pathData;
const src = Buffer.from(data, 'base64').toString('utf8').replaceAll('\n', '');
const width = parseInt((src.includes('<svg ') ? src.match(/width="(\d+)"/)?.[1] : null) ?? '24');
const height = parseInt(
(src.includes('<svg ') ? src.match(/height="(\d+)"/)?.[1] : null) ?? '24',
);
const src = Buffer.from(data, 'base64')
.toString('utf8')
.replaceAll('\n', '')
.replace(/<svg[^>]*>|<\/svg>/g, '');
const children = [];
@@ -40,7 +38,7 @@ export default eventHandler((event) => {
children.push(
createElement(Backdrop, {
backdropString,
src: src.replace(/<svg[^>]*>|<\/svg>/g, ''),
src,
color: '#777',
}),
);
@@ -48,18 +46,7 @@ export default eventHandler((event) => {
const svg = Buffer.from(
// We can't use jsx here, is not supported here by nitro.
renderToString(
createElement(
SvgPreview,
{
src: src.replace(/<svg[^>]*>|<\/svg>/g, ''),
height,
width,
showGrid: true,
},
children,
),
),
renderToString(createElement(SvgPreview, { src, showGrid: true }, children)),
).toString('utf8');
defaultContentType(event, 'image/svg+xml');

View File

@@ -17,13 +17,6 @@ export default eventHandler((event) => {
.replaceAll('\n', '')
.replace(/<svg[^>]*>|<\/svg>/g, '');
const width = parseInt(
(newSrc.includes('<svg ') ? newSrc.match(/width="(\d+)"/)?.[1] : null) ?? '24',
);
const height = parseInt(
(newSrc.includes('<svg ') ? newSrc.match(/height="(\d+)"/)?.[1] : null) ?? '24',
);
const children = [];
const oldSrc = iconNodes[name]
@@ -34,9 +27,7 @@ export default eventHandler((event) => {
const svg = Buffer.from(
// We can't use jsx here, is not supported here by nitro.
renderToString(
createElement(Diff, { oldSrc, newSrc, showGrid: true, height, width }, children),
),
renderToString(createElement(Diff, { oldSrc, newSrc, showGrid: true }, children)),
).toString('utf8');
defaultContentType(event, 'image/svg+xml');

View File

@@ -1,10 +1,6 @@
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
import { Resvg, initWasm } from '@resvg/resvg-wasm';
import iconNodes from '../../../data/iconNodes';
import wasm from './loadWasm';
import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import createLucideIcon from 'lucide-react/src/createLucideIcon';
var initializedResvg = initWasm(wasm);
@@ -13,37 +9,27 @@ export default eventHandler(async (event) => {
await initializedResvg;
const imageSize = 96;
const name = params.data.split('/').at(-3);
const iconSizeString = params.data.split('/').at(-2);
const svgData = params.data.split('/').at(-1);
const [iconSizeString, svgData] = params.data.split('/');
const iconSize = parseInt(iconSizeString, 10);
const data = svgData.slice(0, -4);
const prevSvg = iconNodes[name]
? renderToStaticMarkup(createElement(createLucideIcon(name, iconNodes[name])))
: undefined;
const src = Buffer.from(data, 'base64').toString('utf8');
const svg = (src.includes('<svg') ? src : `<svg>${src}</svg>`)
.replace(/(\r\n|\n|\r)/gm, '')
.replace(
/<svg[^>]*>/,
/<svg[^>]*/,
`<svg
xmlns="http://www.w3.org/2000/svg"
width="${iconSize}"
height="${prevSvg ? iconSize * 2 : iconSize}"
viewBox="0 0 24 ${prevSvg ? 48 : 24}"
height="${iconSize}"
viewBox="0 0 24 24"
fill="none"
stroke="#fff"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
${prevSvg?.replaceAll('\n', '').replace(/<svg[^>]*>|<\/svg>/g, '')}
<g transform="translate(0, ${prevSvg ? 24 : 0})">
`,
)
.replace(/<\/svg>/, '</g></svg>');
);
const resvg = new Resvg(svg, { background: '#000' });
const pngData = resvg.render();
@@ -53,7 +39,7 @@ export default eventHandler(async (event) => {
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
return `
<svg xmlns="http://www.w3.org/2000/svg" width="${imageSize}" height="${prevSvg ? imageSize * 2 : imageSize}" viewBox="0 0 ${imageSize} ${prevSvg ? imageSize * 2 : imageSize}">
<svg xmlns="http://www.w3.org/2000/svg" width="${imageSize}" height="${imageSize}" viewBox="0 0 ${imageSize} ${imageSize}">
<style>
@media screen and (prefers-color-scheme: light) {
#fallback-background { fill: transparent; }
@@ -66,20 +52,20 @@ export default eventHandler(async (event) => {
<mask id="mask">
<image
width="${imageSize}"
height="${prevSvg ? imageSize * 2 : imageSize}"
height="${imageSize}"
href="data:image/png;base64,${pngBuffer.toString('base64')}"
image-rendering="pixelated"
/>
</mask>
<rect
id="fallback-background"
width="100%"
height="100%" ry="${imageSize / 24}"
width="${imageSize}"
height="${imageSize}" ry="${imageSize / 24}"
fill="#fff"
/>
<rect
width="100%"
height="100%"
width="${imageSize}"
height="${imageSize}"
fill="#000"
mask="url(#mask)"
/>

View File

@@ -36,13 +36,6 @@ export default defineConfig({
},
},
head: [
[
'link',
{
rel: 'preconnect',
href: 'https://analytics.lucide.dev',
},
],
[
'script',
{

View File

@@ -7,14 +7,6 @@
"dark": "/company-logos/vercel-dark.svg"
}
},
{
"name": "MDN Web Docs",
"url": "https://developer.mozilla.org/",
"image": {
"light": "/company-logos/mdn-light.svg",
"dark": "/company-logos/mdn-dark.svg"
}
},
{
"name": "Supabase",
"url": "https://supabase.com",
@@ -31,14 +23,6 @@
"dark": "/company-logos/obsidian-dark.svg"
}
},
{
"name": "Nuxt",
"url": "https://nuxt.com/",
"image": {
"light": "/company-logos/nuxt-light.svg",
"dark": "/company-logos/nuxt-dark.svg"
}
},
{
"name": "Open Collective",
"url": "https://opencollective.com",

View File

@@ -127,27 +127,8 @@
}
]
},
"@lucide/astro": {
"docsAlias": "lucide-astro",
"packageDirname": "astro",
"order": 8,
"icon": "astro",
"iconDark": "astro-dark",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/@lucide/astro",
"href": "https://www.npmjs.com/package/@lucide/astro"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/@lucide/astro",
"href": "https://www.npmjs.com/package/@lucide/astro"
}
]
},
"lucide-static": {
"order": 9,
"order": 8,
"icon": "svg",
"shields": [
{

View File

@@ -79,7 +79,7 @@
},
{
"name": "lucide-lustre",
"description": "A library providing https://lucide.dev icons to lustre.",
"description": "A library providing https://lucide.dev icons to lustre",
"icon": "/framework-logos/lustre.webp",
"shields": [
{
@@ -95,24 +95,5 @@
],
"source": "https://github.com/dinkelspiel/lucide_lustre",
"documentation": "https://github.com/dinkelspiel/lucide_lustre/blob/master/README.md"
},
{
"name": "lucide_icons_flutter",
"description": "A library providing https://lucide.dev icons to lustre.",
"icon": "/framework-logos/flutter.svg",
"shields": [
{
"alt": "Latest Stable Version",
"src": "https://img.shields.io/pub/v/lucide_icons_flutter",
"href": "https://pub.dev/packages/lucide_icons_flutter"
},
{
"alt": "Total Downloads",
"src": "https://img.shields.io/pub/dm/lucide_icons_flutter",
"href": "https://pub.dev/packages/lucide_icons_flutter"
}
],
"source": "https://github.com/vqh2602/lucide-flutter-main",
"documentation": "https://pub.dev/documentation/lucide_icons_flutter/latest/"
}
]

View File

@@ -53,8 +53,8 @@ const Backdrop = ({
<rect
x="0"
y="0"
width="100%"
height="100%"
width="24"
height="24"
fill="#fff"
stroke="none"
/>
@@ -67,8 +67,8 @@ const Backdrop = ({
<rect
x="0"
y="0"
width="100%"
height="100%"
width="24"
height="24"
opacity={0.5}
fill={`url(#pattern-${id})`}
stroke="none"

View File

@@ -7,17 +7,15 @@ const SvgPreview = React.forwardRef<
{
oldSrc: string;
newSrc: string;
height: number;
width: number;
} & React.SVGProps<SVGSVGElement>
>(({ oldSrc, newSrc, children, height, width, ...props }, ref) => {
>(({ oldSrc, newSrc, children, ...props }, ref) => {
return (
<svg
ref={ref}
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
viewBox={`0 0 ${width} ${height}`}
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
@@ -27,8 +25,6 @@ const SvgPreview = React.forwardRef<
>
<style>{darkModeCss}</style>
<Grid
width={width}
height={height}
strokeWidth={0.1}
stroke="#777"
strokeOpacity={0.3}
@@ -41,8 +37,8 @@ const SvgPreview = React.forwardRef<
<rect
x="0"
y="0"
width="100%"
height="100%"
width="24"
height="24"
fill="#000"
stroke="none"
/>

View File

@@ -1,137 +0,0 @@
import React from 'react';
import pathToPoints from './path-to-points';
import { Path, PathProps } from './types';
export const GapViolationHighlight = ({
radius,
stroke,
strokeWidth,
strokeOpacity,
paths,
...props
}: {
paths: Path[];
} & PathProps<'stroke' | 'strokeOpacity' | 'strokeWidth', 'd'>) => {
const id = React.useId();
const groupedPaths = Object.entries(
paths.reduce(
(groups, val) => {
const key = val.c.id;
groups[key] = [...(groups[key] || []), val];
return groups;
},
{} as Record<number, Path[]>,
),
);
const groups: Group[] = [];
for (const [, paths] of groupedPaths) {
const d = paths.map((path) => path.d).join(' ');
const points = paths.flatMap((path) => pathToPoints(path));
groups.push({ id: d, points });
}
const mergedGroups = mergeGroups(groups, 2);
return (
<g {...props}>
<defs xmlns="http://www.w3.org/2000/svg">
<pattern
id={`backdrop-pattern-${id}`}
width=".1"
height=".1"
patternUnits="userSpaceOnUse"
patternTransform="rotate(45 50 50)"
>
<line
stroke={stroke}
strokeWidth={0.1}
y2={1}
/>
<line
stroke={stroke}
strokeWidth={0.1}
y2={1}
/>
</pattern>
</defs>
{mergedGroups.flatMap((ds, idx, arr) =>
arr.slice(0, idx).map((val, i) => (
<g
strokeWidth={strokeWidth}
key={i}
>
<mask
id={`svg-preview-backdrop-mask-${id}-${i}`}
maskUnits="userSpaceOnUse"
>
<path
stroke="white"
d={val.join(' ')}
/>
</mask>
<path
d={ds.join(' ')}
stroke={`url(#backdrop-pattern-${id})`}
strokeWidth={strokeWidth}
strokeOpacity={strokeOpacity}
mask={`url(#svg-preview-backdrop-mask-${id}-${i})`}
/>
</g>
)),
)}
</g>
);
};
type Point = { x: number; y: number };
type Group = { id: string; points: Point[] };
// Euclidean distance
function distance(a: Point, b: Point): number {
return Math.hypot(a.x - b.x, a.y - b.y);
}
// Check if two groups should be merged based on minimum distance
function shouldMerge(a: Group, b: Group, minDistance: number): boolean {
for (const pa of a.points) {
for (const pb of b.points) {
if (distance(pa, pb) <= minDistance) {
return true;
}
}
}
return false;
}
// Merge groups and return arrays of merged group IDs
function mergeGroups(groups: Group[], minDistance: number): string[][] {
const mergedGroups: Group[][] = groups.map((g) => [g]);
let changed = true;
while (changed) {
changed = false;
outer: for (let i = 0; i < mergedGroups.length; i++) {
for (let j = i + 1; j < mergedGroups.length; j++) {
// Check if any group in mergedGroups[i] should merge with any in mergedGroups[j]
if (
mergedGroups[i].some((ga) =>
mergedGroups[j].some((gb) => shouldMerge(ga, gb, minDistance)),
)
) {
// Merge group j into group i
mergedGroups[i] = [...mergedGroups[i], ...mergedGroups[j]];
mergedGroups.splice(j, 1);
changed = true;
break outer;
}
}
}
}
// Return only arrays of IDs
return mergedGroups.map((groupList) => groupList.map((g) => g.id));
}

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { PathProps, Path } from './types';
import getPaths, { assert } from './utils';
import { GapViolationHighlight } from './GapViolationHighlight.tsx';
import { getPaths, assert } from './utils';
export const darkModeCss = `
@media screen and (prefers-color-scheme: light) {
@@ -21,16 +20,10 @@ export const darkModeCss = `
export const Grid = ({
radius,
fill,
height,
width,
subGridSize = 0,
fill = '#fff',
...props
}: {
height: number;
width: number;
strokeWidth: number;
subGridSize?: number;
radius: number;
} & PathProps<'stroke', 'strokeWidth'>) => (
<g
@@ -40,53 +33,43 @@ export const Grid = ({
>
<rect
className="svg-preview-grid-rect"
width={width - props.strokeWidth}
height={height - props.strokeWidth}
width={24 - props.strokeWidth}
height={24 - props.strokeWidth}
x={props.strokeWidth / 2}
y={props.strokeWidth / 2}
rx={radius}
fill={fill}
/>
<path
strokeDasharray={
'0 0.1 ' + '0.1 0.15 '.repeat(subGridSize ? subGridSize * 4 - 1 : 95) + '0 0.15'
}
strokeDasharray={'0 0.1 ' + '0.1 0.15 '.repeat(11) + '0 0.15'}
strokeWidth={0.1}
d={
props.d ||
[
...new Array(Math.floor(width - 1))
new Array(Math.floor(24 - 1))
.fill(null)
.map((_, i) => i)
.filter((i) => !subGridSize || i % subGridSize !== subGridSize - 1)
.flatMap((i) => [`M${i + 1} ${props.strokeWidth}v${height - props.strokeWidth * 2}`]),
...new Array(Math.floor(height - 1))
.fill(null)
.map((_, i) => i)
.filter((i) => !subGridSize || i % subGridSize !== subGridSize - 1)
.flatMap((i) => [`M${props.strokeWidth} ${i + 1}h${width - props.strokeWidth * 2}`]),
].join('')
.filter((i) => i % 3 !== 2)
.flatMap((i) => [
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
])
.join('')
}
/>
{!!subGridSize && (
<path
d={
props.d ||
[
...new Array(Math.floor(width - 1))
new Array(Math.floor(24 - 1))
.fill(null)
.map((_, i) => i)
.filter((i) => i % subGridSize === subGridSize - 1)
.flatMap((i) => [`M${i + 1} ${props.strokeWidth}v${height - props.strokeWidth * 2}`]),
...new Array(Math.floor(height - 1))
.fill(null)
.map((_, i) => i)
.filter((i) => i % subGridSize === subGridSize - 1)
.flatMap((i) => [`M${props.strokeWidth} ${i + 1}h${width - props.strokeWidth * 2}`]),
].join('')
.filter((i) => i % 3 === 2)
.flatMap((i) => [
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
])
.join('')
}
/>
)}
</g>
);
@@ -116,7 +99,6 @@ const Shadow = ({
>
{groupedPaths.map(([id, paths]) => (
<mask
key={`svg-preview-shadow-mask-${id}`}
id={`svg-preview-shadow-mask-${id}`}
maskUnits="userSpaceOnUse"
strokeOpacity="1"
@@ -126,8 +108,8 @@ const Shadow = ({
<rect
x={0}
y={0}
width="100%"
height="100%"
width={24}
height={24}
fill="#fff"
stroke="none"
rx={radius}
@@ -170,9 +152,7 @@ const ColoredPath = ({
colors,
paths,
...props
}: { paths: Path[]; colors: string[] } & PathProps<never, 'd' | 'stroke'>) => {
let idx = 0;
return (
}: { paths: Path[]; colors: string[] } & PathProps<never, 'd' | 'stroke'>) => (
<g
className="svg-preview-colored-path-group"
{...props}
@@ -181,23 +161,21 @@ const ColoredPath = ({
<path
key={i}
d={d}
stroke={colors[(c.name === 'path' ? idx++ : c.id) % colors.length]}
stroke={colors[(c.name === 'path' ? i : c.id) % colors.length]}
/>
))}
</g>
);
};
);
const ControlPath = ({
paths,
radius,
pointSize,
...props
}: {
pointSize: number;
paths: Path[];
radius: number;
} & PathProps<'stroke' | 'strokeWidth', 'd'>) => {
}: { pointSize: number; paths: Path[]; radius: number } & PathProps<
'stroke' | 'strokeWidth',
'd'
>) => {
const controlPaths = paths.map((path, i) => {
const element = paths.filter((p) => p.c.id === path.c.id);
const lastElement = element.at(-1)?.next;
@@ -229,8 +207,8 @@ const ControlPath = ({
<rect
x="0"
y="0"
width="100%"
height="100%"
width="24"
height="24"
fill="#fff"
stroke="none"
rx={radius}
@@ -265,7 +243,7 @@ const ControlPath = ({
)
.join('')}
/>
{controlPaths.map(({ prev, next, startMarker, endMarker }, i) => (
{controlPaths.map(({ d, prev, next, startMarker, endMarker }, i) => (
<React.Fragment key={i}>
{startMarker && (
<circle
@@ -301,37 +279,11 @@ const Radii = ({
{...props}
>
{paths.map(
({ circle, next, prev, c }, i) =>
({ c, prev, next, circle }, i) =>
circle && (
<React.Fragment key={i}>
{circle.tangentIntersection && c.name === 'path' && (
<>
<circle
cx={next.x * 2 - circle.tangentIntersection.x}
cy={next.y * 2 - circle.tangentIntersection.y}
r={0.25}
/>
<circle
cx={prev.x * 2 - circle.tangentIntersection.x}
cy={prev.y * 2 - circle.tangentIntersection.y}
r={0.25}
/>
<path
d={`M${next.x * 2 - circle.tangentIntersection.x} ${
next.y * 2 - circle.tangentIntersection.y
}L${circle.tangentIntersection.x} ${circle.tangentIntersection.y}L${prev.x * 2 - circle.tangentIntersection.x} ${
prev.y * 2 - circle.tangentIntersection.y
}`}
/>
<circle
cx={circle.tangentIntersection.x}
cy={circle.tangentIntersection.y}
r={0.25}
/>
</>
)}
{c.name === 'path' && (
<path d={`M${next.x} ${next.y}L${circle.x} ${circle.y}L${prev.x} ${prev.y}`} />
{c.name !== 'circle' && (
<path d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`} />
)}
<circle
cy={circle.y}
@@ -361,13 +313,17 @@ const Radii = ({
const Handles = ({
paths,
...props
}: { paths: Path[] } & PathProps<'strokeWidth' | 'stroke' | 'strokeOpacity', any>) => (
}: { paths: Path[] } & PathProps<
'strokeWidth' | 'stroke' | 'strokeDasharray' | 'strokeOpacity',
any
>) => {
return (
<g
className="svg-preview-handles-group"
{...props}
>
{paths.map(({ c, prev, next, cp1, cp2 }, i) => (
<React.Fragment key={i}>
{paths.map(({ c, prev, next, cp1, cp2 }) => (
<>
{cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />}
{cp1 && (
<circle
@@ -384,37 +340,28 @@ const Handles = ({
r={0.25}
/>
)}
</React.Fragment>
</>
))}
</g>
);
);
};
const SvgPreview = React.forwardRef<
SVGSVGElement,
{
height?: number;
width?: number;
src: string | ReturnType<typeof getPaths>;
showGrid?: boolean;
} & React.SVGProps<SVGSVGElement>
>(({ src, children, height = 24, width = 24, showGrid = false, ...props }, ref) => {
const subGridSize =
Math.max(height, width) % 3 === 0
? Math.max(height, width) > 24
? 12
: 3
: Math.max(height, width) % 5 === 0
? 5
: 0;
>(({ src, children, showGrid = false, ...props }, ref) => {
const paths = typeof src === 'string' ? getPaths(src) : src;
return (
<svg
ref={ref}
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
viewBox={`0 0 ${width} ${height}`}
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
@@ -425,12 +372,8 @@ const SvgPreview = React.forwardRef<
<style>{darkModeCss}</style>
{showGrid && (
<Grid
height={height}
width={width}
subGridSize={subGridSize}
strokeWidth={0.1}
stroke="#777"
mask="url(#svg-preview-bounding-box-mask)"
strokeOpacity={0.3}
radius={1}
/>
@@ -442,12 +385,6 @@ const SvgPreview = React.forwardRef<
radius={1}
strokeOpacity={0.15}
/>
<GapViolationHighlight
paths={paths}
stroke="red"
strokeOpacity={0.75}
strokeWidth={4}
/>
<Handles
paths={paths}
strokeWidth={0.12}
@@ -496,6 +433,4 @@ const SvgPreview = React.forwardRef<
);
});
SvgPreview.displayName = 'SvgPreview';
export default SvgPreview;

View File

@@ -1,19 +0,0 @@
import memoize from 'lodash/memoize';
import SVGPathCommander from 'svg-path-commander';
import { Path } from './types';
function pathToPoints({ d, prev, next }: Path, interval = 1) {
const commander = new SVGPathCommander(d);
const points = [];
try {
const totalLength = commander.getTotalLength();
points.push(prev);
for (let i = interval; i < totalLength - interval; i += interval) {
points.push(commander.getPointAtLength(i));
}
points.push(next);
} catch (err) {}
return points;
}
export default memoize(pathToPoints);

View File

@@ -1,10 +1,7 @@
import { INode, parseSync } from 'svgson';
// @ts-ignore
import toPath from 'element-to-path';
// @ts-ignore
import { SVGPathData, encodeSVGPath } from 'svg-pathdata';
import { Path, Point } from './types';
import memoize from 'lodash/memoize';
function assertNever(x: never): never {
throw new Error('Unknown type: ' + x['type']);
@@ -47,21 +44,17 @@ const extractNodes = (node: INode): INode[] => {
return [];
};
export const getNodes = memoize((src: string) =>
extractNodes(parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`)),
);
export const getNodes = (src: string) =>
extractNodes(parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`));
export const getCommands = (src: string) =>
getNodes(src)
.map(convertToPathNode)
.flatMap(({ d, name }, idx) =>
new SVGPathData(d)
.toAbs()
// @ts-ignore
.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) => {
export const getPaths = (src: string) => {
const commands = getCommands(src.includes('<svg') ? src : `<svg>${src}</svg>`);
const paths: Path[] = [];
let prev: Point | undefined = undefined;
@@ -244,7 +237,6 @@ const getPaths = (src: string) => {
break;
}
default: {
// @ts-ignore
assertNever(c);
}
}
@@ -252,7 +244,7 @@ const getPaths = (src: string) => {
return paths;
};
const arcEllipseCenter = (
export const arcEllipseCenter = (
x1: number,
y1: number,
rx: number,
@@ -304,52 +296,5 @@ const arcEllipseCenter = (
M2[1][0] * Cp[0] + M2[1][1] * Cp[1] + V3[1],
];
return {
x: C[0],
y: C[1],
tangentIntersection: intersectTangents(
{ x: x1, y: y1 },
{ x: x2, y: y2 },
{ x: C[0], y: C[1] },
),
};
return { x: C[0], y: C[1] };
};
function getTangentDirection(p: Point, center: Point): Point {
// Tangent is perpendicular to the radius vector (rotate radius 90°)
const dx = p.x - center.x;
const dy = p.y - center.y;
return { x: -dy, y: dx }; // 90° rotation
}
function intersectTangents(start: Point, end: Point, center: Point): Point | null {
const t1 = getTangentDirection(start, center);
const t2 = getTangentDirection(end, center);
// Solve: start + λ * t1 = end + μ * t2
const A = [
[t1.x, -t2.x],
[t1.y, -t2.y],
];
const b = [end.x - start.x, end.y - start.y];
// Compute determinant
const det = A[0][0] * A[1][1] - A[0][1] * A[1][0];
if (Math.abs(det) < 1e-10) {
// Lines are parallel, no intersection
return null;
}
const invDet = 1 / det;
const lambda = (b[0] * A[1][1] - b[1] * A[0][1]) * invDet;
// Intersection point = start + lambda * t1
return {
x: start.x + lambda * t1.x,
y: start.y + lambda * t1.y,
};
}
export default memoize(getPaths);

View File

@@ -1,4 +1,5 @@
import { bundledLanguages, getHighlighter, type ThemeRegistration } from 'shikiji';
import { bundledLanguages, type ThemeRegistration } from 'shikiji';
import { getHighlighter } from 'shikiji';
type CodeExampleType = {
title: string;
@@ -101,8 +102,13 @@ import { LucideAngularModule, $PascalCase } from 'lucide-angular';
},
{
language: 'html',
title: 'Icon font',
code: `<div class="icon-$Name"></div>`,
title: 'Icon Font',
code: `<style>
@import ('~lucide-static/font/Lucide.css');
</style>
<div class="icon-$Name"></div>
`,
},
];
};

View File

@@ -96,10 +96,6 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
text: 'Lucide Preact',
link: '/guide/packages/lucide-preact',
},
{
text: 'Lucide Astro',
link: '/guide/packages/lucide-astro',
},
{
text: 'Lucide Static',
link: '/guide/packages/lucide-static',

View File

@@ -38,7 +38,6 @@ const value = computed({
<Input
ref="input"
type="search"
autofocus
v-bind="$attrs"
v-model="value"
class="input-wrapper"

View File

@@ -1,22 +1,15 @@
<script setup>
import { ref } from 'vue'
import { Switch } from '@headlessui/vue'
defineProps({
modelValue: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:modelValue'])
const enabled = ref(false)
</script>
<template>
<Switch
:model-value="modelValue"
@update:model-value="emit('update:modelValue', $event)"
v-model="enabled"
class="switch"
:class="{ enabled: modelValue }"
:class="{ enabled }"
>
<span class="thumb" />
</Switch>

View File

@@ -48,7 +48,6 @@ function resetStyle () {
color.value = 'currentColor'
strokeWidth.value = 2
size.value = 24
absoluteStrokeWidth.value = false
}
watch(absoluteStrokeWidth, (enabled) => {

View File

@@ -37,12 +37,6 @@ export default {
logo: '/framework-logos/angular.svg',
label: 'Lucide documentation for Angular',
},
{
name: 'lucide-astro',
logo: '/framework-logos/astro.svg',
logoDark: '/framework-logos/astro-dark.svg',
label: 'Lucide documentation for Astro',
},
{
name: 'lucide-react-native',
logo: '/framework-logos/react-native.svg',

View File

@@ -13,7 +13,7 @@ const { go } = useRouter()
<HomeSectionTitle>Available For:</HomeSectionTitle>
<div class="packages-list">
<a
v-for="{ name, logo, logoDark } in data.packages"
v-for="{ name, logo } in data.packages"
:href="`/guide/packages/${name}`"
class="package-logo"
:aria-label="`Read more about: ${name} package`"
@@ -21,17 +21,10 @@ const { go } = useRouter()
>
<img
:src="logo"
:class="{ light: logoDark, 'image-logo': true }"
:alt="`${name} logo`"
height="36"
width="36"
loading="lazy"
/>
<img
v-if="logoDark"
:src="logoDark"
:alt="`${name} logo`"
class="image-logo dark"
loading="lazy"
/>
</a>
</div>
@@ -42,13 +35,6 @@ const { go } = useRouter()
</template>
<style scoped>
.image-logo {
object-fit: contain;
width: 36px;
height: 36px;
}
.packages-list {
display: flex;
flex-wrap: wrap;
@@ -71,11 +57,4 @@ const { go } = useRouter()
.package-logo:hover {
opacity: .6;
}
html.dark .image-logo.light {
display: none;
}
html:not(.dark) .image-logo.dark {
display: none;
}
</style>

View File

@@ -9,8 +9,6 @@ import {useData, useRouter} from 'vitepress';
import { computed } from 'vue';
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
import { diamond } from '../../../data/iconNodes'
import deprecationReasonTemplate from '../../../../../tools/build-icons/utils/deprecationReasonTemplate.ts';
const props = defineProps<{
icon: IconEntity
@@ -26,15 +24,6 @@ const tags = computed(() => {
})
const DiamondIcon = createLucideIcon('Diamond', diamond)
const deprecatedTitle = computed(() => {
if (!props.icon.deprecationReason) return '';
return deprecationReasonTemplate(props.icon.deprecationReason, {
componentName: props.icon.name,
iconName: props.icon.name,
toBeRemovedInVersion: props.icon.toBeRemovedInVersion,
});
});
</script>
<template>
@@ -47,13 +36,6 @@ const deprecatedTitle = computed(() => {
<DiamondIcon fill="currentColor" :size="12"/>
{{ icon.externalLibrary }}
</div>
<Badge
v-if="icon.deprecated"
class="deprecated-badge"
:title="deprecatedTitle"
>
Deprecated
</Badge>
</div>
<div class="tags-scroller" v-if="tags.length">
<p class="icon-tags horizontal-scroller">
@@ -116,16 +98,6 @@ const deprecatedTitle = computed(() => {
align-items: center;
}
.deprecated-badge {
background-color: var(--vp-c-brand-5);
margin-left: 40px;
opacity: .8;
}
.deprecated-badge:hover {
background-color: var(--vp-c-brand-2);
}
.icon-tags {
font-size: 16px;
color: var(--vp-c-text-2);

View File

@@ -178,8 +178,6 @@ const DiamondIcon = createLucideIcon('Diamond', diamond)
stroke-width: var(--customize-strokeWidth, 2);
width: calc(var(--customize-size, 24) * 1px);
height: calc(var(--customize-size, 24) * 1px);
max-width: 3rem;
max-height: 3rem;
}
html.absolute-stroke-width .lucide-icon.customizable {

View File

@@ -26,9 +26,10 @@ const iconComponent = computed(() => {
<component
ref="previewIcon"
:is="iconComponent"
:size="size"
:color="color"
:strokeWidth="absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth"
:width="size"
:height="size"
:stroke="color"
:stroke-width="absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth"
/>
<svg class="icon-grid" :viewBox="`0 0 ${size} ${size}`" fill="none" stroke-width="0.1" xmlns="http://www.w3.org/2000/svg">
<g :key="`grid-${i}`" v-for="(_, i) in gridLines">

View File

@@ -14,15 +14,7 @@ import CarbonAdOverlay from './CarbonAdOverlay.vue';
const ICON_SIZE = 56;
const ICON_GRID_GAP = 8;
const initialGridItems = computed(() => {
if (containerWidth.value === 0) return 120;
const itemsPerRow = columnSize.value || 10;
const visibleRows = Math.ceil(window.innerHeight / (ICON_SIZE + ICON_GRID_GAP));
return Math.min(itemsPerRow * (visibleRows + 2), 200);
});
const DEFAULT_GRID_ITEMS = 10 * 160;
const props = defineProps<{
icons: IconEntity[];
@@ -123,14 +115,15 @@ function handleCloseDrawer() {
/>
</StickyBar>
<NoResults
v-if="searchResults.length === 0 && searchQuery !== ''"
v-if="list.length === 0 && searchQuery !== ''"
:searchQuery="searchQuery"
@clear="searchQuery = ''"
/>
<IconGrid
v-else-if="list.length === 0"
:key="index"
overlayMode
:icons="searchResults.slice(0, initialGridItems)"
:icons="[...searchResults].splice(0, DEFAULT_GRID_ITEMS)"
:activeIcon="activeIconName"
@setActiveIcon="setActiveIconName"
/>

View File

@@ -47,7 +47,6 @@ function resetStyle () {
color.value = STYLE_DEFAULTS.color
strokeWidth.value = STYLE_DEFAULTS.strokeWidth
size.value = STYLE_DEFAULTS.size
absoluteStrokeWidth.value = STYLE_DEFAULTS.absoluteStrokeWidth
}
watch(absoluteStrokeWidth, (enabled) => {
@@ -60,8 +59,9 @@ const customizingActive = computed(() => {
return color.value !== STYLE_DEFAULTS.color
|| strokeWidth.value !== STYLE_DEFAULTS.strokeWidth
|| size.value !== STYLE_DEFAULTS.size
|| absoluteStrokeWidth.value !== STYLE_DEFAULTS.absoluteStrokeWidth
})
</script>
<template>
@@ -110,7 +110,7 @@ const customizingActive = computed(() => {
name="size"
v-model="size"
:min="16"
:max="256"
:max="48"
:step="4"
/>
</InputField>
@@ -120,8 +120,8 @@ const customizingActive = computed(() => {
label="Absolute Stroke width"
>
<Switch
id="absolute-stroke-width"
name="absolute-stroke-width"
id="size"
name="size"
v-model="absoluteStrokeWidth"
/>
</InputField>
@@ -161,4 +161,9 @@ const customizingActive = computed(() => {
.color-picker {
margin-left: auto;
}
#absolute-stroke-width {
flex-direction: row-reverse;
}
</style>

View File

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

View File

@@ -2,7 +2,7 @@
import { ref, inject, Ref } from 'vue';
export const ICON_STYLE_CONTEXT = Symbol('style');
export const ICON_STYLE_CONTEXT = Symbol('size');
interface IconSizeContext {
size: Ref<number>;

View File

@@ -25,7 +25,6 @@
--vp-code-editor-string: #032f62;
--vp-c-text-4: rgba(60, 60, 67, 0.32);
--vp-home-hero-name-color: var(--vp-c-text);
}
.dark {
@@ -61,15 +60,36 @@
.VPHomeHero .image-container {
transform: none;
width: 100%;
/* padding: 0 24px; */
}
.VPHomeHero .name::first-line {
/* .VPHomeHero .container {
flex-direction: column-reverse;
} */
.VPHomeHero .container .main {
/* flex:1; */
flex-shirk: 0;
}
.VPHomeHero .container .main h1.name {
color: var(--vp-c-text);
}
.VPHomeHero .container .main h1.name .clip {
color: inherit;
-webkit-text-fill-color: unset;
color: var(--vp-c-text);
font-size: 36px;
}
.VPHomeHero .container .main h1::first-line {
color: var(--vp-c-brand);
}
/* */
.VPHomeHero .container .image {
margin: 0;
order: 2;
/* flex: 1; */
margin-top: 32px;
}
@@ -78,6 +98,10 @@
justify-content: flex-end;
}
.VPHomeHero .container .image-bg {
display: none;
}
.VPFeature .icon {
background-color: var(--vp-c-bg);
}
@@ -91,6 +115,12 @@
padding-left: 0;
}
@media (min-width: 640px) {
.VPHomeHero .container .main h1.name .clip {
font-size: unset;
}
}
@media (min-width: 960px) {
.VPHomeHero .container .image {
order: 1;
@@ -110,11 +140,18 @@
.VPHomeHero .container .image-container {
display: block;
}
.VPHomeHero .container .main h1.name {
}
}
.VPNavBarHamburger .container > span {
border-radius: 2px;
}
/*
html:has(* .outline-link:target) {
scroll-behavior: smooth;
} */
.sp-wrapper + * {
margin-top: 24px;
@@ -146,6 +183,7 @@
font-size: 14px;
font-weight: 500;
position: relative;
/* box-sizing: content-box; */
}
.sp-wrapper .sp-tabs .sp-tab-button:after {

View File

@@ -6,9 +6,6 @@ export interface IconMetaData {
categories: string[];
contributors: string[];
aliases?: string[];
deprecated?: boolean;
deprecationReason?: string;
toBeRemovedInVersion?: string;
}
export type ExternalLibs = 'lab';
@@ -36,19 +33,12 @@ interface Shield {
export interface PackageItem {
name: string;
// set when the package's directory
// name under the `packages/` directory
// is diffrent from the package name
packageDirname?: string;
description: string;
icon: string;
iconDark: string;
shields: Shield[];
source: string;
documentation: string;
// set when the docs page name is
// diffrent from the package name
docsAlias?: string;
order?: number;
private?: boolean;
flutter?: object;

View File

@@ -139,22 +139,22 @@ contained icon.
```tsx
// Don't do this
<button class="btn-icon">
<House/>
<Home/>
</button>
// Don't do this either
<button class="btn-icon">
<House aria-label="Home icon"/>
<Home aria-label="Home icon"/>
</button>
// This works but might not be the best solution, see below
<button aria-label="Go to home" class="btn-icon">
<House/>
<Home/>
</button>
// Do this instead
<button class="btn-icon">
<House/>
<Home/>
<span class="visually-hidden">Go to home</span>
</button>
```
@@ -167,7 +167,6 @@ Although you could provide accessible labels to your elements via the `aria-labe
generally recommend avoiding this and instead suggest that you use your chosen CSS framework's "
visually hidden" utility whenever possible. You can
[read more about why `aria-label` might not be the best solution](https://gomakethings.com/revisting-aria-label-versus-a-visually-hidden-class/).
### Example - Radix UI
Use [Radix UI's built-in accessible icon utility component](https://www.radix-ui.com/primitives/docs/utilities/accessible-icon).
@@ -180,7 +179,6 @@ import { AccessibleIcon } from '@radix-ui/react-accessible-icon';
<ArrowRightIcon />
</AccessibleIcon>
```
### Example - Bootstrap
```html

View File

@@ -7,9 +7,9 @@ Beside aliases names lucide also includes prefixed and suffixed names to use wit
```tsx
// These are all the same icon
import {
House,
HouseIcon,
LucideHouse,
Home,
HomeIcon,
LucideHome,
} from "lucide-react";
```

View File

@@ -42,11 +42,7 @@ Here are rules that should be followed to keep quality and consistency when maki
### 8. Distinct elements must have **2 pixels of spacing between each other**
![2px-element-spacing](../../images/2px-element-spacing.svg?raw=true '2px-element-spacing')
![2px-element-spacing-connected](../../images/2px-element-spacing-connected.svg?raw=true '2px-element-spacing-connected')
![2px-element-spacing-abrupt-cut](../../images/2px-element-spacing-abrupt-cut.svg?raw=true '2px-element-spacing-abrupt-cut')
![2px-element-spacing](../../images/2px-element-spacing.svg?raw=true "2px-element-spacing")
### 9. Icons should have a similar optical volume to `circle` and `square`.
@@ -146,7 +142,7 @@ For each icon these attributes are applied, corresponding to the above rules.
### Minify paths
The code of paths can sometimes get quite large. To reduce file size we like to minify the code.
We recommend to use [Lucide Studio](https://studio.lucide.dev/) to tidy paths to 3 points of precision.
We recommend to use [SVGOMG](https://jakearchibald.github.io/svgomg/) to minify paths to 2 points of precision.
### Allowed elements

View File

@@ -7,7 +7,7 @@ nextPage:
# What is Lucide?
Lucide is an open-source icon library that provides 1000+ vector (svg) files for displaying icons and symbols in digital and non-digital projects. The library aims to make it easier for designers and developers to incorporate icons into their projects by providing several official [packages](/packages).
Lucide is an open-source icon library that provides 1000+ vector (svg) files for displaying icons and symbols in digital and non-digital projects. The library aims to make it easier for designers and developers to incorporate icons into their projects by providing several official [packages](/packages) to make it easier to use these icons in your project.
## Available Icons
@@ -29,8 +29,7 @@ However, not everyone can understand them easily. Read more about [how to use Lu
## Official Packages
Lucide's official packages are designed to work on different platforms, making it easier for users to integrate icons into their projects. The packages are available for various technologies, including [Web (Vanilla)](https://lucide.dev/guide/packages/lucide), [React](https://lucide.dev/guide/packages/lucide-react), [React Native](https://lucide.dev/guide/packages/lucide-react-native), [Vue](https://lucide.dev/guide/packages/lucide-vue), [Vue 3](https://lucide.dev/guide/packages/lucide-vue-next), [Svelte](https://lucide.dev/guide/packages/lucide-svelte), [Preact](https://lucide.dev/guide/packages/lucide-preact), [Solid](https://lucide.dev/guide/packages/lucide-solid), [Angular](https://lucide.dev/guide/packages/lucide-angular), [Astro](https://lucide.dev/guide/packages/lucide-astro), and [NodeJS](https://lucide.dev/guide/packages/lucide-static#nodejs).
Lucide's official packages are designed to work on different platforms, making it easier for users to integrate icons into their projects. The packages are available for various technologies, including [Web (Vanilla)](https://lucide.dev/guide/packages/lucide), [React](https://lucide.dev/guide/packages/lucide-react), [React Native](https://lucide.dev/guide/packages/lucide-react-native), [Vue](https://lucide.dev/guide/packages/lucide-vue), [Vue 3](https://lucide.dev/guide/packages/lucide-vue-next), [Svelte](https://lucide.dev/guide/packages/lucide-svelte), [Preact](https://lucide.dev/guide/packages/lucide-preact), [Solid](https://lucide.dev/guide/packages/lucide-solid), [Angular](https://lucide.dev/guide/packages/lucide-angular), [NodeJS](https://lucide.dev/guide/packages/lucide-static#nodejs) and [Flutter](https://lucide.dev/guide/packages/lucide-flutter).
## Community
If you have any questions about Lucide, feel free to reach out to the community. You can find them on [GitHub](https://github.com/lucide-icons/lucide) and [Discord](https://discord.gg/EH6nSts).

View File

@@ -184,37 +184,8 @@ npm install lucide-preact
bun add lucide-preact
```
:::
For more details, see the [documentation](packages/lucide-preact.md).
## Astro
Implementation of the lucide icon library for Astro applications.
::: code-group
```sh [pnpm]
pnpm install @lucide/astro
```
```sh [yarn]
yarn add @lucide/astro
```
```sh [npm]
npm install @lucide/astro
```
```sh [bun]
bun add @lucide/astro
```
:::
For more details, see the [documentation](packages/lucide-astro.md).
## Static usage
Implementation of the lucide icon library for multiple usages that like to use: SVG files icons, SVG Sprite, Icon Fonts and static SVG strings export in Common JS modules (for NodeJS).

View File

@@ -31,11 +31,11 @@ bun add lucide-angular
In any Angular module you wish to use Lucide icons in, you have to import `LucideAngularModule`, and pick any icons you wish to use:
```js
import { LucideAngularModule, File, House, Menu, UserCheck } from 'lucide-angular';
import { LucideAngularModule, File, Home, Menu, UserCheck } from 'lucide-angular';
@NgModule({
imports: [
LucideAngularModule.pick({File, House, Menu, UserCheck})
LucideAngularModule.pick({File, Home, Menu, UserCheck})
]
})
export class AppModule { }
@@ -64,7 +64,7 @@ Within your templates you may now use one of the following component tags to ins
```html
<lucide-angular name="file" class="my-icon"></lucide-angular>
<lucide-icon name="house" class="my-icon"></lucide-icon>
<lucide-icon name="home" class="my-icon"></lucide-icon>
<i-lucide name="menu" class="my-icon"></i-lucide>
<span-lucide name="user-check" class="my-icon"></span-lucide>
```
@@ -88,7 +88,7 @@ You can pass additional props to adjust the icon appearance.
| `absoluteStrokeWidth` | *boolean* | false |
```html
<i-lucide name="house" [size]="48" color="red" [strokeWidth]="1"></i-lucide>
<i-lucide name="home" [size]="48" color="red" [strokeWidth]="1"></i-lucide>
```
### Global configuration
@@ -151,11 +151,11 @@ They can be used in the same way as the official icons.
```js
import { LucideAngularModule } from 'lucide-angular';
import { coconut } from '@lucide/lab';
import { burger } from '@lucide/lab';
@NgModule({
imports: [
LucideAngularModule.pick({ coconut })
LucideAngularModule.pick({ burger })
]
})
export class AppModule { }

View File

@@ -1,187 +0,0 @@
# Lucide Astro
Implementation of the lucide icon library for Astro applications.
## Installation
::: code-group
```sh [pnpm]
pnpm add @lucide/astro
```
```sh [yarn]
yarn add @lucide/astro
```
```sh [npm]
npm install @lucide/astro
```
```sh [bun]
bun add @lucide/astro
```
:::
## How to use
Lucide is built with ES Modules, so it's completely tree-shakable.
Each icon can be imported as an Astro component, which renders an inline SVG element. This way, only the icons that are imported into your project are included in the final bundle. The rest of the icons are tree-shaken away.
### Example
Default usage:
```astro
---
import { Skull } from '@lucide/astro';
---
<Skull />
```
Additional props can be passed to adjust the icon:
```astro
---
import { Camera } from '@lucide/astro';
---
<Camera color="#ff3e98" />
```
For faster builds and load times, you can import icons directly from the `@lucide/astro/icons` directory:
```astro
---
import CircleAlert from '@lucide/astro/icons/circle-alert';
---
<CircleAlert color="#ff3e98" />
```
## Props
| name | type | default |
| --------------------- | --------- | ------------ |
| `size` | _number_ | 24 |
| `color` | _string_ | currentColor |
| `stroke-width` | _number_ | 2 |
| `absoluteStrokeWidth` | _boolean_ | false |
### Applying props
To customize the appearance of an icon, you can pass custom properties as props directly to the component. The component accepts all SVG attributes as props, which allows flexible styling of the SVG elements. See the list of SVG Presentation Attributes on [MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation).
```astro
---
import { Phone } from '@lucide/astro';
---
<Phone fill="#333" />
```
This results a filled phone icon.
## Types
The package includes type definitions for all icons. This is useful if you want to dynamically render icons.
### Example
```astro
---
import { House, Library, Cog, type Icon as IconType } from '@lucide/astro';
type MenuItem = {
name: string;
href: string;
icon: typeof IconType;
};
const menuItems: MenuItem[] = [
{
name: 'Home',
href: '/',
icon: House,
},
{
name: 'Blog',
href: '/blog',
icon: Library,
},
{
name: 'Projects',
href: '/projects',
icon: Cog,
},
];
---
{
menuItems.map((item) => (
<a href={item.href}>
<item.icon />
<span>{item.name}</span>
</a>
))
}
```
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used by using the `Icon` component.
All props of the regular Lucide icons can be passed to adjust the icon appearance.
### Using the `Icon` component
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
```astro
---
import { Icon } from '@lucide/astro';
import { burger, sausage } from '@lucide/lab';
---
<Icon iconNode={burger} />
<Icon iconNode={sausage} color="red"/>
```
## One generic icon component
It is possible to create one generic icon component to load icons, but it is not recommended.
::: danger
The example below imports all ES Modules, so exercise caution when using it. Importing all icons will significantly increase the build size of the application. This may be passable if you're doing SSG and SSR in server environments. However if you're doing SSR in serverless environments, it could negatively affect your app's performance, as a bigger bundle size will translate to an increase in cold starts.
:::
### Icon Component Example
```astro
---
import { icons, type IconProps } from '@lucide/astro';
interface Props extends IconProps {
name: keyof typeof icons;
}
const { name, ...restProps } = Astro.props;
const Icon = icons[name];
---
<Icon {...restProps} />
```
### Using the Icon Component
```astro
---
import LucideIcon from './LucideIcon.astro';
---
<LucideIcon name="Menu" />
```

View File

@@ -84,10 +84,10 @@ This creates a single icon based on the iconNode passed and renders a Lucide ico
```jsx
import { Icon } from 'lucide-preact';
import { coconut } from '@lucide/lab';
import { burger } from '@lucide/lab';
const App = () => (
<Icon iconNode={coconut} />
<Icon iconNode={burger} />
);
```
@@ -119,7 +119,7 @@ export default Icon;
import Icon from './Icon';
const App = () => {
return <Icon name="house" />;
return <Icon name="home" />;
};
export default App;

View File

@@ -78,10 +78,10 @@ This creates a single icon based on the iconNode passed and renders a Lucide ico
```jsx
import { Icon } from 'lucide-react-native';
import { coconut } from '@lucide/lab';
import { burger } from '@lucide/lab';
const App = () => (
<Icon iconNode={coconut} />
<Icon iconNode={burger} />
);
```
@@ -113,7 +113,7 @@ export default Icon;
import Icon from './Icon';
const App = () => {
return <Icon name="house" />;
return <Icon name="home" />;
};
export default App;

View File

@@ -78,16 +78,17 @@ This creates a single icon based on the iconNode passed and renders a Lucide ico
```jsx
import { Icon } from 'lucide-react';
import { coconut } from '@lucide/lab';
import { burger } from '@lucide/lab';
const App = () => (
<Icon iconNode={coconut} />
<Icon iconNode={burger} />
);
```
## Dynamic Icon Component
It is possible to create one generic icon component to load icons. But it is not recommended, since it is importing all icons during the build. This increases build time and the different modules it will create.
It is possible to create one generic icon component to load icons, but it is not recommended.
Since it is importing all icons during build. This increases build time and the different modules it will create.
`DynamicIcon` is useful for applications that want to show icons dynamically by icon name. For example, when using a content management system with where icon names are stored in a database.

View File

@@ -91,7 +91,7 @@ This creates a single icon based on the iconNode passed and renders a Lucide ico
```jsx
import { Icon } from 'lucide-solid';
import { sausage } from '@lucide/lab';
import { burger, sausage } from '@lucide/lab';
const App = () => (
<Icon iconNode={sausage} color="red"/>

View File

@@ -1,38 +1,24 @@
# Lucide Static
This package includes the following implementations of Lucide icons:
This package include the following lucide implementations:
- Individual SVG files
- All SVG files
- SVG sprite
- Icon font files
- A JavaScript library exporting SVG strings
- Icon fonts
- JavaScript library containing strings of SVGs.
## Who is this for?
## Why lucide-static?
`lucide-static` is suitable for _very specific use cases_ where you want to use Lucide icons without relying on a JavaScript framework or component system. It's ideal for:
This package is suitable for specific use cases, for example if you want to use icon fonts, SVG sprites, normal SVGs or Common.js SVG strings in your javascript project.
- Projects that use icon fonts with plain CSS or utility-first frameworks
- Embedding raw SVG files or sprites directly in HTML
- Using SVGs as CSS background images
- Importing SVG strings into Node.js (CommonJS) environments
::: danger
### Not recommended for production {#production-warning}
SVG sprites and icon fonts include **all icons**, which can significantly increase your app's bundle size and load time.
For production environments, we recommend using a bundler with tree-shaking support to include only the icons you actually use. Consider using:
- [lucide](lucide)
- [lucide-react](lucide-react)
- [lucide-vue](lucide-vue)
- [lucide-vue-next](lucide-vue-next)
- [lucide-angular](lucide-angular)
- [lucide-preact](lucide-preact)
::: warning
While they can be useful for prototyping, it is not recommended to use the SVG sprites or icon fonts provided by this package in production web apps as all the available icons are included in the app, hence increasing loading time and data usage. We recommend to use a bundler and tree-shaking to make sure only the icons you use are bundled with your app. Tree-shaking is only available in these packages: [lucide](lucide), [lucide-react](lucide-react), [lucide-vue](lucide-vue), [lucide-vue-next](lucide-vue-next), [lucide-angular](lucide-angular), [lucide-preact](lucide-preact)
:::
## Installation
## Package Managers
::: code-group
```sh [pnpm]
@@ -53,86 +39,64 @@ bun add lucide-static
:::
## SVG Files
### CDN
You can use standalone SVG files or SVG sprites in several ways.
```html
<!-- SVG file for a single icon -->
<img src="https://unpkg.com/lucide-static@latest/icons/house.svg" />
Check out our [codesandbox example](https://codesandbox.io/s/using-the-svg-sprite-lz1kk).
<!-- Icon font -->
<style>
@font-face {
font-family: 'LucideIcons';
src: url(https://unpkg.com/lucide-static@latest/font/Lucide.ttf) format('truetype');
}
</style>
```
### SVG file as image
## Usage
#### In HTML:
Check out the [codesandbox examples](https://codesandbox.io/s/using-the-svg-sprite-lz1kk).
::: code-group
### SVG Files
```html [Webpack]
#### SVG file as image
To use it in for example html:
```html
<!-- SVG file for a single icon -->
<img src="~lucide-static/icons/house.svg" />
```
```html [CDN]
<!-- SVG file for a single icon -->
<img src="https://unpkg.com/lucide-static@latest/icons/house.svg" />
```
:::
#### In CSS:
::: code-group
```css [Webpack]
```css
.house-icon {
background-image: url(~lucide-static/icons/house.svg);
}
```
```css [CDN]
.house-icon {
background-image: url(https://unpkg.com/lucide-static@latest/icons/house.svg);
}
```
Make sure you have the correct webpack loaders to make this work. [url-loader](https://v4.webpack.js.org/loaders/url-loader/)
:::
#### SVG file as string
Make sure you have the correct Webpack loader configured, such as [`url-loader`](https://v4.webpack.js.org/loaders/url-loader/).
You can simply import each SVG by targeting `lucide-static/icons/{icon-name}.svg`.
To use SVGs in your project you can for example use a [SVG loader](https://v4.webpack.js.org/loaders/svg-inline-loader/).
### SVG file as string
To import an SVG as a string (e.g., for templating):
::: code-group
```js [Webpack]
```js
import arrowRightIcon from 'lucide-static/icons/arrow-right';
// return string of an SVG
```
```js [Vite]
import arrowRightIcon from 'lucide-static/icons/arrow-right.svg?raw';
```
### SVG Sprite
:::
You'll need an SVG loader like [`svg-inline-loader`](https://v4.webpack.js.org/loaders/svg-inline-loader/).
### Using the SVG sprite
:::danger
[Not intended for production use.](#production-warning)
:::
You may also need an additional SVG loader to handle this.
#### Basic sprite usage (not production-optimized):
You may need additional loader for this.
```html
<!-- Icon Sprite, not recommended for production! -->
<img src="lucide-static/sprite.svg#house" />
```
#### Inline usage:
```html
<!-- or -->
<svg
width="24"
height="24"
@@ -145,13 +109,12 @@ You may also need an additional SVG loader to handle this.
<use href="#alert-triangle" />
</svg>
<!-- sprite SVG -->
<svg>...</svg>
<svg>
...sprite svg
</svg>
```
#### Inline with CSS helper class
If you'd prefer, you can use CSS to hold your base SVG properties:
If you'd prefer, you can use CSS to hold your base SVG properties
```css
.lucide-icon {
@@ -165,79 +128,47 @@ If you'd prefer, you can use CSS to hold your base SVG properties:
}
```
...and update the SVG as follows:
and update the SVG as follows
```xml
<svg
xmlns="http://www.w3.org/2000/svg"
class="lucide-icon"
>
<use href="#triangle-alert" />
<use
href="#alert-triangle"
/>
</svg>
<svg>
...sprite svg
</svg>
<!-- sprite SVG -->
<svg>...</svg>
```
## Icon font
### Icon Font
:::danger
[Not intended for production use.](#production-warning)
:::
Lucide icons are also available as a web font. To use them, you first need to include the corresponding stylesheet:
::: code-group
```css [Vite]
@import 'lucide-static/font/lucide.css';
```
```css [Webpack]
```css
@import ('~lucide-static/font/lucide.css');
```
```html [CDN]
<link rel="stylesheet" href="https://unpkg.com/lucide-static@latest/font/lucide.css" />
```
```html [Static asset]
<link rel="stylesheet" href="/your/path/to/lucide.css" />
```
:::
Once included, use the format `icon-{kebab-case-name}`. You can copy icon names from the [Lucide Icons page](https://lucide.dev/icons).
```html
<div class="icon-house"></div>
```
If you're not using a package manager, you can download the font files directly from the [latest release](https://github.com/lucide-icons/lucide/releases/latest).
### Node.js
## Node.js
You can also import Lucide icons in Node.js projects:
::: code-group
```js [ESM]
import {MessageSquare} from 'lucide-static';
```
```js [CommonJs]
const {MessageSquare} = require('lucide-static');
```
:::
> Note: Each icon name is in PascalCase.
#### Express app example in Node.js
To use lucide icons in your Nodejs project you can import each icon as:
```js
import express from 'express';
import {MessageSquare} from 'lucide-static';
const { messageSquare } = require('lucide-static/lib');
```
> Note: Each icon name is in camelCase.
#### Example in node.js project
```js
const express = require('express');
const { messageSquare } = require('lucide-static/lib');
const app = express();
const port = 3000;
@@ -250,7 +181,7 @@ app.get('/', (req, res) => {
</head>
<body>
<h1>Lucide Icons</h1>
<p>This is a Lucide icon ${MessageSquare}</p>
<p>This is a lucide icon ${messageSquare}</p>
</body>
</html>

View File

@@ -7,22 +7,22 @@ Implementation of the lucide icon library for svelte applications.
::: code-group
```sh [pnpm]
pnpm add @lucide/svelte
pnpm add lucide-svelte
```
```sh [yarn]
yarn add @lucide/svelte
yarn add lucide-svelte
```
```sh [npm]
npm install @lucide/svelte
npm install lucide-svelte
```
```sh [bun]
bun add @lucide/svelte
bun add lucide-svelte
```
:::
> `@lucide/svelte` is only for Svelte 5, for Svelte 4 use the `lucide-svelte` package.
## How to use
@@ -36,7 +36,7 @@ Default usage:
```svelte
<script>
import { Skull } from '@lucide/svelte';
import { Skull } from 'lucide-svelte';
</script>
<Skull />
@@ -46,17 +46,17 @@ Additional props can be passed to adjust the icon:
```svelte
<script>
import { Camera } from '@lucide/svelte';
import { Camera } from 'lucide-svelte';
</script>
<Camera color="#ff3e98" />
```
For faster builds and load times, you can import icons directly from the `@lucide/svelte/icons` directory:
For faster builds and load times, you can import icons directly from the `lucide-svelte/icons` directory:
```svelte
<script>
import CircleAlert from '@lucide/svelte/icons/circle-alert';
import CircleAlert from 'lucide-svelte/icons/circle-alert';
</script>
<CircleAlert color="#ff3e98" />
@@ -77,7 +77,7 @@ To customize the appearance of an icon, you can pass custom properties as props
```svelte
<script>
import { Phone } from '@lucide/svelte';
import { Phone } from 'lucide-svelte';
</script>
<Phone fill="#333" />
@@ -91,11 +91,53 @@ The package includes type definitions for all icons. This is useful if you want
### TypeScript Example
::: code-group
#### Svelte 4
```svelte [Svelte 5]
```svelte
<script lang="ts">
import { Home, Library, Cog, type Icon as IconType } from '@lucide/svelte';
import { Home, Library, Cog, type Icon } from 'lucide-svelte';
import type { ComponentType } from 'svelte';
type MenuItem = {
name: string;
href: string;
icon: ComponentType<Icon>;
};
const menuItems: MenuItem[] = [
{
name: 'Home',
href: '/',
icon: Home
},
{
name: 'Blog',
href: '/blog',
icon: Library
},
{
name: 'Projects',
href: '/projects',
icon: Cog
}
];
</script>
{#each menuItems as item}
<a href={item.href}>
<svelte:component this={item.icon} />
<span>{item.name}</span>
</a>
{/each}
```
#### Svelte 5
Some changes are required since Svelte 5 [deprecates](https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes-Component-typing-changes) the `ComponentType` typescript type.
```svelte
<script lang="ts">
import { Home, Library, Cog, type Icon as IconType } from 'lucide-svelte';
type MenuItem = {
name: string;
@@ -131,100 +173,19 @@ The package includes type definitions for all icons. This is useful if you want
{/each}
```
```svelte [Svelte 4]
<script lang="ts">
import { Home, Library, Cog, type Icon } from '@lucide/svelte';
import type { ComponentType } from 'svelte';
type MenuItem = {
name: string;
href: string;
icon: ComponentType<Icon>;
};
const menuItems: MenuItem[] = [
{
name: 'Home',
href: '/',
icon: Home
},
{
name: 'Blog',
href: '/blog',
icon: Library
},
{
name: 'Projects',
href: '/projects',
icon: Cog
}
];
</script>
{#each menuItems as item}
{@const Icon = item.icon}
<a href={item.href}>
<Icon />
<span>{item.name}</span>
</a>
{/each}
```
:::
### JSDoc Example
::: code-group
#### Svelte 4
```svelte [Svelte 5]
```svelte
<script>
import { Home, Library, Cog } from '@lucide/svelte';
import { Home, Library, Cog } from 'lucide-svelte';
/**
* @typedef {Object} MenuItem
* @property {string} name
* @property {string} href
* @property {typeof import('@lucide/svelte').Icon} icon
*/
/** @type {MenuItem[]} */
const menuItems = [
{
name: 'Home',
href: '/',
icon: Home
},
{
name: 'Blog',
href: '/blog',
icon: Library
},
{
name: 'Projects',
href: '/projects',
icon: Cog
}
];
</script>
{#each menuItems as item}
{@const Icon = item.icon}
<a href={item.href}>
<Icon />
<span>{item.name}</span>
</a>
{/each}
```
```svelte [Svelte 4]
<script>
import { Home, Library, Cog } from '@lucide/svelte';
/**
* @typedef {Object} MenuItem
* @property {string} name
* @property {string} href
* @property {import('svelte').ComponentType<import('@lucide/svelte').Icon>} icon
* @property {import('svelte').ComponentType<import('lucide-svelte').Icon>} icon
*/
/** @type {MenuItem[]} */
@@ -247,6 +208,48 @@ The package includes type definitions for all icons. This is useful if you want
];
</script>
{#each menuItems as item}
<a href={item.href}>
<svelte:component this={item.icon} />
<span>{item.name}</span>
</a>
{/each}
```
#### Svelte 5
```svelte
<script>
import { Home, Library, Cog } from 'lucide-svelte';
/**
* @typedef {Object} MenuItem
* @property {string} name
* @property {string} href
* @property {typeof import('lucide-svelte').Icon} icon
*/
/** @type {MenuItem[]} */
const menuItems = [
{
name: 'Home',
href: '/',
icon: Home
},
{
name: 'Blog',
href: '/blog',
icon: Library
},
{
name: 'Projects',
href: '/projects',
icon: Cog
}
];
</script>
{#each menuItems as item}
{@const Icon = item.icon}
<a href={item.href}>
@@ -254,9 +257,8 @@ The package includes type definitions for all icons. This is useful if you want
<span>{item.name}</span>
</a>
{/each}
```
:::
```
For more details about typing the `svelte:component` directive, see the [Svelte documentation](https://svelte.dev/docs/typescript#types-componenttype).
@@ -273,11 +275,11 @@ This creates a single icon based on the iconNode passed and renders a Lucide ico
```svelte
<script>
import { Icon } from '@lucide/svelte';
import { pear, sausage } from '@lucide/lab';
import { Icon } from 'lucide-svelte';
import { burger, sausage } from '@lucide/lab';
</script>
<Icon iconNode={pear} />
<Icon iconNode={burger} />
<Icon iconNode={sausage} color="red"/>
```
@@ -291,29 +293,29 @@ The example below imports all ES Modules, so exercise caution when using it. Imp
### Icon Component Example
::: code-group
#### Svelte 4
```svelte [Svelte 5]
```svelte
<script>
import * as icons from '@lucide/svelte';
let { name, ...props } = $props();
const Icon = icons[name];
</script>
<Icon {...props} />
```
```svelte [Svelte 4]
<script>
import * as icons from '@lucide/svelte';
import * as icons from 'lucide-svelte';
export let name;
</script>
<svelte:component this={icons[name]} {...$$props} />
```
:::
#### Svelte 5
```svelte
<script>
import * as icons from 'lucide-svelte';
let { name } = $props();
const Icon = icons[name];
</script>
<Icon {...props} />
```
#### Using the Icon Component

View File

@@ -54,7 +54,7 @@ import { Camera } from 'lucide-vue-next';
| `size` | *number* | 24 |
| `color` | *string* | currentColor |
| `stroke-width` | *number* | 2 |
| `absoluteStrokeWidth` | *boolean* | false |
| `absolute-stroke-width` | *boolean* | false |
| `default-class` | *string* | lucide-icon |
### Applying props
@@ -81,11 +81,11 @@ This creates a single icon based on the iconNode passed and renders a Lucide ico
```vue
<script setup>
import { Icon } from 'lucide-vue-next';
import { baseball } from '@lucide/lab';
import { burger } from '@lucide/lab';
</script>
<template>
<Icon :iconNode="baseball" />
<Icon :iconNode="burger" />
</template>
```

View File

@@ -60,7 +60,7 @@ Additional props can be passed to adjust the icon:
| `size` | *number* | 24 |
| `color` | *string* | currentColor |
| `stroke-width` | *number* | 2 |
| `absoluteStrokeWidth` | *boolean* | false |
| `absolute-stroke-width` | *boolean* | false |
| `default-class` | *string* | lucide-icon |
### Applying props

View File

@@ -88,11 +88,7 @@ createIcons({
### Additional Options
In the `createIcons` function you can pass some extra parameters:
- you can pass `nameAttr` to adjust the attribute name to replace for
- you can pass `attrs` to pass additional custom attributes, for instance CSS classes or stroke options.
- you can pass `root` to provide a custom DOM element the icons should be replaced in (useful when manipulating small sections of a large DOM or elements in the shadow DOM)
In the `createIcons` function you can pass some extra parameters to adjust the `nameAttr` or add custom attributes like for example classes.
Here is a full example:
@@ -105,8 +101,7 @@ createIcons({
'stroke-width': 1,
stroke: '#333'
},
nameAttr: 'data-lucide', // attribute for the icon name.
root: element, // DOM element to replace icons in.
nameAttr: 'data-lucide' // attribute for the icon name.
});
```
@@ -131,23 +126,11 @@ import { createElement, Menu } from 'lucide';
const menuIcon = createElement(Menu); // Returns HTMLElement (svg)
// Append HTMLElement in the DOM
const myApp = document.getElementById('app');
myApp.appendChild(menuIcon);
```
// set custom attributes with browser native functions
menuIcon.setAttribute('stroke', '#333');
menuIcon.classList.add('my-icon-class');
#### Custom Element binding with custom attributes
```js
import { createElement, Menu } from 'lucide';
const menuIcon = createElement(Menu, {
class: ['my-custom-class', 'icon'],
'stroke-width': 1,
stroke: '#333'
}); // Returns HTMLElement (svg)
// Append HTMLElement in the DOM
// Append HTMLElement in webpage
const myApp = document.getElementById('app');
myApp.appendChild(menuIcon);
```
@@ -158,11 +141,11 @@ myApp.appendChild(menuIcon);
They can be used in the same way as the official icons.
```js
import { coconut } from '@lucide/lab';
import { burger } from '@lucide/lab';
createIcons({
icons: {
coconut
burger
}
});
```

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@@ -48,12 +48,10 @@
<path d="M340 120v240" stroke="#D8D8D9" stroke-width=".5"/>
<path d="M120 350h240" stroke="#D8D8D9" stroke-width=".5"/>
<path d="M350 120v240" stroke="#D8D8D9" stroke-width=".5"/>
</g>
<g id="embed-lucide-layers" stroke="#3C3C43" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" transform="translate(120 120), scale(10)">
<path d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z" />
<path d="M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12" />
<path d="M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17" />
</g>
</g>
<path d="m240 140-100 50 100 50 100-50-100-50Z" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<path d="m140 290 100 50 100-50" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<path d="m140 240 100 50 100-50" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<g opacity=".5" stroke="#fff" stroke-width="3">
<path d="M120 92v16"/>
<path d="M360 92v16"/>

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.6 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -53,13 +53,9 @@
<path d="M350 120V360" stroke="#D8D8D9" stroke-width="0.5"/>
</g>
<g clip-path="url(#clip1_1612_15659)">
<g id="embed-lucide-heart" stroke="#F56565" stroke-width="7" stroke-dasharray="0.04 0.08" stroke-linejoin="round" transform="translate(120 120), scale(10)">
<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z" />
</g>
</g>
<g id="embed-lucide-heart" stroke="#3C3C43" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" transform="translate(120 120), scale(10)">
<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z" />
<path d="M310 260C324.9 245.4 340 227.9 340 205C340 190.413 334.205 176.424 323.891 166.109C313.576 155.795 299.587 150 285 150C267.4 150 255 155 240 170C225 155 212.6 150 195 150C180.413 150 166.424 155.795 156.109 166.109C145.795 176.424 140 190.413 140 205C140 228 155 245.5 170 260L240 330L310 260Z" stroke="#F56565" stroke-width="60" stroke-linejoin="round" stroke-dasharray="0.2 0.4"/>
</g>
<path d="M310 260C324.9 245.4 340 227.9 340 205C340 190.413 334.205 176.424 323.891 166.109C313.576 155.795 299.587 150 285 150C267.4 150 255 155 240 170C225 155 212.6 150 195 150C180.413 150 166.424 155.795 156.109 166.109C145.795 176.424 140 190.413 140 205C140 228 155 245.5 170 260L240 330L310 260Z" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<defs>
<clipPath id="clip0_1612_15659">
<rect x="120" y="120" width="240" height="240" rx="8" fill="white"/>

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -52,18 +52,16 @@
<path d="M120 350H360" stroke="#D8D8D9" stroke-width="0.5"/>
<path d="M350 120V360" stroke="#D8D8D9" stroke-width="0.5"/>
</g>
<g id="embed-lucide-calendar-days" stroke="#3C3C43" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" transform="translate(120 120), scale(10)">
<path d="M8 2v4" />
<path d="M16 2v4" />
<rect width="18" height="18" x="3" y="4" rx="2" />
<path d="M3 10h18" />
<path d="M8 14h.01" />
<path d="M12 14h.01" />
<path d="M16 14h.01" />
<path d="M8 18h.01" />
<path d="M12 18h.01" />
<path d="M16 18h.01" />
</g>
<path d="M310 160H170C158.954 160 150 168.954 150 180V320C150 331.046 158.954 340 170 340H310C321.046 340 330 331.046 330 320V180C330 168.954 321.046 160 310 160Z" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M280 140V180" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M200 140V180" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M150 220H330" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M200 260H200.1" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M240 260H240.1" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M280 260H280.1" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M200 300H200.1" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M240 300H240.1" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M280 300H280.1" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<defs>
<clipPath id="clip0_1612_15298">
<rect x="120" y="120" width="240" height="240" rx="8" fill="white"/>

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -54,12 +54,10 @@
</g>
<circle opacity="0.17" cx="240" cy="240" r="100" stroke="#F56565" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<rect opacity="0.17" x="150" y="150" width="180" height="180" rx="20" stroke="#F56565" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<g id="embed-lucide-expand" stroke="#3C3C43" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" transform="translate(120 120), scale(10)">
<path d="m21 21-6-6m6 6v-4.8m0 4.8h-4.8" />
<path d="M3 16.2V21m0 0h4.8M3 21l6-6" />
<path d="M21 7.8V3m0 0h-4.8M21 3l-6 6" />
<path d="M3 7.8V3m0 0h4.8M3 3l6 6" />
</g>
<path d="M330 330L270 270M330 330V282M330 330H282" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M150 282V330M150 330H198M150 330L210 270" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M330 198V150M330 150H282M330 150L270 210" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M150 198V150M150 150H198M150 150L210 210" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<circle opacity="0.5" cx="240" cy="240" r="100" stroke="#F56565" stroke-dasharray="1 1"/>
<rect opacity="0.5" x="150" y="150" width="180" height="180" rx="20" stroke="#F56565" stroke-dasharray="1 1"/>
<defs>

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -54,9 +54,7 @@
<path d="M120 350H360" stroke="#D8D8D9" stroke-width="0.5"/>
<path d="M350 120V360" stroke="#D8D8D9" stroke-width="0.5"/>
</g>
<g id="embed-lucide-mouse-pointer-2" stroke="#3C3C43" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" transform="translate(110 110), scale(10)">
<path d="M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z" />
</g>
<path d="M150 150L220.7 320L245.8 246.1L320 220.7L150 150Z" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<path opacity="0.5" d="M240 120V360M360 240H120" stroke="#F56565" stroke-dasharray="1 1"/>
</g>
<defs>

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -53,9 +53,7 @@
<path d="M120 350H360" stroke="#D8D8D9" stroke-width="0.5"/>
<path d="M350 120V360" stroke="#D8D8D9" stroke-width="0.5"/>
</g>
<g id="embed-lucide-mouse-pointer-2" stroke="#3C3C43" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" transform="translate(120 120), scale(10)">
<path d="M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z" />
</g>
<path d="M160 160L230.7 330L255.8 256.1L330 230.7L160 160Z" stroke="#3C3C43" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<path opacity="0.5" d="M240 120V360M360 240H120" stroke="#F56565" stroke-dasharray="1 1"/>
</g>
<defs>

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -2,7 +2,6 @@ import copy from 'rollup-plugin-copy';
import replace from '@rollup/plugin-replace';
export default defineNitroConfig({
compatibilityDate: '2025-07-30',
preset: 'vercel_edge',
srcDir: '.vitepress',
routeRules: {

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