Compare commits

..

1 Commits

Author SHA1 Message Date
Eric Fennis
e8c7e572af export types lucide solid 2024-04-26 09:58:49 +02:00
1604 changed files with 54715 additions and 40656 deletions

View File

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

View File

@@ -1,4 +1,4 @@
const DEFAULT_ATTRS = require('./tools/build-icons/render/default-attrs.json');
const DEFAULT_ATTRS = require('./scripts/render/default-attrs.json');
module.exports = {
root: true,
@@ -15,9 +15,7 @@ module.exports = {
'no-use-before-define': 'off',
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: ['**/*.test.js', '**/*.spec.js', '**/scripts/**'],
},
{ devDependencies: ['**/*.test.js', '**/*.spec.js', './scripts/**'] },
],
'import/extensions': [
'error',

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
@@ -70,9 +69,6 @@ body:
- label: Windows
- label: Linux
- label: macOS
- label: ChromeOS
- label: iOS
- label: Android
- label: Other/not relevant
- type: textarea
id: description

View File

@@ -30,9 +30,6 @@ body:
- label: Windows
- label: Linux
- label: macOS
- label: ChromeOS
- label: iOS
- label: Android
- label: Other/not relevant
- type: textarea
id: description

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

View File

@@ -9,18 +9,20 @@ 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
with:
version: 8
- 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 +63,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

@@ -1,35 +0,0 @@
name: Close Issue with Banned Phrases
on:
issues:
types: [opened]
jobs:
block_phrases:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- 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")
# Check title and body for blocked phrases
for PHRASE in "${BLOCKED_PHRASES[@]}"
do
if echo "$ISSUE_TITLE" | grep -i "$PHRASE"; then
gh issue close ${{ github.event.issue.number }} --reason "not planned" --comment "This looks like a duplicate, use the [search](https://github.com/lucide-icons/lucide/issues?q=is%3Aissue+$PHRASE) to find similar issues.
Read more about brand guideline rules at [We're not accepting new Brand icons #670](https://github.com/lucide-icons/lucide/issues/670).
Always happy to help on [Discord](https://discord.gg/EH6nSts)."
gh issue lock ${{ github.event.issue.number }} --reason spam
exit 1
fi
done
env:
GH_TOKEN: ${{ github.token }}

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

@@ -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,36 +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
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v46
with:
files: icons/*
- name: Generate annotations
run: node ./scripts/lintFilenames.mjs
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,22 @@
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
with:
version: 8
- 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,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- 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 +29,14 @@ 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
with:
version: 8
- 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,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- 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 +30,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

@@ -4,7 +4,6 @@ on:
pull_request:
paths:
- packages/lucide-preact/**
- packages/shared/**
- tools/build-icons/**
- tools/rollup-plugins/**
- pnpm-lock.yaml
@@ -14,11 +13,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- 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

@@ -4,37 +4,22 @@ on:
pull_request:
paths:
- packages/lucide-react-native/**
- packages/shared/**
- tools/build-icons/**
- tools/rollup-plugins/**
- 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
with:
version: 8
- 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

@@ -4,7 +4,6 @@ on:
pull_request:
paths:
- packages/lucide-react/**
- packages/shared/**
- tools/build-icons/**
- tools/rollup-plugins/**
- scripts/generateNextJSAliases.mjs
@@ -15,11 +14,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- 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 +31,14 @@ 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
with:
version: 8
- 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,24 +0,0 @@
name: Lucide Shared Checks
on:
pull_request:
paths:
- packages/shared/**
- pnpm-lock.yaml
jobs:
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-react test

View File

@@ -4,7 +4,6 @@ on:
pull_request:
paths:
- packages/lucide-solid/**
- packages/shared/**
- tools/build-icons/**
- tools/rollup-plugins/**
- pnpm-lock.yaml
@@ -14,11 +13,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- 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 +30,14 @@ 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
with:
version: 8
- 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,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- 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

@@ -4,7 +4,6 @@ on:
pull_request:
paths:
- packages/lucide-svelte/**
- packages/shared/**
- tools/build-icons/**
- tools/rollup-plugins/**
- pnpm-lock.yaml
@@ -14,11 +13,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- 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 +30,14 @@ 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
with:
version: 8
- 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

@@ -4,7 +4,6 @@ on:
pull_request:
paths:
- packages/lucide-vue-next/**
- packages/shared/**
- tools/build-icons/**
- tools/rollup-plugins/**
- pnpm-lock.yaml
@@ -14,11 +13,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- 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 +30,14 @@ 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
with:
version: 8
- 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,11 @@
name: Lucide Astro Checks
name: Lucide Vue checks
on:
pull_request:
paths:
- packages/astro/**
- packages/shared/**
- packages/lucide-vue/**
- tools/build-icons/**
- tools/rollup-plugins/**
- pnpm-lock.yaml
jobs:
@@ -14,6 +14,8 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 18
@@ -23,13 +25,15 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter @lucide/astro build
run: pnpm --filter lucide-vue build
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v3.8.1
with:
node-version: 18
@@ -39,4 +43,4 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Test
run: pnpm --filter @lucide/astro test
run: pnpm --filter lucide-vue test

View File

@@ -13,11 +13,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- 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 +30,14 @@ 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
with:
version: 8
- 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,43 +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
- 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
- name: Save PR number
run: |
mkdir -p ./pr
echo ${{ github.event.number }} > ./pr/NR
- name: Generate comment markup
run: node ./scripts/generateChangedIconsCommentMarkup.mjs >> ./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/

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

@@ -0,0 +1,115 @@
name: Add Changed Icons comment
on:
pull_request_target:
paths:
- 'icons/*.svg'
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
- uses: pnpm/action-setup@v2
- name: Install simple-git (safer and faster than installing all deps)
run: pnpm 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
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
- uses: pnpm/action-setup@v2
- name: Install svgson for code preview (safer and faster than installing all deps)
run: pnpm 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:
@@ -54,16 +52,16 @@ jobs:
'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
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -81,24 +79,22 @@ 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
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -117,8 +113,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'
@@ -126,11 +120,13 @@ jobs:
needs: pre-release
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -142,7 +138,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
@@ -154,14 +150,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 }}

9
.gitignore vendored
View File

@@ -16,17 +16,11 @@ outlined
packages/**/src/icons/*.js
packages/**/src/icons/*.ts
packages/**/src/icons/*.tsx
packages/**/src/aliases/*.ts
packages/**/src/aliases.ts
!packages/**/src/aliases/index.ts
packages/**/src/dynamicIconImports.ts
packages/**/DynamicIcon.d.ts
packages/**/dynamicIconImports.js
packages/**/dynamicIconImports.d.ts
packages/**/dynamicIconImports.js.map
packages/**/dynamic.d.ts
packages/**/dynamic.mjs.map
packages/**/dynamic.mjs
packages/**/LICENSE
categories.json
tags.json
@@ -40,10 +34,7 @@ docs/.vitepress/data/iconNodes
docs/.vitepress/data/iconMetaData.ts
docs/.vitepress/data/releaseMetaData.json
docs/.vitepress/data/releaseMetaData
docs/.vitepress/data/categoriesData.json
docs/.vitepress/data/iconDetails
docs/.vitepress/data/relatedIcons.json
docs/.vercel
docs/.nitro
.gitignore

View File

@@ -2,10 +2,6 @@ pnpm-lock.yaml
# docs examples
docs/**/examples/
docs/.vitepress/.temp
docs/.vitepress/cache
docs/.vitepress/data
docs/.nitro
# lucide-angular
packages/lucide-angular/.angular/cache

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,24 @@ 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)
### 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
@@ -76,12 +70,12 @@ pnpm install # Install dependencies, including the workspace packages
### Packages -> PNPM Workspaces
To distribute different packages we use [PNPM workspaces](https://pnpm.io/workspaces). Before you start make sure you are familiar with this concept. The concept of working in workspaces is created by Yarn, they have a well written introduction: [yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces).
To distribute different packages we use PNPM workspaces. Before you start make sure you are familiar with this concept. The concept of working in workspaces is created by Yarn, they have a well written introduction: [yarn workspaces](https://classic.yarnpkg.com/lang/enhttps://lucide.dev/docs/workspaces).
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 +125,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` or `pnpm link` to link the package. Before you do this make sure you builded the package first.
```sh
# in packages/lucide-react
@@ -169,15 +163,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.
> 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.
### Scripts
### scripts
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.
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
@@ -194,4 +188,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>

268
README.md
View File

@@ -8,40 +8,185 @@
</p>
<p align="center">
<a href="https://github.com/lucide-icons/lucide/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/lucide" alt="license"></a>
<a href="https://www.figma.com/community/plugin/939567362549682242/Lucide-Icons"><img src="https://img.shields.io/badge/Figma-F24E1E?logo=figma&logoColor=white" alt="figma installs"></a>
<a href="https://www.npmjs.com/package/lucide"><img src="https://img.shields.io/npm/v/lucide" alt="npm package"></a>
<a href="https://www.figma.com/community/plugin/939567362549682242/Lucide-Icons"><img src="https://img.shields.io/endpoint?logo=figma&label=installs&url=https://yuanqing.github.io/figma-plugins-stats/plugin/939567362549682242/installs.json" alt="figma installs"></a>
<a href="https://github.com/lucide-icons/lucide/actions/workflows/release.yml"><img src="https://github.com/lucide-icons/lucide/actions/workflows/release.yml/badge.svg" alt="build status"></a>
<a href="https://discord.gg/EH6nSts"><img src="https://img.shields.io/discord/723074157486800936?label=chat&logo=discord&logoColor=%23ffffff&colorB=%237289DA" alt="discord chat"></a>
</p>
<p align="center">
<a href="https://lucide.dev/icons/">Icons</a>
·
<a href="https://lucide.dev/guide/">Guide</a>
·
<a href="https://lucide.dev/packages">Packages</a>
·
<a href="https://lucide.dev/license">License</a>
·
<a href="https://lucide.dev/showcase">Showcase</a>
</p>
# 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](https://lucide.dev/packages) to make it easier to use these icons in your project.
Community-run fork of [Feather Icons](https://github.com/feathericons/feather), open for anyone to contribute icons.
## Packages
It began after growing dissatisfaction with the [Feather Icons](https://github.com/feathericons/feather) project moderation. With over 300+ open issues and over 100+ open PRs, the Feather Icons project has been abandoned. This unfortunately means that hundreds of developers and designers wasted their time contributing to Feather Icons with no chance of PRs being accepted.
| Logo | Package | Version | Downloads | Links |
| ---- | ------- | ------- | --------- | ----- |
| <img src="https://lucide.dev/framework-logos/js.svg" alt="JS logo" width="48"> | **`lucide`** | [![npm](https://img.shields.io/npm/v/lucide)](https://www.npmjs.com/package/lucide) | ![NPM Downloads](https://img.shields.io/npm/dw/lucide) | [Docs](https://lucide.dev/guide/packages/lucide) · [Source](./packages/lucide) |
| <img src="https://lucide.dev/framework-logos/react.svg" alt="React logo" width="48"> | **`lucide-react`** | [![npm](https://img.shields.io/npm/v/lucide-react)](https://www.npmjs.com/package/lucide-react) | ![NPM Downloads](https://img.shields.io/npm/dw/lucide-react) | [Docs](https://lucide.dev/guide/packages/lucide-react) · [Source](./packages/lucide-react) |
| <img src="https://lucide.dev/framework-logos/vue.svg" alt="Vue logo" width="48"> | **`lucide-vue-next`** | [![npm](https://img.shields.io/npm/v/lucide-vue-next)](https://www.npmjs.com/package/lucide-vue-next) | ![NPM Downloads](https://img.shields.io/npm/dw/lucide-vue-next) | [Docs](https://lucide.dev/guide/packages/lucide-vue-next) · [Source](./packages/lucide-vue-next) |
| <img src="https://lucide.dev/framework-logos/svelte.svg" alt="Svelte logo" width="48"> | **`lucide-svelte`** | [![npm](https://img.shields.io/npm/v/lucide-svelte)](https://www.npmjs.com/package/lucide-svelte) | ![NPM Downloads](https://img.shields.io/npm/dw/lucide-svelte) | [Docs](https://lucide.dev/guide/packages/lucide-svelte) · [Source](./packages/lucide-svelte) |
| <img src="https://lucide.dev/framework-logos/solid.svg" alt="Solid logo" width="48"> | **`lucide-solid`** | [![npm](https://img.shields.io/npm/v/lucide-solid)](https://www.npmjs.com/package/lucide-solid) | ![NPM Downloads](https://img.shields.io/npm/dw/lucide-solid) | [Docs](https://lucide.dev/guide/packages/lucide-solid) · [Source](./packages/lucide-solid) |
| <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) |
Lucide is trying to expand the icon set as much as possible while staying faithful to the original simplistic design language. We do this as a community of devs and designers and hope that you'll join us!
### Why choose Lucide over Feather Icons
- More icons to work with: Lucide already has hundreds of icons more than Feather does.
- Official libraries and integrations with popular frameworks and design tools.
- Well maintained code base.
- Active community, regularly growing and improving the set.
## Table of Contents
- [Usage](#usage)
- [Web](#web)
- [React](#react)
- [React Native](#react-native)
- [Vue 2](#vue-2)
- [Vue 3](#vue-3)
- [Angular](#angular)
- [Preact](#preact)
- [Static (svg sprite, font, icons ..)](#static-svg-sprite-font-icons-)
- [Figma](#figma)
- [Laravel](#laravel)
- [Svelte](#svelte)
- [Solid](#solid)
- [Hyva](#hyva)
- [Eleventy](#eleventy)
- [Contributing](#contributing)
- [Community](#community)
- [License](#license)
- [Credits](#credits)
- [Sponsors](#sponsors)
## Usage
At its core, Lucide is a collection of [SVG](https://svgontheweb.com/#svg) files. This means that you can use Lucide icons in all the same ways you can use SVGs (e.g. `img`, `background-image`, `inline`, `object`, `embed`, `iframe`). Here's a helpful article detailing the many ways SVGs can be used on the web: [SVG on the Web Implementation Options](https://svgontheweb.com/#implementation)
The following are additional ways you can use Lucide.
With the Javascript library you can easily incorporate the icon you want in your webpage.
### Web
Implementation of the lucide icon library for web applications.
```sh
npm install lucide
```
or
```sh
yarn add lucide
```
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide#lucide).
### React
Implementation of the lucide icon library for react applications.
```sh
yarn add lucide-react
```
or
```sh
npm install lucide-react
```
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide-react#lucide-react).
### React Native
Implementation of the lucide icon library for React Native applications.
```sh
yarn add lucide-react-native
```
or
```sh
npm install lucide-react-native
```
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide-react-native#lucide-react-native).
### Vue 2
Implementation of the lucide icon library for vue applications.
```sh
yarn add lucide-vue
```
or
```sh
npm install lucide-vue
```
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide-vue#lucide-vue).
### Vue 3
Implementation of the lucide icon library for vue applications.
```sh
yarn add lucide-vue-next
```
or
```sh
npm install lucide-vue-next
```
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide-vue-next#lucide-vue-next).
### Angular
```sh
yarn add lucide-angular
```
or
```sh
npm install lucide-angular
```
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide-angular#lucide-angular).
### Preact
Implementation of the lucide icon library for preact applications.
```sh
yarn add lucide-preact
```
or
```sh
npm install lucide-preact
```
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide-preact#lucide-preact).
### Static (svg sprite, font, icons ..)
Assets:
[Font Files](https://github.com/lucide-icons/lucide/releases/latest)
[SVG Files](https://github.com/lucide-icons/lucide/releases/latest)
[SVG Sprite](https://cdn.jsdelivr.net/npm/lucide-static@latest/sprite.svg)
NPM package
```sh
yarn add lucide-static
```
or
```sh
npm install lucide-static
```
### Figma
@@ -51,6 +196,68 @@ Visit [Figma community page](https://www.figma.com/community/plugin/939567362549
<img width="420" src="https://www.figma.com/community/plugin/939567362549682242/thumbnail" alt="Figma Lucide Cover">
### Laravel
Implementation of Lucide icon's using `blade-icons` for Laravel based projects.
```sh
composer require mallardduck/blade-lucide-icons
```
For more details, see the [documentation](https://github.com/mallardduck/blade-lucide-icons/blob/main/README.md).
### Svelte
Implementation of the lucide icon library for Svelte applications.
```sh
yarn add lucide-svelte
```
or
```sh
npm install lucide-svelte
```
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide-svelte#lucide-svelte).
### Solid
Implementation of the lucide icon library for solid applications.
```sh
yarn add lucide-solid
```
or
```sh
npm install lucide-solid
```
For more details, see the [documentation](https://github.com/lucide-icons/lucide/tree/main/packages/lucide-solid#lucide-solid).
### Hyva
Implementation of Lucide icon's using Hyvä's svg php viewmodal to render icons for Magento 2 Hyva theme based projects.
```sh
composer require siteation/magento2-hyva-icons-lucide
```
For more details, see the [documentation](https://github.com/Siteation/magento2-hyva-icons-lucide/blob/main/README.md).
### Eleventy
Using this plugin, Eleventy projects can incorporate Lucide icons. it makes it simple to use Lucide icons into your themes via shortcodes, improving your website's overall usability and visual appeal.
```sh
npm install @grimlink/eleventy-plugin-lucide-icons
```
For more details, see the [documentation](https://github.com/GrimLink/eleventy-plugin-lucide-icons/blob/main/README.md).
## Contributing
For more info on how to contribute please see the [contribution guidelines](https://github.com/lucide-icons/lucide/blob/main/CONTRIBUTING.md).
@@ -63,16 +270,14 @@ Join the community on our [Discord](https://discord.gg/EH6nSts) server!
## License
Lucide is totally free for commercial use and personal use, this software is licensed under the [ISC License](https://github.com/lucide-icons/lucide/blob/main/LICENSE).
Lucide is totally free for commercial use and personally use, this software is licensed under the [ISC License](https://github.com/lucide-icons/lucide/blob/main/LICENSE).
## Credits
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
@@ -82,7 +287,6 @@ Thank you to all the people who contributed to Lucide!
<a href="https://www.digitalocean.com/?refcode=b0877a2caebd&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge"><img src="docs/public/digitalocean.svg" width="200" alt="DigitalOcean Referral Badge" /></a>
### Awesome backers 🍺
### Awesome backer 🍺
<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>

View File

@@ -1,5 +1,5 @@
{
"$schema": "../category.schema.json",
"title": "Charts",
"icon": "chart-pie"
"icon": "pie-chart"
}

5
categories/currency.json Normal file
View File

@@ -0,0 +1,5 @@
{
"$schema": "../category.schema.json",
"title": "Currency",
"icon": "dollar-sign"
}

View File

@@ -0,0 +1,5 @@
{
"$schema": "../category.schema.json",
"title": "Furniture",
"icon": "rocking-chair"
}

View File

@@ -1,5 +1,5 @@
{
"$schema": "../category.schema.json",
"title": "Home",
"icon": "house"
"icon": "home"
}

5
categories/maps.json Normal file
View File

@@ -0,0 +1,5 @@
{
"$schema": "../category.schema.json",
"title": "Maps",
"icon": "map"
}

View File

@@ -1,5 +1,5 @@
{
"$schema": "../category.schema.json",
"title": "Mathematics",
"title": "Maths",
"icon": "divide"
}

View File

@@ -1,5 +1,5 @@
{
"$schema": "../category.schema.json",
"title": "Finance",
"title": "Money",
"icon": "piggy-bank"
}

View File

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

View File

@@ -1,3 +1,4 @@
import { eventHandler, setResponseHeader } from 'h3';
import iconMetaData from '../../data/iconMetaData';
export default eventHandler((event) => {

View File

@@ -1,40 +0,0 @@
import iconNodes from '../../data/iconNodes/index.ts';
import { IconNodeWithKeys } from '../../theme/types';
import iconMetaData from '../../data/iconMetaData';
import releaseMeta from '../../data/releaseMetaData.json';
import categories from '../../data/categoriesData.json';
const dataResponse = {
icons: Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
return [name, attrs];
});
acc[name] = {
iconNode: newIconNode,
aliases: (iconMetaData[name]?.aliases ?? []).map((alias) =>
typeof alias === 'string' ? alias : alias.name,
),
tags: iconMetaData[name].tags ?? [],
categories: iconMetaData[name].categories ?? [],
...releaseMeta[name],
};
return acc;
}, {}),
aliases: Object.entries(iconNodes).reduce((acc, [name]) => {
for (const alias of iconMetaData[name]?.aliases ?? []) {
acc[typeof alias === 'string' ? alias : alias.name] = name;
}
return acc;
}, {}),
categories,
};
export default eventHandler((event) => {
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
return dataResponse;
});

View File

@@ -13,10 +13,7 @@ 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', '')
.replace(/<svg[^>]*>|<\/svg>/g, '');
const src = Buffer.from(data, 'base64').toString('utf8');
const children = [];
@@ -28,18 +25,18 @@ export default eventHandler((event) => {
.map((_, idx, arr) => arr.slice(0, idx + 1).join('-'))
.reverse()
.find((groupName) => groupName in iconNodes);
if (!(name in iconNodes) && backdropName) {
if (backdropName) {
const iconNode = iconNodes[backdropName];
const LucideIcon = createLucideIcon(backdropName, iconNode);
const svg = renderToStaticMarkup(createElement(LucideIcon));
const backdropString = svg.replaceAll('\n', '').replace(/<svg[^>]*>|<\/svg>/g, '');
const backdropString = svg.replace(/<svg[^>]*>|<\/svg>/g, '');
children.push(
createElement(Backdrop, {
backdropString,
src,
color: '#777',
color: name in iconNodes ? 'red' : '#777',
}),
);
}

View File

@@ -1,37 +0,0 @@
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
import { renderToString, renderToStaticMarkup } from 'react-dom/server';
import { createElement } from 'react';
import Diff from '../../../lib/SvgPreview/Diff.tsx';
import iconNodes from '../../../data/iconNodes';
import createLucideIcon from 'lucide-react/src/createLucideIcon';
export default eventHandler((event) => {
const { params } = event.context;
const pathData = params.data.split('/');
const data = pathData.at(-1).slice(0, -4);
const [name] = pathData;
const newSrc = Buffer.from(data, 'base64')
.toString('utf8')
.replaceAll('\n', '')
.replace(/<svg[^>]*>|<\/svg>/g, '');
const children = [];
const oldSrc = iconNodes[name]
? renderToStaticMarkup(createElement(createLucideIcon(name, iconNodes[name])))
.replaceAll('\n', '')
.replace(/<svg[^>]*>|<\/svg>/g, '')
: '';
const svg = Buffer.from(
// We can't use jsx here, is not supported here by nitro.
renderToString(createElement(Diff, { oldSrc, newSrc, showGrid: true }, children)),
).toString('utf8');
defaultContentType(event, 'image/svg+xml');
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
return svg;
});

View File

@@ -1,3 +1,4 @@
import { eventHandler, setResponseHeader } from 'h3';
import iconMetaData from '../../data/iconMetaData';
export default eventHandler((event) => {

View File

@@ -28,10 +28,6 @@ export default defineConfig({
new URL('./theme/components/overrides/VPFooter.vue', import.meta.url),
),
},
{
find: '~/.vitepress',
replacement: fileURLToPath(new URL('./', import.meta.url)),
},
],
},
},

View File

@@ -1,174 +0,0 @@
[
{
"name": "accessibility",
"title": "Accessibility"
},
{
"name": "account",
"title": "Accounts & access"
},
{
"name": "animals",
"title": "Animals"
},
{
"name": "arrows",
"title": "Arrows"
},
{
"name": "brands",
"title": "Brands"
},
{
"name": "buildings",
"title": "Buildings"
},
{
"name": "charts",
"title": "Charts"
},
{
"name": "communication",
"title": "Communication"
},
{
"name": "connectivity",
"title": "Connectivity"
},
{
"name": "cursors",
"title": "Cursors"
},
{
"name": "design",
"title": "Design"
},
{
"name": "development",
"title": "Coding & development"
},
{
"name": "devices",
"title": "Devices"
},
{
"name": "emoji",
"title": "Emoji"
},
{
"name": "files",
"title": "File icons"
},
{
"name": "finance",
"title": "Finance"
},
{
"name": "food-beverage",
"title": "Food & beverage"
},
{
"name": "gaming",
"title": "Gaming"
},
{
"name": "home",
"title": "Home"
},
{
"name": "layout",
"title": "Layout"
},
{
"name": "mail",
"title": "Mail"
},
{
"name": "math",
"title": "Mathematics"
},
{
"name": "medical",
"title": "Medical"
},
{
"name": "multimedia",
"title": "Multimedia"
},
{
"name": "nature",
"title": "Nature"
},
{
"name": "navigation",
"title": "Navigation"
},
{
"name": "notifications",
"title": "Notification"
},
{
"name": "people",
"title": "People"
},
{
"name": "photography",
"title": "Photography"
},
{
"name": "science",
"title": "Science"
},
{
"name": "seasons",
"title": "Seasons"
},
{
"name": "security",
"title": "Security"
},
{
"name": "shapes",
"title": "Shapes"
},
{
"name": "shopping",
"title": "Shopping"
},
{
"name": "social",
"title": "Social"
},
{
"name": "sports",
"title": "Sports"
},
{
"name": "sustainability",
"title": "Sustainability"
},
{
"name": "text",
"title": "Text formatting"
},
{
"name": "time",
"title": "Time & calendar"
},
{
"name": "tools",
"title": "Tools"
},
{
"name": "transportation",
"title": "Transportation"
},
{
"name": "travel",
"title": "Travel"
},
{
"name": "weather",
"title": "Weather"
}
]

View File

@@ -14,13 +14,5 @@
"light": "/library-logos/tamagui.svg",
"dark": "/library-logos/tamagui.svg"
}
},
{
"name": "Reflex",
"url": "https://reflex.dev",
"image": {
"light": "/library-logos/reflex-light.svg",
"dark": "/library-logos/reflex-dark.svg"
}
}
]

View File

@@ -31,8 +31,24 @@
}
]
},
"lucide-vue-next": {
"lucide-vue": {
"order": 2,
"icon": "vue",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-vue",
"href": "https://www.npmjs.com/package/lucide-vue"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-vue",
"href": "https://www.npmjs.com/package/lucide-vue"
}
]
},
"lucide-vue-next": {
"order": 3,
"icon": "vue-next",
"shields": [
{
@@ -48,7 +64,7 @@
]
},
"lucide-svelte": {
"order": 3,
"order": 4,
"icon": "svelte",
"shields": [
{
@@ -79,8 +95,24 @@
}
]
},
"lucide-react-native": {
"lucide-preact": {
"order": 5,
"icon": "preact",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-preact",
"href": "https://www.npmjs.com/package/lucide-preact"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-preact",
"href": "https://www.npmjs.com/package/lucide-preact"
}
]
},
"lucide-react-native": {
"order": 6,
"icon": "react-native",
"shields": [
{
@@ -96,7 +128,7 @@
]
},
"lucide-angular": {
"order": 6,
"order": 7,
"icon": "angular",
"shields": [
{
@@ -111,43 +143,8 @@
}
]
},
"lucide-preact": {
"order": 7,
"icon": "preact",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-preact",
"href": "https://www.npmjs.com/package/lucide-preact"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-preact",
"href": "https://www.npmjs.com/package/lucide-preact"
}
]
},
"@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": [
{
@@ -161,5 +158,16 @@
"href": "https://www.npmjs.com/package/lucide-static"
}
]
},
"lucide-flutter": {
"order": 9,
"icon": "flutter",
"shields": [
{
"alt": "flutter",
"src": "https://img.shields.io/pub/v/lucide_icons",
"href": "https://img.shields.io/pub/v/lucide_icons"
}
]
}
}

View File

@@ -76,24 +76,5 @@
],
"source": "https://github.com/swisnl/nuxt-lucide-icons",
"documentation": "https://github.com/swisnl/nuxt-lucide-icons/blob/main/README.md"
},
{
"name": "lucide-lustre",
"description": "A library providing https://lucide.dev icons to lustre.",
"icon": "/framework-logos/lustre.webp",
"shields": [
{
"alt": "Latest Stable Version",
"src": "https://img.shields.io/hexpm/v/lucide_lustre",
"href": "https://hex.pm/packages/lucide_lustre"
},
{
"alt": "Total Downloads",
"src": "https://img.shields.io/hexpm/dw/lucide_lustre",
"href": "https://hex.pm/packages/lucide_lustre"
}
],
"source": "https://github.com/dinkelspiel/lucide_lustre",
"documentation": "https://github.com/dinkelspiel/lucide_lustre/blob/master/README.md"
}
]

View File

@@ -11,7 +11,7 @@
},
{
"icon": "x",
"link": "https://x.com/ericfennis"
"link": "https://github.com/ericfennis"
}
]
},
@@ -31,8 +31,8 @@
]
},
{
"name": "Jakob Guddas",
"title": "Maintainer of Lucide & Software engineer @LEGO",
"name": "jguddas",
"title": "Maintainer of Lucide & Software engineer @lego",
"image": "https://github.com/jguddas.png?size=192",
"socialLinks": [
{

View File

@@ -3,22 +3,15 @@ import React from 'react';
interface BackdropProps {
src: string;
color?: string;
outline?: boolean;
backdropString: string;
}
const Backdrop = ({
src,
color = 'red',
outline = true,
backdropString,
}: BackdropProps): JSX.Element => {
const id = React.useId();
const Backdrop = ({ src, color = 'red', backdropString }: BackdropProps): JSX.Element => {
return (
<>
<defs xmlns="http://www.w3.org/2000/svg">
<pattern
id={`pattern-${id}`}
id="pattern"
width=".1"
height=".1"
patternUnits="userSpaceOnUse"
@@ -37,58 +30,69 @@ const Backdrop = ({
</pattern>
</defs>
<mask
id={`svg-preview-backdrop-mask-${id}`}
id="svg-preview-backdrop-mask-outline"
maskUnits="userSpaceOnUse"
>
<g
stroke="#fff"
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
<g dangerouslySetInnerHTML={{ __html: src }} />
<g
dangerouslySetInnerHTML={{ __html: src }}
strokeWidth={2.05}
/>
</mask>
<mask
id={`svg-preview-backdrop-mask-outline-${id}`}
id="svg-preview-backdrop-mask-fill"
maskUnits="userSpaceOnUse"
>
<rect
x="0"
y="0"
width="24"
height="24"
fill="#fff"
stroke="none"
<g
stroke="#fff"
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
<g
dangerouslySetInnerHTML={{ __html: src }}
strokeWidth={2.05}
/>
<g
strokeWidth={1.75}
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
</mask>
<g mask={`url(#svg-preview-backdrop-mask-${id})`}>
<g
strokeWidth={2.25}
stroke="url(#pattern)"
mask={'url(#svg-preview-backdrop-mask-outline)'}
>
<rect
x="0"
y="0"
width="24"
height="24"
fill="url(#pattern)"
opacity={0.5}
fill={`url(#pattern-${id})`}
stroke="none"
/>
<g
stroke={color}
strokeWidth={2.25}
opacity={0.75}
dangerouslySetInnerHTML={{ __html: src }}
/>
{outline && (
<g
stroke={color}
strokeWidth={2.25}
opacity={0.75}
mask={`url(#svg-preview-backdrop-mask-outline-${id})`}
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
)}
</g>
<rect
x="0"
y="0"
width="24"
height="24"
fill="url(#pattern)"
stroke="none"
mask={'url(#svg-preview-backdrop-mask-fill)'}
/>
<rect
x="0"
y="0"
width="24"
height="24"
fill={color}
opacity={0.5}
stroke="none"
mask={'url(#svg-preview-backdrop-mask-fill)'}
/>
</>
);
};

View File

@@ -1,71 +0,0 @@
import React from 'react';
import Backdrop from './Backdrop.tsx';
import { darkModeCss, Grid } from './index.tsx';
const SvgPreview = React.forwardRef<
SVGSVGElement,
{
oldSrc: string;
newSrc: string;
} & React.SVGProps<SVGSVGElement>
>(({ oldSrc, newSrc, children, ...props }, ref) => {
return (
<svg
ref={ref}
xmlns="http://www.w3.org/2000/svg"
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<style>{darkModeCss}</style>
<Grid
strokeWidth={0.1}
stroke="#777"
strokeOpacity={0.3}
radius={1}
/>
<mask
id="gray"
maskUnits="userSpaceOnUse"
>
<rect
x="0"
y="0"
width="24"
height="24"
fill="#000"
stroke="none"
/>
<g
stroke="#fff"
dangerouslySetInnerHTML={{ __html: oldSrc }}
/>
</mask>
<Backdrop
src=""
outline={false}
backdropString={`<g mask="url('#gray')">${newSrc}</g>`}
color="#777"
/>
<Backdrop
src={oldSrc}
backdropString={newSrc}
color="lime"
/>
<Backdrop
src={newSrc}
backdropString={oldSrc}
color="red"
/>
{children}
</svg>
);
});
export default SvgPreview;

View File

@@ -2,23 +2,7 @@ import React from 'react';
import { PathProps, Path } from './types';
import { getPaths, assert } from './utils';
export const darkModeCss = `
@media screen and (prefers-color-scheme: light) {
.svg-preview-grid-rect { fill: none }
}
@media screen and (prefers-color-scheme: dark) {
.svg-preview-grid-rect { fill: none }
.svg
.svg-preview-grid-group,
.svg-preview-radii-group,
.svg-preview-shadow-mask-group,
.svg-preview-shadow-group {
stroke: #fff;
}
}
`;
export const Grid = ({
const Grid = ({
radius,
fill = '#fff',
...props
@@ -317,6 +301,7 @@ const Handles = ({
'strokeWidth' | 'stroke' | 'strokeDasharray' | 'strokeOpacity',
any
>) => {
console.log(paths);
return (
<g
className="svg-preview-handles-group"
@@ -355,6 +340,19 @@ const SvgPreview = React.forwardRef<
>(({ src, children, showGrid = false, ...props }, ref) => {
const paths = typeof src === 'string' ? getPaths(src) : src;
const darkModeCss = `@media screen and (prefers-color-scheme: light) {
.svg-preview-grid-rect { fill: none }
}
@media screen and (prefers-color-scheme: dark) {
.svg-preview-grid-rect { fill: none }
.svg
.svg-preview-grid-group,
.svg-preview-radii-group,
.svg-preview-shadow-mask-group,
.svg-preview-shadow-group {
stroke: #fff;
}
}`;
return (
<svg
ref={ref}

View File

@@ -1,161 +0,0 @@
import { bundledLanguages, type ThemeRegistration } from 'shikiji';
import { getHighlighter } from 'shikiji';
type CodeExampleType = {
title: string;
language: string;
code: string;
}[];
const getIconCodes = (): CodeExampleType => {
return [
{
language: 'js',
title: 'Vanilla',
code: `\
import { createIcons, icons } from 'lucide';
import { $CamelCase } from '@lucide/lab';
createIcons({
icons: {
$CamelCase
}
});
document.body.append('<i data-lucide="$Name"></i>');\
`,
},
{
language: 'tsx',
title: 'React',
code: `import { Icon } from 'lucide-react';
import { $CamelCase } from '@lucide/lab';
const App = () => {
return (
<Icon iconNode={$CamelCase} />
);
};
export default App;
`,
},
{
language: 'vue',
title: 'Vue',
code: `<script setup>
import { Icon } from 'lucide-vue-next';
import { $CamelCase } from '@lucide/lab';
</script>
<template>
<Icon :iconNode="$CamelCase" />
</template>
`,
},
{
language: 'svelte',
title: 'Svelte',
code: `<script>
import { Icon } from 'lucide-svelte';
import { $CamelCase } from '@lucide/lab';
</script>
<Icon iconNode={$CamelCase} />
`,
},
{
language: 'tsx',
title: 'Preact',
code: `import { Icon } from 'lucide-preact';
import { $CamelCase } from '@lucide/lab';
const App = () => {
return (
<Icon iconNode={$CamelCase} />
);
};
export default App;
`,
},
{
language: 'tsx',
title: 'Solid',
code: `import { Icon } from 'lucide-solid';
import { $CamelCase } from '@lucide/lab';
const App = () => {
return (
<Icon iconNode={$CamelCase} />
);
};
export default App;
`,
},
{
language: 'tsx',
title: 'Angular',
code: `// app.module.ts
import { LucideAngularModule } from 'lucide-angular';
import { $CamelCase } from '@lucide/lab';
@NgModule({
imports: [
LucideAngularModule.pick({ $CamelCase })
],
})
// app.component.html
<lucide-icon name="$CamelCase"></lucide-icon>
`,
},
];
};
export type ThemeOptions =
| ThemeRegistration
| { light: ThemeRegistration; dark: ThemeRegistration };
const highLightCode = async (code: string, lang: string, active?: boolean) => {
const highlighter = await getHighlighter({
themes: ['github-light', 'github-dark'],
langs: Object.keys(bundledLanguages),
});
const highlightedCode = highlighter
.codeToHtml(code, {
lang,
themes: {
light: 'github-light',
dark: 'github-dark',
},
defaultColor: false,
})
.replace('shiki-themes', 'shiki-themes vp-code');
return `<div class="language-${lang} ${active ? 'active' : ''}">
<button title="Copy Code" class="copy"></button>
<span class="lang">${lang}</span>
${highlightedCode}
</div>`;
};
export default async function createCodeExamples() {
const codes = getIconCodes();
const codeExamplePromises = codes.map(async ({ title, language, code }, index) => {
const isFirst = index === 0;
const codeString = await highLightCode(code, language, isFirst);
return {
title,
language: language,
code: codeString,
};
});
return Promise.all(codeExamplePromises);
}

View File

@@ -1,32 +0,0 @@
import { bundledLanguages, type ThemeRegistration } from 'shikiji';
import { getHighlighter } from 'shikiji';
export type ThemeOptions =
| ThemeRegistration
| { light: ThemeRegistration; dark: ThemeRegistration };
const highLightCode = async (code: string, lang: string, active?: boolean) => {
const highlighter = await getHighlighter({
themes: ['github-light', 'github-dark'],
langs: Object.keys(bundledLanguages),
});
const highlightedCode = highlighter
.codeToHtml(code, {
lang,
themes: {
light: 'github-light',
dark: 'github-dark',
},
defaultColor: false,
})
.replace('shiki-themes', 'shiki-themes vp-code');
return `<div class="language-${lang} ${active ? 'active' : ''}">
<button title="Copy Code" class="copy"></button>
<span class="lang">${lang}</span>
${highlightedCode}
</div>`;
};
export default highLightCode;

View File

@@ -1,5 +0,0 @@
export type CodeExampleType = {
title: string;
language: string;
code: string;
}[];

View File

@@ -10,24 +10,18 @@ type CodeExampleType = {
const getIconCodes = (): CodeExampleType => {
return [
{
language: 'js',
title: 'Vanilla',
code: `\
import { createIcons, icons } from 'lucide';
createIcons({ icons });
document.body.append('<i data-lucide="$Name"></i>');\
`,
language: 'html',
title: 'HTML',
code: `<i data-lucide="Name"></i>`,
},
{
language: 'tsx',
title: 'React',
code: `import { $PascalCase } from 'lucide-react';
code: `import { PascalCase } from 'lucide-react';
const App = () => {
return (
<$PascalCase />
<PascalCase />
);
};
@@ -38,11 +32,11 @@ export default App;
language: 'vue',
title: 'Vue',
code: `<script setup>
import { $PascalCase } from 'lucide-vue-next';
import { PascalCase } from 'lucide-vue-next';
</script>
<template>
<$PascalCase />
<PascalCase />
</template>
`,
},
@@ -50,20 +44,20 @@ import { $PascalCase } from 'lucide-vue-next';
language: 'svelte',
title: 'Svelte',
code: `<script>
import { $PascalCase } from 'lucide-svelte';
import { PascalCase } from 'lucide-svelte';
</script>
<$PascalCase />
<PascalCase />
`,
},
{
language: 'tsx',
title: 'Preact',
code: `import { $PascalCase } from 'lucide-preact';
code: `import { PascalCase } from 'lucide-preact';
const App = () => {
return (
<$PascalCase />
<PascalCase />
);
};
@@ -73,11 +67,11 @@ export default App;
{
language: 'tsx',
title: 'Solid',
code: `import { $PascalCase } from 'lucide-solid';
code: `import { PascalCase } from 'lucide-solid';
const App = () => {
return (
<$PascalCase />
<PascalCase />
);
};
@@ -88,16 +82,16 @@ export default App;
language: 'tsx',
title: 'Angular',
code: `// app.module.ts
import { LucideAngularModule, $PascalCase } from 'lucide-angular';
import { LucideAngularModule, PascalCase } from 'lucide-angular';
@NgModule({
imports: [
LucideAngularModule.pick({ $PascalCase })
LucideAngularModule.pick({ PascalCase })
],
})
// app.component.html
<lucide-icon name="$Name"></lucide-icon>
<lucide-icon name="Name"></lucide-icon>
`,
},
{
@@ -107,7 +101,7 @@ import { LucideAngularModule, $PascalCase } from 'lucide-angular';
@import ('~lucide-static/font/Lucide.css');
</style>
<div class="icon-$Name"></div>
<div class="icon-Name"></div>
`,
},
];

View File

@@ -1,5 +1,6 @@
import { promises as fs, constants } from 'fs';
import path from 'path';
import yaml from 'js-yaml';
import { PackageItem } from '../theme/types';
const fileExist = (filePath) =>
@@ -26,6 +27,11 @@ const fetchPackages = async (): Promise<PackageItem[]> => {
return JSON.parse(await fs.readFile(jsonFilePath, 'utf-8'));
}
const ymlFilePath = path.resolve(filePath, 'pubspec.yaml');
if (await fileExist(ymlFilePath)) {
return yaml.load(await fs.readFile(ymlFilePath, 'utf-8'));
}
return null;
}),
);

View File

@@ -31,10 +31,10 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
{
text: 'Advanced',
items: [
{
text: 'Accessibility',
link: '/guide/advanced/accessibility',
},
// {
// text: 'Accessibility',
// link: '/guide/advanced/accessibility'
// },
{
text: 'Global styling',
link: '/guide/advanced/global-styling',
@@ -46,10 +46,6 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
text: 'Filled icons',
link: '/guide/advanced/filled-icons',
},
{
text: 'Aliased Names',
link: '/guide/advanced/aliased-names',
},
// {
// text: 'Combining icons',
// },
@@ -72,6 +68,10 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
text: 'Lucide React',
link: '/guide/packages/lucide-react',
},
{
text: 'Lucide React Native',
link: '/guide/packages/lucide-react-native',
},
{
text: 'Lucide Vue',
link: '/guide/packages/lucide-vue-next',
@@ -84,21 +84,13 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
text: 'Lucide Solid',
link: '/guide/packages/lucide-solid',
},
{
text: 'Lucide React Native',
link: '/guide/packages/lucide-react-native',
},
{
text: 'Lucide Angular',
link: '/guide/packages/lucide-angular',
},
{
text: 'Lucide Preact',
link: '/guide/packages/lucide-preact',
},
{
text: 'Lucide Astro',
link: '/guide/packages/lucide-astro',
text: 'Lucide Angular',
link: '/guide/packages/lucide-angular',
},
{
text: 'Lucide Static',
@@ -125,10 +117,6 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
text: 'Designing in Figma',
link: '/guide/design/figma-guide',
},
{
text: 'Designing in Affinity Designer',
link: '/guide/design/affinity-designer-guide',
},
],
},
],

View File

@@ -7,16 +7,7 @@ const props = defineProps<{
href?: string
}>()
const isExternal = computed(() => props.href?.startsWith('http') ?? false)
const component = computed(() => props.href ? 'a' : 'div')
const target = computed(() => isExternal.value ? '_blank' : undefined)
const rel = computed(() => isExternal.value ? 'noreferrer noopener' : undefined)
const onClick = computed(() => {
if(!props.href || isExternal) return
return go(props.href)
})
</script>
<template>
@@ -24,9 +15,7 @@ const onClick = computed(() => {
:is="component"
:href="href"
class="badge"
:target="target"
:rel="rel"
@click="onClick"
@click="props?.href ? go(href) : undefined"
>
<slot/>
</component>

View File

@@ -1,90 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{
label: string
id: string
value: string
modelValue: string | string[]
}>()
const emit = defineEmits(['change', 'input', 'update:modelValue'])
const model = computed({
get: () => {
if (Array.isArray(props.modelValue)) {
return props.modelValue.includes(props.value)
}
return props.modelValue === props.value
},
set: (value: string) => {
if (Array.isArray(props.modelValue)) {
const newValue = [...props.modelValue]
const index = newValue.indexOf(props.value)
if (value) {
if (index === -1) {
newValue.push(props.value)
}
} else {
if (index !== -1) {
newValue.splice(index, 1)
}
}
emit('update:modelValue', newValue)
} else {
emit('update:modelValue', value)
}
}
})
</script>
<template>
<div class="checkbox-wrapper">
<input
type="checkbox"
class="checkbox"
ref="input"
:id="id"
v-model="model"
v-bind="$attrs"
/>
<label :for="id" class="checkbox-label">
{{ label }}
</label>
</div>
</template>
<style scoped>
.checkbox-wrapper {
display: flex;
align-items: center;
gap: 8px;
}
.checkbox-label {
line-height: 20px;
font-size: 13px;
color: var(--vt-c-text-1);
transition: color .5s;
display: block;
}
.checkbox {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
cursor: pointer;
border: 1px solid var(--vp-input-border-color);
background-color: var(--vp-input-switch-bg-color);
border-radius: 4px;
}
.checkbox:checked {
border-color: transparent;
background: url("data:image/svg+xml,%3Csvg width='12px' height='12px' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='4' stroke-linecap='round' stroke-linejoin='round' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E")
center no-repeat var(--vp-c-brand);;
}
</style>

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

@@ -42,7 +42,7 @@ onMounted(() => {
font-weight: 400;
background: var(--vp-c-brand-dark);
color: white;
z-index: 99;
z-index: 10;
white-space: nowrap;
padding: 2px 8px;
border-radius: 4px;

View File

@@ -9,7 +9,7 @@ export default {
}
return null;
})
.then((res) => res?.tag_name);
.then((res) => res.tag_name);
return {
version,

View File

@@ -8,6 +8,8 @@ import { data } from './HomeHeroBefore.data'
<HomeContainer class="container">
<Badge
:href="`https://github.com/lucide-icons/lucide/releases/tag/${data.version}`"
target="_blank"
rel="noreferrer noopener"
>v{{ data.version }}</Badge>
</HomeContainer>
</template>

View File

@@ -171,14 +171,16 @@ watch(absoluteStrokeWidth, (enabled) => {
margin-top: 32px;
padding: 0;
background: none;
max-width: 280px;
}
@media (min-width: 640px) {
.card {
display: grid;
grid-template-columns: 8fr 10fr;
}
/*
/*
.card-column {
flex: 1;
} */

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

@@ -1,23 +1,17 @@
<script setup lang="ts">
import Card from '../base/Card.vue';
import HomeSectionTitle from './HomeSectionTitle.vue';
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
import Card from '../base/Card.vue'
import HomeSectionTitle from './HomeSectionTitle.vue'
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
</script>
<template>
<HomeSectionTitle :headingLevel="3"> Sponsor the Lucide maintainers </HomeSectionTitle>
<HomeSectionTitle :headingLevel="3">
Sponsor the Lucide maintainers
</HomeSectionTitle>
<Card class="sponsor-card">
<img
src="/company-logos/open-collective-light.svg"
src="/open-collective.png"
alt="Open Collective logo"
class="logo light"
width="242"
height="42"
/>
<img
src="/company-logos/open-collective-dark.svg"
alt="Open Collective logo"
class="logo dark"
width="242"
height="42"
/>
@@ -43,13 +37,6 @@ import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vu
margin: auto 0;
}
html.dark .logo.dark {
display: none;
}
html:not(.dark) .logo.light {
display: none;
}
@media (min-width: 640px) {
.sponsor-card {
flex-direction: row;

View File

@@ -3,7 +3,7 @@ export interface TeamMember {
name: string
title: string
image: string
sponsor?: string
sponsor: string
socialLinks: DefaultTheme.SocialLink[]
}
</script>
@@ -75,7 +75,6 @@ defineProps<TeamMember>()
font-weight: 500;
color: var(--vp-c-text-2);
text-align: center;
text-wrap: balance;
margin-bottom: 16px;;
}

View File

@@ -5,32 +5,22 @@ import IconButton from '../base/IconButton.vue';
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue'
import { x } from '../../../data/iconNodes'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
import { onMounted, ref } from 'vue';
const { theme } = useData()
const showAd = useSessionStorage('show-carbon-ads', true)
const carbonLoaded = ref(true)
const showAd = useSessionStorage('show-carbon-ads',true)
defineProps<{
drawerOpen: boolean
}>()
const CloseIcon = createLucideIcon('Close', x)
onMounted(() => {
setTimeout(() => {
if (window?._carbonads == null) {
carbonLoaded.value = false
}
}, 5000)
})
</script>
<template>
<div
:class="{
'drawer-open': drawerOpen,
'hide-ad': !(showAd && carbonLoaded)
'hide-ad': !showAd
}"
class="floating-ad"
v-if="theme.carbonAds"

View File

@@ -6,7 +6,6 @@ import { isActive } from 'vitepress/dist/client/shared'
import { useActiveAnchor } from '../../composables/useActiveAnchor'
import { data } from './CategoryList.data'
import CategoryListItem from './CategoryListItem.vue'
import SidebarTitle from './SidebarTitle.vue'
import { useCategoryView } from '../../composables/useCategoryView'
const { page } = useData()
@@ -47,13 +46,10 @@ watch(headers, () => {
<template>
<div class="category-list" ref="container">
<SidebarTitle>
View
</SidebarTitle>
<VPLink class="sidebar-link sidebar-text" href="/icons/" :class="{ 'active': overviewIsActive } ">
<VPLink class="sidebar-title" href="/icons/" :class="{ 'active': overviewIsActive } ">
All
</VPLink>
<VPLink class="sidebar-link sidebar-text" href="/icons/categories" :class="{ 'active': categoriesIsActive } ">
<VPLink class="sidebar-title" href="/icons/categories" :class="{ 'active': categoriesIsActive } ">
Categories
</VPLink>
<div class="content">
@@ -66,20 +62,17 @@ watch(headers, () => {
</template>
<style scoped>
.sidebar-text {
.sidebar-title {
font-weight: 500;
color: var(--vp-c-text-2);
margin-bottom: 6px;
line-height: 24px;
font-size: 14px;
display: block;
transition: color 0.25s;
padding: 4px 0;
}
.sidebar-link {
font-weight: 500;
color: var(--vp-c-text-2);
}
.sidebar-link:hover, .sidebar-link.active {
.sidebar-title:hover, .sidebar-title.active {
color: var(--vp-c-brand);
}
.content {

View File

@@ -1,98 +1,92 @@
<script setup lang="ts">
import { computed } from 'vue';
import { toPascalCase } from '@lucide/shared';
import ButtonMenu from '../base/ButtonMenu.vue';
import { startCase, camelCase } from 'lodash-es'
import ButtonMenu from '../base/ButtonMenu.vue'
import { useIconStyleContext } from '../../composables/useIconStyle';
import useConfetti from '../../composables/useConfetti';
const props = defineProps<{
name: string;
popoverPosition?: 'top' | 'bottom';
}>();
const { size, color, strokeWidth, absoluteStrokeWidth } = useIconStyleContext();
const { animate, confetti } = useConfetti();
name: string
popoverPosition?: 'top' | 'bottom'
}>()
const { size, color, strokeWidth, absoluteStrokeWidth } = useIconStyleContext()
const { animate, confetti } = useConfetti()
const componentName = computed(() => {
return (toPascalCase(props.name) as string).replace(/\s/g, '');
});
return startCase(camelCase(props.name)).replace(/\s/g, '')
})
function copyJSX() {
let attrs = [''];
let attrs = ['']
if (size.value && size.value !== 24) {
attrs.push(`size={${size.value}}`);
attrs.push(`size={${size.value}}`)
}
if (color.value && color.value !== 'currentColor') {
attrs.push(`color="${color.value}"`);
attrs.push(`color="${color.value}"`)
}
if (strokeWidth.value && strokeWidth.value !== 2) {
attrs.push(`strokeWidth={${strokeWidth.value}}`);
attrs.push(`strokeWidth={${strokeWidth.value}}`)
}
if (absoluteStrokeWidth.value) {
attrs.push(`absoluteStrokeWidth`);
attrs.push(`absoluteStrokeWidth`)
}
const code = `<${componentName.value}${attrs.join(' ')} />`;
const code = `<${componentName.value}${attrs.join(' ')} />`
navigator.clipboard.writeText(code);
}
function copyComponentName() {
const code = componentName.value;
navigator.clipboard.writeText(code);
navigator.clipboard.writeText(code)
}
function copyVue() {
let attrs = [''];
let attrs = ['']
if (size.value && size.value !== 24) {
attrs.push(`:size="${size.value}"`);
attrs.push(`:size="${size.value}"`)
}
if (color.value && color.value !== 'currentColor') {
attrs.push(`color="${color.value}"`);
attrs.push(`color="${color.value}"`)
}
if (strokeWidth.value && strokeWidth.value !== 2) {
attrs.push(`:stroke-width="${strokeWidth.value}"`);
attrs.push(`:stroke-width="${strokeWidth.value}"`)
}
if (absoluteStrokeWidth.value) {
attrs.push(`absoluteStrokeWidth`);
attrs.push(`absoluteStrokeWidth`)
}
const code = `<${componentName.value}${attrs.join(' ')} />`;
const code = `<${componentName.value}${attrs.join(' ')} />`
navigator.clipboard.writeText(code);
navigator.clipboard.writeText(code)
}
function copyAngular() {
let attrs = [''];
let attrs = ['']
attrs.push(`name="${props.name}"`);
attrs.push(`name="${props.name}"`)
if (size.value && size.value !== 24) {
attrs.push(`[size]="${size.value}"`);
attrs.push(`[size]="${size.value}"`)
}
if (color.value && color.value !== 'currentColor') {
attrs.push(`color="${color.value}"`);
attrs.push(`color="${color.value}"`)
}
if (strokeWidth.value && strokeWidth.value !== 2) {
attrs.push(`[strokeWidth]="${strokeWidth.value}"`);
attrs.push(`[strokeWidth]="${strokeWidth.value}"`)
}
if (absoluteStrokeWidth.value) {
attrs.push(`[absoluteStrokeWidth]="true"`);
attrs.push(`[absoluteStrokeWidth]="true"`)
}
const code = `<lucide-icon${attrs.join(' ')}></lucide-icon>`;
const code = `<lucide-icon${attrs.join(' ')}></lucide-icon>`
navigator.clipboard.writeText(code);
navigator.clipboard.writeText(code)
}
</script>
@@ -106,11 +100,10 @@ function copyAngular() {
data-confetti-text="Copied!"
:popoverPosition="popoverPosition"
:options="[
{ text: 'Copy JSX', onClick: copyJSX },
{ text: 'Copy Component Name', onClick: copyComponentName },
{ text: 'Copy Vue', onClick: copyVue },
{ text: 'Copy Svelte', onClick: copyJSX },
{ text: 'Copy Angular', onClick: copyAngular },
{ text: 'Copy JSX' , onClick: copyJSX },
{ text: 'Copy Vue' , onClick: copyVue },
{ text: 'Copy Svelte' , onClick: copyJSX },
{ text: 'Copy Angular' , onClick: copyAngular },
]"
/>
</template>

View File

@@ -11,30 +11,19 @@ import IconInfo from './IconInfo.vue';
import Badge from '../base/Badge.vue';
import { computedAsync } from '@vueuse/core';
import { satisfies } from 'semver';
import { useExternalLibs } from '../../composables/useExternalLibs';
const props = defineProps<{
iconName: string | null
}>()
const { externalIconNodes } = useExternalLibs()
const { go } = useRouter()
const icon = computedAsync<IconEntity | null>(async () => {
if (props.iconName) {
try {
if (props.iconName.includes(':')) {
const [library, name] = props.iconName.split(':')
return externalIconNodes.value[library].find((icon) => icon.name === name)
} else {
return (await import(`../../../data/iconDetails/${props.iconName}.ts`)).default as IconEntity
}
return (await import(`../../../data/iconDetails/${props.iconName}.ts`)).default as IconEntity
} catch (err) {
if (!props.iconName.includes(':')) {
go(`/icons/${props.iconName}`)
}
go(`/icons/${props.iconName}`)
}
}
return null
@@ -66,8 +55,10 @@ const Expand = createLucideIcon('Expand', expand)
v-if="icon.createdRelease"
class="version"
:href="releaseTagLink(icon.createdRelease.version)"
target="_blank"
rel="noreferrer noopener"
>v{{ icon.createdRelease.version }}</Badge>
<IconButton @click="go(icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}` : `/icons/${icon.name}`)">
<IconButton @click="go(`/icons/${icon.name}`)">
<component :is="Expand" />
</IconButton>
<IconButton @click="onClose">

View File

@@ -25,10 +25,8 @@ function setActiveIcon(name: string) {
:key="icon.name"
>
<IconItem
:iconNode="icon.iconNode"
:name="icon.name"
:externalLibrary="icon.externalLibrary"
@setActiveIcon="setActiveIcon"
v-bind="icon"
@setActiveIcon="setActiveIcon(icon.name)"
:active="activeIcon === icon.name"
customizable
:overlayMode="overlayMode"

View File

@@ -7,10 +7,6 @@ import CopyCodeButton from './CopyCodeButton.vue';
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
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.mjs';
const props = defineProps<{
icon: IconEntity
@@ -24,37 +20,13 @@ const tags = computed(() => {
if (!props.icon || !props?.icon?.tags) return []
return props.icon.tags.join(' • ')
})
const DiamondIcon = createLucideIcon('Diamond', diamond)
const deprecatedTitle = computed(() => {
if (!props.icon.deprecationReason) return '';
return deprecationReasonTemplate(props.icon.deprecationReason, {
componentName: props.icon.name,
iconName: props.icon.name,
toBeRemovedInVersion: props.icon.toBeRemovedInVersion,
});
});
</script>
<template>
<div class="icon-info">
<div class="icon-name-wrapper">
<IconDetailName class="icon-name">
{{ icon.name }}
</IconDetailName>
<div v-if="icon.externalLibrary" class="icon-external-lib">
<DiamondIcon fill="currentColor" :size="12"/>
{{ icon.externalLibrary }}
</div>
<Badge
v-if="icon.deprecated"
class="deprecated-badge"
:title="deprecatedTitle"
>
Deprecated
</Badge>
</div>
<IconDetailName class="icon-name">
{{ icon.name }}
</IconDetailName>
<div class="tags-scroller" v-if="tags.length">
<p class="icon-tags horizontal-scroller">
{{ tags }}
@@ -72,10 +44,10 @@ const deprecatedTitle = computed(() => {
<div class="group buttons">
<VPButton
v-if="!page?.relativePath?.startsWith?.(icon.externalLibrary ? `icons/${icon.externalLibrary}/${icon.name}`: `icons/${icon.name}`)"
:href="icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}`: `/icons/${icon.name}`"
v-if="!page?.relativePath?.startsWith?.(`icons/${icon.name}`)"
:href="`/icons/${icon.name}`"
text="See in action"
@click="go(icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}`: `/icons/${icon.name}`)"
@click="go(`/icons/${icon.name}`)"
/>
<CopySVGButton :name="icon.name" :popoverPosition="popoverPosition"/>
<CopyCodeButton :name="icon.name" :popoverPosition="popoverPosition"/>
@@ -95,37 +67,9 @@ const deprecatedTitle = computed(() => {
text-transform: capitalize;
}
.icon-name {
margin-right: -36px;
}
.icon-name-wrapper {
display: flex;
align-items: center;
gap: 2px;
margin-bottom: 4px;
}
.icon-external-lib {
color: var(--vp-c-brand-dark);
padding: 4px 12px;
font-size: 16px;
font-weight: 600;
line-height: 28px;
display: flex;
gap: 8px;
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

@@ -6,7 +6,6 @@ import { useRouter } from 'vitepress';
import getSVGIcon from '../../utils/getSVGIcon';
import useConfetti from '../../composables/useConfetti';
import Tooltip from '../base/Tooltip.vue';
import { diamond } from '../../../data/iconNodes'
const downloadText = 'Download!'
const copiedText = 'Copied!'
@@ -17,7 +16,6 @@ const props = defineProps<{
name: string;
iconNode: IconNode;
active: boolean;
externalLibrary?: string;
customizable?: boolean;
overlayMode?: boolean
hideIcon?: boolean
@@ -35,9 +33,8 @@ const icon = computed(() => {
return createLucideIcon(props.name, props.iconNode)
})
const href = computed(() => props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`)
async function navigateToIcon(event) {
if (event.shiftKey) {
event.preventDefault()
const svgString = getSVGIcon(event.target.firstChild, {
@@ -53,16 +50,13 @@ async function navigateToIcon(event) {
if(props.overlayMode && showOverlay.value) {
event.preventDefault()
window.history.pushState({}, '', props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`)
emit('setActiveIcon', props.externalLibrary ? `${props.externalLibrary}:${props.name}`: props.name)
window.history.pushState({}, '', `/icons/${props.name}`)
emit('setActiveIcon', props.name)
} else {
event.preventDefault()
go(props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`)
go(`/icons/${props.name}`)
}
}
const DiamondIcon = createLucideIcon('Diamond', diamond)
</script>
<template>
@@ -72,7 +66,7 @@ const DiamondIcon = createLucideIcon('Diamond', diamond)
@click="navigateToIcon"
:class="{ active, animate }"
:aria-label="name"
:href="`/icons/${props.name}`"
:data-confetti-text="confettiText"
ref="ref"
>
@@ -86,13 +80,6 @@ const DiamondIcon = createLucideIcon('Diamond', diamond)
}"
/>
</KeepAlive>
<div
v-if="externalLibrary"
class="floating-diamond"
aria-hidden="true"
>
<DiamondIcon fill="currentColor" :size="8"/>
</div>
</a>
</Tooltip>
</template>
@@ -101,7 +88,6 @@ const DiamondIcon = createLucideIcon('Diamond', diamond)
<style scoped>
.icon-button {
position: relative;
display: inline-block;
border: 1px solid transparent;
text-align: center;
@@ -118,13 +104,6 @@ const DiamondIcon = createLucideIcon('Diamond', diamond)
color: var(--vp-c-text-1);
}
.floating-diamond {
position: absolute;
top: 4px;
right: 4px;
color: var(--vp-c-brand);
}
.confetti-button:before,
.confetti-button:after {
z-index: 100;

View File

@@ -1,438 +0,0 @@
<script setup lang="ts">
import type { IconEntity } from '../../types';
import { computed } from 'vue';
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon.ts';
import Calendar from '../../../data/iconDetails/calendar.ts';
import Clock from '../../../data/iconDetails/clock.ts';
import Bug from '../../../data/iconDetails/bug.ts';
import Rocket from '../../../data/iconDetails/rocket.ts';
import TriangleAlert from '../../../data/iconDetails/triangle-alert.ts';
import PartyPopper from '../../../data/iconDetails/party-popper.ts';
import Scissors from '../../../data/iconDetails/scissors.ts';
import Copy from '../../../data/iconDetails/copy.ts';
import Save from '../../../data/iconDetails/save.ts';
import Clipboard from '../../../data/iconDetails/clipboard.ts';
import MessageCircle from '../../../data/iconDetails/message-circle.ts';
import ThumbsDown from '../../../data/iconDetails/thumbs-down.ts';
import ThumbsUp from '../../../data/iconDetails/thumbs-up.ts';
import Heart from '../../../data/iconDetails/heart.ts';
import Folder from '../../../data/iconDetails/folder.ts';
import Files from '../../../data/iconDetails/files.ts';
import Plus from '../../../data/iconDetails/plus.ts';
import File from '../../../data/iconDetails/file.ts';
import FileText from '../../../data/iconDetails/file-text.ts';
const props = defineProps<{
name: IconEntity['name'];
iconNode: IconEntity['iconNode'];
}>();
const iconComponent = computed(() => {
if (!props.name || !props.iconNode) return null;
return createLucideIcon(props.name, props.iconNode);
});
const CalendarIcon = createLucideIcon('calendar', Calendar.iconNode);
const ClockIcon = createLucideIcon('clock', Clock.iconNode);
const BugIcon = createLucideIcon('bug', Bug.iconNode);
const RocketIcon = createLucideIcon('rocket', Rocket.iconNode);
const AlertTriangleIcon = createLucideIcon('alert-triangle', TriangleAlert.iconNode);
const ScissorsIcon = createLucideIcon('scissors', Scissors.iconNode);
const CopyIcon = createLucideIcon('copy', Copy.iconNode);
const SaveIcon = createLucideIcon('save', Save.iconNode);
const ClipboardIcon = createLucideIcon('clipboard', Clipboard.iconNode);
const PartyPopperIcon = createLucideIcon('party-popper', PartyPopper.iconNode);
const HeartIcon = createLucideIcon('heart', Heart.iconNode);
const ThumbsUpIcon = createLucideIcon('thumbs-up', ThumbsUp.iconNode);
const ThumbsDownIcon = createLucideIcon('thumbs-down', ThumbsDown.iconNode);
const MessageCircleIcon = createLucideIcon('message-circle', MessageCircle.iconNode);
const FolderIcon = createLucideIcon('folder.ts', Folder.iconNode);
const FilesIcon = createLucideIcon('files.ts', Files.iconNode);
const PlusIcon = createLucideIcon('plus.ts', Plus.iconNode);
const FileIcon = createLucideIcon('file.ts', File.iconNode);
const FileTextIcon = createLucideIcon('file-text.ts', FileText.iconNode);
const prettyName = props.name
.split('-')
.at(0)
.split('')
.map((character, index) => (index === 0 ? character.toUpperCase() : character))
.join('');
</script>
<template>
<section class="showcase">
<h2 class="title">See this icon in action</h2>
<div class="showcase-grid">
<div class="showcase-item column">
<div class="placeholder"></div>
<div class="placeholder"></div>
<div class="placeholder"></div>
<div class="placeholder narrow"></div>
<div class="spacer"></div>
<div class="actions">
<button class="button button-brand">
<iconComponent />
{{ prettyName }}
</button>
<button class="button button-alt">Cancel</button>
</div>
</div>
<div class="showcase-item column">
<div class="placeholder narrow"></div>
<div class="input-wrapper">
<CalendarIcon v-if="name !== 'calendar'" />
<ClockIcon v-if="name == 'calendar'" />
<input
type="text"
v-if="name !== 'calendar'"
placeholder="Enter a date..."
/>
<input
type="text"
v-if="name == 'calendar'"
placeholder="Enter a time..."
/>
</div>
<div class="spacer"></div>
<div class="placeholder narrow"></div>
<div class="input-wrapper">
<iconComponent />
<input
type="text"
placeholder="Enter a value..."
/>
</div>
</div>
<div class="showcase-item column">
<div
class="row"
v-if="name !== 'bug'"
>
<div class="placeholder"></div>
<div class="badge badge-red">
<BugIcon :size="20" />
Bug
</div>
</div>
<div
class="row"
v-else
>
<div class="placeholder"></div>
<div class="badge badge-red">
<AlertTriangleIcon :size="20" />
Alert
</div>
</div>
<div class="row">
<div class="placeholder"></div>
<div class="badge badge-indigo">
<RocketIcon
:size="20"
v-if="name !== 'rocket'"
/>
<PartyPopperIcon
:size="20"
v-else
/>
Feature
</div>
</div>
<div class="row">
<div class="placeholder"></div>
<div class="badge badge-green">
<iconComponent :size="20" />
{{ prettyName }}
</div>
</div>
</div>
<div class="showcase-item column">
<button class="button button-alt button-square">
<FolderIcon v-if="name !== 'folder'" />
<FilesIcon v-else />
Documents
<PlusIcon class="ms-auto" />
</button>
<button class="button button-alt button-square">
<FileIcon v-if="name !== 'file'" />
<FileTextIcon v-else />
Readme
</button>
<button class="button button-alt button-square">
<iconComponent />
{{ prettyName }}
<span class="badge-notification ms-auto">12</span>
</button>
</div>
<div class="showcase-item column">
<div class="placeholder"></div>
<div class="placeholder"></div>
<div class="placeholder"></div>
<div class="placeholder narrow"></div>
<div class="spacer"></div>
<div class="actions">
<div class="icon-counter">
<HeartIcon v-if="name !== 'heart'" />
<ThumbsUpIcon v-else />
112
</div>
<div class="spacer"></div>
<div class="icon-counter">
<MessageCircleIcon v-if="name !== 'message-circle'" />
<ThumbsDownIcon v-else />
8
</div>
<div class="spacer"></div>
<div class="icon-counter">
<iconComponent />
11
</div>
</div>
</div>
<div class="showcase-item">
<div class="column align-items-center">
<div class="actions justify-content-center">
<button class="button button-icon">
<CopyIcon v-if="name !== 'copy'" />
<SaveIcon v-else />
</button>
<button class="button button-icon">
<ScissorsIcon v-if="name !== 'scissors'" />
<SaveIcon v-else />
</button>
<button class="button button-icon">
<ClipboardIcon v-if="name !== 'clipboard'" />
<SaveIcon v-else />
</button>
<button class="button button-icon">
<iconComponent></iconComponent>
<span class="badge-notification">2</span>
</button>
</div>
<div class="spacer"></div>
<div class="placeholder"></div>
<div class="placeholder"></div>
<div class="placeholder"></div>
</div>
</div>
</div>
</section>
</template>
<style scoped>
.showcase {
margin-bottom: 32px;
}
.showcase-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 48px;
margin-inline-start: 24px;
margin-block-start: 48px;
}
.showcase-item {
padding: 24px;
border-radius: 8px;
background-color: var(--vp-c-bg);
box-shadow:
var(--vp-shadow-4),
-24px -24px 0 var(--vp-c-bg-soft);
display: flex;
align-items: center;
justify-content: center;
min-height: 240px;
}
.spacer {
flex-basis: 0;
}
.button {
position: relative;
border-radius: 32px;
padding: 8px 16px;
display: flex;
flex-direction: row;
gap: 12px;
font-weight: 500;
transition:
color 0.25s,
border-color 0.25s,
background-color 0.25s;
border-color: var(--vp-button-alt-border);
color: var(--vp-button-alt-text);
background-color: var(--vp-button-alt-bg);
&:hover {
border-color: var(--vp-button-alt-hover-border);
color: var(--vp-button-alt-hover-text);
background-color: var(--vp-button-alt-hover-bg);
}
&.button-brand {
border-color: var(--vp-button-brand-border);
color: var(--vp-button-brand-text);
background-color: var(--vp-button-brand-bg);
&:hover {
border-color: var(--vp-button-brand-hover-border);
color: var(--vp-button-brand-hover-text);
background-color: var(--vp-button-brand-hover-bg);
}
}
&.button-icon {
padding: 12px;
}
&.button-square {
border-radius: 8px;
width: 100%;
}
}
.actions {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 12px;
}
.lucide {
flex-shrink: 0;
flex-grow: 0;
}
.placeholder {
display: block;
background-color: var(--vp-c-bg-soft);
border-radius: 32px;
height: 16px;
width: 100%;
&.narrow {
width: 33%;
}
}
.input-wrapper {
position: relative;
width: 100%;
.lucide {
position: absolute;
inset-inline-start: 12px;
inset-block: 50%;
translate: 0 -50%;
color: var(--vp-c-text-1);
opacity: 0.7;
}
input {
padding: 12px 20px;
padding-inline-start: 48px;
border-radius: 8px;
background-color: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
display: block;
width: 100%;
border: 2px solid transparent;
transition:
color 0.25s,
border-color 0.25s,
background-color 0.25s;
&:hover {
border-color: var(--vp-c-border);
}
&:focus {
border-color: var(--vp-c-brand);
}
}
&:focus-within {
.lucide {
opacity: 1;
}
}
}
.badge {
flex-shrink: 0;
display: flex;
flex-direction: row;
align-items: center;
gap: 6px;
padding: 4px 8px;
border-radius: 8px;
background-color: transparent;
overflow: hidden;
position: relative;
color: var(--badge-color);
&:before {
content: ' ';
inset: 0;
position: absolute;
background-color: var(--badge-color);
opacity: 0.1;
}
&.badge-indigo {
--badge-color: var(--vp-c-indigo-2);
}
&.badge-green {
--badge-color: var(--vp-c-green-2);
}
&.badge-red {
--badge-color: var(--vp-c-brand);
}
}
.badge-notification {
background-color: var(--vp-c-brand);
color: var(--vp-button-brand-text);
border-radius: 32px;
padding: 0 8px;
min-width: 24px;
min-height: 24px;
}
.button-icon {
.badge-notification {
position: absolute;
top: 0;
right: 0;
translate: 33% -33%;
}
}
.row {
display: flex;
flex-direction: row;
gap: 12px;
align-items: center;
width: 100%;
}
.icon-counter {
display: flex;
flex-direction: row;
gap: 6px;
color: var(--vp-c-text-2);
align-items: center;
}
.column {
display: flex;
flex-direction: column;
gap: 12px;
align-items: start;
width: 100%;
}
.align-items-center {
align-items: center;
}
.justify-content-center {
justify-content: center;
}
.ms-auto {
margin-inline-start: auto;
}
</style>

View File

@@ -19,7 +19,7 @@ export type CategoryRow = CategoryNameRow | CategoryIconsRow;
import IconGrid from './IconGrid.vue'
defineProps<{
activeIconName: string | null
activeIconName: string
categoryRow: CategoryRow
}>()

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, computed, defineAsyncComponent, onMounted, watch, watchEffect } from 'vue';
import { ref, computed, defineAsyncComponent, onMounted } from 'vue';
import type { IconEntity, Category } from '../../types';
import useSearch from '../../composables/useSearch';
import InputSearch from '../base/InputSearch.vue';
@@ -69,7 +69,7 @@ const categories = computed(() => {
return props.categories
.map(({ name, title }) => {
const categoryIcons = props.icons.filter((icon) => {
const iconCategories = icon?.externalLibrary ? icon.categories : props.iconCategories[icon.name]
const iconCategories = props.iconCategories[icon.name];
return iconCategories?.includes(name);
});
@@ -140,12 +140,6 @@ function handleCloseDrawer() {
window.history.pushState({}, '', '/icons/categories');
}
watchEffect(() => {
console.log(props.icons.find((icon) => icon.name === 'burger'));
});
</script>
<template>

View File

@@ -14,7 +14,6 @@ import CarbonAdOverlay from './CarbonAdOverlay.vue';
const ICON_SIZE = 56;
const ICON_GRID_GAP = 8;
const DEFAULT_GRID_ITEMS = 10 * 160;
const props = defineProps<{
icons: IconEntity[];
@@ -115,23 +114,11 @@ function handleCloseDrawer() {
/>
</StickyBar>
<NoResults
v-if="list.length === 0 && searchQuery !== ''"
v-if="list.length === 0"
:searchQuery="searchQuery"
@clear="searchQuery = ''"
/>
<IconGrid
v-else-if="list.length === 0"
:key="index"
overlayMode
:icons="[...searchResults].splice(0, DEFAULT_GRID_ITEMS)"
:activeIcon="activeIconName"
@setActiveIcon="setActiveIconName"
/>
<div
v-bind="wrapperProps"
class="icon"
v-else
>
<div v-bind="wrapperProps" class="icon">
<IconGrid
v-for="{ index, data: icons } in list"
:key="index"

View File

@@ -1,20 +1,29 @@
<script setup lang="ts">
import type { IconEntity } from '../../types';
import IconGrid from './IconGrid.vue';
import type { IconEntity } from '../../types'
import IconGrid from './IconGrid.vue'
defineProps<{
icons: IconEntity[];
}>();
icons: IconEntity[]
}>()
</script>
<template>
<section class="related-icons">
<h2 class="title">More icons like this</h2>
<IconGrid :icons="icons" />
<h2 class="title">
Related Icons
</h2>
<IconGrid
:icons="icons"
/>
</section>
</template>
<style scoped>
.title {
margin-bottom: 24px;
font-size: 19px;
font-weight: 500;
}
.related-icons {
margin-bottom: 32px;
}

View File

@@ -1,47 +0,0 @@
<script setup lang="ts">
import Checkbox from '../base/Checkbox.vue'
import SidebarTitle from './SidebarTitle.vue'
import { useExternalLibs } from '../../composables/useExternalLibs';
import { ExternalLibs } from '../../types';
interface ExternalLibrary {
name: string;
value: ExternalLibs;
}
const externalLibraries: ExternalLibrary[] = [
{
name: 'Lab',
value: 'lab'
},
];
const { selectedLibs } = useExternalLibs();
</script>
<template>
<div class="external-library-select">
<SidebarTitle>
Include external libs
</SidebarTitle>
<ul>
<li
v-for="library in externalLibraries"
:key="library.name"
>
<Checkbox
:label="library.name"
:id="library.name"
v-model="selectedLibs"
:value="library.value"
/>
</li>
</ul>
</div>
</template>
<style scoped>
.external-library-select {
margin-bottom: 24px;
}
</style>

View File

@@ -1,19 +0,0 @@
<template>
<h2 class="sidebar-title sidebar-text">
<slot />
</h2>
</template>
<style scoped>
.sidebar-title {
font-weight: 700;
color: var(--vp-c-text-1);
}
.sidebar-text {
line-height: 24px;
font-size: 14px;
display: block;
transition: color 0.25s;
padding: 4px 0;
}
</style>

View File

@@ -23,8 +23,8 @@ const links = computed(() => [
href: `${githubLink.value}/releases`
},
{
text: 'GitHub',
href: `${githubLink.value}`
text: 'Github',
href: `${githubLink.value}/issues`
},
{
text: 'Issues',

View File

@@ -1,27 +1,20 @@
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 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

@@ -1,8 +1,7 @@
<script setup lang="ts">
import { data } from './PackageList.data'
import GridSection from '../base/GridSection.vue'
import PackageListItem from "./PackageListItem.vue";
</script>
import PackageListItem from "./PackageListItem.vue";</script>
<template>
<GridSection

View File

@@ -19,7 +19,7 @@ const props = defineProps<{
<img v-if="packageData.iconDark" :src="packageData.iconDark" alt="" class="package-icon dark" :class="packageData.iconClass" />
</div>
<div class="package-title">
<h2 class="title">{{ props.packageData?.name }}</h2>
<h2 class="title">{{ props.packageData.name }}</h2>
<a v-for="shield in props.packageData.shields" :href="shield.href" class="package-shield" rel="noreferrer noopener">
<img :src="shield.src" :alt="shield.href" />
</a>

View File

@@ -1,57 +0,0 @@
import { ref, inject, Ref, watch } from 'vue';
import { ExternalLibs, IconEntity } from '../types';
export const EXTERNAL_LIBS_CONTEXT = Symbol('externalLibs');
type ExternalIconNodes = Partial<Record<ExternalLibs, IconEntity[]>>;
interface ExternalLibContext {
selectedLibs: Ref<[ExternalLibs]>;
externalIconNodes: Ref<ExternalIconNodes>;
}
export const externalLibContext = {
selectedLibs: ref([]),
externalIconNodes: ref({}),
};
const externalLibIconNodesAPI = {
lab: 'https://lab.lucide.dev/api/icon-details',
};
export function useExternalLibs(): ExternalLibContext {
const context = inject<ExternalLibContext>(EXTERNAL_LIBS_CONTEXT);
watch(context?.selectedLibs, async (selectedLibs) => {
const savedIconNodes = { ...context?.externalIconNodes.value };
const newExternalIconNodes: ExternalIconNodes = {};
try {
for (const lib of selectedLibs) {
if (savedIconNodes[lib]) {
newExternalIconNodes[lib] = savedIconNodes[lib];
} else {
const response = await fetch(externalLibIconNodesAPI[lib]);
const iconNodes = await response.json();
if (iconNodes) {
newExternalIconNodes[lib] = Object.values(iconNodes).map((iconEntity: IconEntity) => ({
...iconEntity,
externalLibrary: lib,
}));
}
}
}
context.externalIconNodes.value = newExternalIconNodes;
} catch (error) {
console.error(error);
}
});
if (!context) {
throw new Error('useExternalLibs must be used with externalLibs context');
}
return context;
}

View File

@@ -1,29 +0,0 @@
import { computed } from 'vue';
import { useExternalLibs } from '~/.vitepress/theme/composables/useExternalLibs';
import { IconEntity } from '../types';
const useIconsWithExternalLibs = (initialIcons?: IconEntity[]) => {
const { externalIconNodes } = useExternalLibs();
return computed(() => {
let icons = [];
if (initialIcons) {
icons = icons.concat(initialIcons);
}
const externalIconNodesArray = Object.values(externalIconNodes.value);
if (externalIconNodesArray?.length) {
externalIconNodesArray.forEach((iconNodes) => {
if (iconNodes?.length) {
icons = icons.concat(iconNodes);
}
});
}
return icons;
});
};
export default useIconsWithExternalLibs;

View File

@@ -7,7 +7,6 @@ import HomeHeroIconsCard from './components/home/HomeHeroIconsCard.vue';
import HomeHeroBefore from './components/home/HomeHeroBefore.vue';
import { ICON_STYLE_CONTEXT, iconStyleContext } from './composables/useIconStyle';
import { CATEGORY_VIEW_CONTEXT, categoryViewContext } from './composables/useCategoryView';
import { EXTERNAL_LIBS_CONTEXT, externalLibContext } from './composables/useExternalLibs';
const theme: Partial<Theme> = {
extends: DefaultTheme,
@@ -21,7 +20,6 @@ const theme: Partial<Theme> = {
enhanceApp({ app }) {
app.provide(ICON_STYLE_CONTEXT, iconStyleContext);
app.provide(CATEGORY_VIEW_CONTEXT, categoryViewContext);
app.provide(EXTERNAL_LIBS_CONTEXT, externalLibContext);
},
};

View File

@@ -3,7 +3,6 @@ import { useData } from 'vitepress'
import CategoryList from '../components/icons/CategoryList.vue'
import SidebarIconCustomizer from '../components/icons/SidebarIconCustomizer.vue'
import ExternalLibrarySelect from '../components/icons/SidebarExternalLibrarySelect.vue'
const { page } = useData()
@@ -12,7 +11,6 @@ const { page } = useData()
<template>
<div>
<SidebarIconCustomizer v-if="page?.relativePath?.startsWith?.('icons')"/>
<ExternalLibrarySelect v-if="page?.relativePath?.startsWith?.('icons')"/>
<CategoryList v-if="page?.relativePath?.startsWith?.('icons')"/>
</div>
</template>

View File

@@ -1,21 +1,13 @@
export type IconNode = [elementName: string, attrs: Record<string, string>][];
export type IconNodeWithKeys = [elementName: string, attrs: Record<string, string>, key: string][];
export interface IconMetaData {
export interface IconEntity {
name: string;
tags: string[];
categories: string[];
contributors: string[];
aliases?: string[];
deprecated?: boolean;
deprecationReason?: string;
toBeRemovedInVersion?: string;
}
export type ExternalLibs = 'lab';
export interface IconEntity extends IconMetaData {
name: string;
iconNode: IconNode;
externalLibrary?: ExternalLibs;
createdRelease?: Release;
changedRelease?: Release;
}
@@ -36,19 +28,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

@@ -1,217 +1,4 @@
---
title: Accessibility
---
# Accessibility
Icons are pictures that show what something means without using words. They can be very helpful
because they can quickly give information.
However, not everyone can understand them easily. When using icons it is very important to consider
the following aspects of accessibility.
## Provide visible labels
Icons are a helpful tool to improve perception, but they aren't a replacement for text.
In most cases, it is probably a good idea to also provide a textual representation of your icon's
function.
![In short: Dont rely on communicating the function of elements by icons alone. Do also provide a written description of the your interactive elements. For example: write out "On this page" on your on-page navigation element.](../../images/a11y/visible-labels.svg?raw=true)
## Contrast
Ensure there's enough contrast between the icon and its background so that it's visible to people
with low vision or color vision deficiencies.
We recommend
following [WCAG 2.1 SC 1.4.3](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html).
![In short: use a contrast ratio of at least 4.5:1](../../images/a11y/contrast.svg?raw=true)
## Use of color
Avoid relying solely on color to convey meaning in icons, as some users may have color blindness.
Instead, use additional visual cues like shape, shading or text.
![For example: Dont mark state with color, mark it with distinct visuals.](../../images/a11y/use-of-color.svg?raw=true)
## Interactivity
Ensure that interactive icons are accessible via keyboard navigation and provide clear feedback when
activated.
![](../../images/a11y/interactivity.svg?raw=true)
In most cases this is easily done by wrapping them in icon buttons.
## Minimum target size
Small targets can be difficult to click or touch, if your icon is interactive, we recommend that it
should have a minimum target size of 44×44 pixels.
![](../../images/a11y/target-size.svg?raw=true)
In practice, this doesn't necessarily mean that the icon itself should be this large, only its
interactive wrapper element.
## Meaningfulness
Icons should represent concepts or actions in a universally understandable way. Avoid using abstract
or ambiguous, or culture-specific symbols that might confuse some users.
![For example: Use universally understandable symbols and don't base your choice of icon on puns.](../../images/a11y/meaningfulness.svg?raw=true)
## Consistency
Maintain consistency in icon design and usage across your interface to help users learn and
understand their meanings more easily.
![For example: Dont use the same icon for multiple distinct purposes or meanings. Dont use different icons for the same purpose or function.](../../images/a11y/consistency.svg?raw=true)
## Text Alternatives
You may have to provide text labels or alternative text descriptions for icons, especially for
screen readers used by people with visual impairments.
However: descriptions should only be provided to standalone icons that aren't purely decorative, as
providing accessible names to non-functional elements only increases clutter when using screen
readers.
### On standalone icons
Icons are usually very unlikely to stand on their own with no semantically meaningful wrapper
element. In most cases they will be part of a badge, button (including icon buttons), navigation
item or other interactive UI element.
::: warning
In case some of your icons stand alone, and they serve a non-decorative function, make sure to
provide the appropriate accessible label for them.
:::
![In short: provide accessible label for semantic icons, but not for decorative icons.](../../images/a11y/alttext-standalone.svg?raw=true)
In general try to avoid using functional icons with no interactivity, we recommend that:
1) you either add a visible description next to them, or
2) place them in badges, labels or on buttons, and at least add a tooltip to them.
In any such case, it is preferred that the accessible name be provided for these interactive
elements (badges, buttons, nav items etc.) only, _not_ the icons themselves.
### On buttons
Do not provide an accessible label to icons when used on a button, as this label will be read out by
screen readers, leading to nonsensical text.
![](../../images/a11y/alttext-buttons.svg?raw=true)
::: details Code examples
```tsx
// Don't do this
<button>
<Plus aria-label="Plus icon"/>
Add document
</button>
// Do this, just leave it
<button>
<Plus/>
Add document
</button>
```
:::
### On icon buttons
Icon buttons are buttons that do not contain any visible text apart from the icon itself (think of
the close button of a dialog for example).
As previously stated, you should provide your accessible label on the icon button itself, not the
contained icon.
![](../../images/a11y/alttext-iconbuttons.svg?raw=true)
::: details Code examples
```tsx
// Don't do this
<button class="btn-icon">
<House/>
</button>
// Don't do this either
<button class="btn-icon">
<House 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/>
</button>
// Do this instead
<button class="btn-icon">
<House/>
<span class="visually-hidden">Go to home</span>
</button>
```
:::
## A note on `aria-label`
Although you could provide accessible labels to your elements via the `aria-label` attribute, we
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).
```tsx
import { ArrowRightIcon } from 'lucide-react';
import { AccessibleIcon } from '@radix-ui/react-accessible-icon';
<AccessibleIcon label="Next item">
<ArrowRightIcon />
</AccessibleIcon>
```
### Example - Bootstrap
```html
<div>
<i data-lucide="phone" aria-hidden="true"></i>
<span class="visually-hidden">Phone number</span>
</div>
```
### Example - Tailwind CSS
```html
<div>
<i data-lucide="phone" aria-hidden="true"></i>
<span class="sr-only">Phone number</span>
</div>
```
If you're not sure, you may consider learning more
about [how to hide content.](https://www.a11yproject.com/posts/how-to-hide-content/)
## Further resources
We also recommend checking out the following resources about accessibility:
- [Web Content Accessibility Guidelines (WCAG) 2.1](https://www.w3.org/TR/WCAG21/)
- [Web Accessibility Initiative (WAI)](https://www.w3.org/WAI/)
- [Learn accessibility on web.dev](https://web.dev/learn/accessibility)
- [Inclusive Components](https://inclusive-components.design/)
- [A11yTalks](https://www.a11ytalks.com/)
- [A11y automation tracker](https://a11y-automation.dev/)
- [The A11Y Project](https://www.a11yproject.com/)
<!-- Description how you should use svg icons keeping web accessible -->
<!-- See @JanTrichter comment about some information to write this: https://github.com/lucide-icons/lucide/pull/1521#discussion_r1332141390 -->

View File

@@ -1,93 +0,0 @@
# Aliased Names
Icons can have multiple names for the same icon. This is because we choose to rename some icons to make them more consistent with the rest of the icon set, or the name was not generic. For example, the `edit-2` icon is renamed to `pen` to make the name more generic, since it is just a pen icon.
Beside aliases names lucide also includes prefixed and suffixed names to use within your project. This is to prevent import name collisions with other libraries or your own code.
```tsx
// These are all the same icon
import {
House,
HouseIcon,
LucideHouse,
} from "lucide-react";
```
## Choosing import name style
To be consistent in your imports or want to change the autocompletion of Lucide icons in your IDE there an option to able the choose the import name style you want.
This can be done by creating a custom module declaration file to override the lucide imports and turning off the autocomplete in your IDE.
### Turn off autocomplete in your IDE
```json [.vscode/settings.json]
{
"typescript.preferences.autoImportFileExcludePatterns": [
"lucide-react", // or
"lucide-preact", // or
"lucide-react-native", // or
"lucide-vue-next",
]
}
```
### Create a custom module declaration file
Only available for `lucide-react`, `lucide-preact`, `lucide-react-native`, `lucide-vue-next` package.
This will enable you to choose the import name style you want to use in your project.
::: code-group
```ts [React]
declare module "lucide-react" {
// Prefixed import names
export * from "lucide-react/dist/lucide-react.prefixed";
// or
// Suffixed import names
export * from "lucide-react/dist/lucide-react.suffixed";
}
```
```ts [Vue]
declare module "lucide-vue-next" {
// Prefixed import names
export * from "lucide-vue-next/dist/lucide-vue-next.prefixed";
// or
// Suffixed import names
export * from "lucide-vue-next/dist/lucide-vue-next.suffixed";
}
```
```ts [Preact]
declare module "lucide-preact" {
// Prefixed import names
export * from "lucide-preact/dist/lucide-preact.prefixed";
// or
// Suffixed import names
export * from "lucide-preact/dist/lucide-preact.suffixed";
}
```
```ts [React Native]
declare module "lucide-react-native" {
// Prefixed import names
export * from "lucide-react-native/dist/lucide-react-native.prefixed";
// or
// Suffixed import names
export * from "lucide-react-native/dist/lucide-react-native.suffixed";
}
```
:::
Place this in your project root or in a folder where your tsconfig.json is located, or locate it in your defined type directory.
Easiest way is to create a `@types` folder in your project root and name the file `[package-name].d.ts`.
### Import name styles
| Import Style | Available imports | Declaration file import |
| ------------- | --------------------------- | ----------------------- |
| Default | Home, HomeIcon, LucideHome | |
| Prefixed | LucideHome | [package].prefixed |
| Suffixed | HomeIcon | [package].suffixed |

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