Compare commits

..

5 Commits

Author SHA1 Message Date
Eric Fennis
d1d57d3c85 Format code 2025-07-04 17:40:28 +02:00
Eric Fennis
701c2fb6b2 Delete lucide-vue 2024-01-23 18:24:03 +01:00
Eric Fennis
294ec9c727 Add simlinks 2024-01-23 17:46:05 +01:00
Eric Fennis
99c883e60a revert import change 2024-01-23 17:45:55 +01:00
Eric Fennis
9e68779d22 create new vue directory 2024-01-23 17:45:43 +01:00
2632 changed files with 54496 additions and 39827 deletions

View File

@@ -1,17 +0,0 @@
# Custom words
fullscreen
gamepad
gantt
kanban
pilcrow
squircle
strikethrough
touchpad
ungroup
pilcrow
toc
# Brands
codepen
codesandbox
dribbble

View File

@@ -6,5 +6,6 @@ tests
node_modules node_modules
.eslintrc.js .eslintrc.js
docs/images docs/images
docs/**/examples/ docs/guide/basics/examples
docs/guide/advanced/examples
packages/lucide-react/dynamicIconImports.js packages/lucide-react/dynamicIconImports.js

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 = { module.exports = {
root: true, root: true,
@@ -15,9 +15,7 @@ module.exports = {
'no-use-before-define': 'off', 'no-use-before-define': 'off',
'import/no-extraneous-dependencies': [ 'import/no-extraneous-dependencies': [
'error', 'error',
{ { devDependencies: ['**/*.test.js', '**/*.spec.js', './scripts/**'] },
devDependencies: ['**/*.test.js', '**/*.spec.js', '**/scripts/**'],
},
], ],
'import/extensions': [ 'import/extensions': [
'error', 'error',
@@ -45,14 +43,11 @@ module.exports = {
'@html-eslint/no-inline-styles': 'error', '@html-eslint/no-inline-styles': 'error',
'@html-eslint/require-attrs': [ '@html-eslint/require-attrs': [
'error', 'error',
...Object.entries(DEFAULT_ATTRS).map(([attr, value]) => ({ ...Object.entries(DEFAULT_ATTRS)
tag: 'svg', .map(([attr, value]) => ({ tag: 'svg', attr, value: String(value) }))
attr,
value: String(value),
})),
], ],
'@html-eslint/indent': ['error', 2], '@html-eslint/indent': ['error', 2],
'@html-eslint/no-multiple-empty-lines': ['error', { max: 0 }], "@html-eslint/no-multiple-empty-lines": ["error", { "max": 0 }],
'@html-eslint/no-extra-spacing-attrs': [ '@html-eslint/no-extra-spacing-attrs': [
'error', 'error',
{ {
@@ -69,7 +64,7 @@ module.exports = {
'@html-eslint/element-newline': 'error', '@html-eslint/element-newline': 'error',
'@html-eslint/no-trailing-spaces': 'error', '@html-eslint/no-trailing-spaces': 'error',
'@html-eslint/quotes': 'error', '@html-eslint/quotes': 'error',
}, }
}, },
], ],
}; };

View File

@@ -35,16 +35,6 @@ body:
placeholder: e.g. 0.289.1 placeholder: e.g. 0.289.1
validations: validations:
required: true required: true
- type: checkboxes
id: can-reproduce-in-latest-version
attributes:
label: Can you reproduce this in the latest version?
description: i.e. after running `npm install lucide-react@latest`
options:
- label: 'Yes'
- label: 'No'
validations:
required: false
- type: checkboxes - type: checkboxes
id: browsers id: browsers
attributes: attributes:
@@ -69,9 +59,6 @@ body:
- label: Windows - label: Windows
- label: Linux - label: Linux
- label: macOS - label: macOS
- label: ChromeOS
- label: iOS
- label: Android
- label: Other/not relevant - label: Other/not relevant
- type: textarea - type: textarea
id: description id: description

View File

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

View File

@@ -1,5 +1,5 @@
name: 'Build and Test' name: "Build and Test"
description: 'Builds and test a package' description: "Builds and test a package"
inputs: inputs:
name: name:
@@ -7,7 +7,7 @@ inputs:
required: true required: true
runs: runs:
using: 'composite' using: "composite"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4

View File

@@ -1,5 +1,5 @@
name: 'Check icons' name: "Check icons"
description: 'Cross-checks icon and category references in JSON descriptors' description: "Cross-checks icon and category references in JSON descriptors"
inputs: inputs:
name: name:
@@ -7,7 +7,7 @@ inputs:
required: true required: true
runs: runs:
using: 'composite' using: "composite"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4

View File

@@ -47,7 +47,7 @@
- [ ] I've made sure that the icons look sharp on low DPI displays. - [ ] I've made sure that the icons look sharp on low DPI displays.
- [ ] I've made sure that the icons look consistent with the icon set in size, optical volume and density. - [ ] I've made sure that the icons look consistent with the icon set in size, optical volume and density.
- [ ] I've made sure that the icons are visually centered. - [ ] I've made sure that the icons are visually centered.
- [ ] I've correctly optimized all icons to three points of precision. - [ ] I've correctly optimized all icons to two points of precision.
## Before Submitting <!-- For every PR! --> ## Before Submitting <!-- For every PR! -->
<!-- All of these requirements must be fulfilled. --> <!-- All of these requirements must be fulfilled. -->

View File

@@ -17,6 +17,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18

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@v3
- name: Check for blocked phrases in issue title
run: |
ISSUE_TITLE=$(jq -r '.issue.title' "$GITHUB_EVENT_PATH")
BLOCKED_PHRASES=("twitter" "whatsapp" "logo" "google" "tiktok" "facebook" "slack" "discord" "bluesky")
# 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,13 +1,11 @@
name: Close stale issues and PR name: Close stale issues and PR
on: on:
schedule: schedule:
- cron: '45 1 * * *' - cron: "45 1 * * *"
jobs: jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
pull-requests: write
steps: steps:
- uses: actions/stale@v9 - uses: actions/stale@v9
with: with:
@@ -16,5 +14,4 @@ jobs:
close-pr-message: This PR was closed because it has been stalled for 5 days with no activity. close-pr-message: This PR was closed because it has been stalled for 5 days with no activity.
close-pr-label: 🧶 stale close-pr-label: 🧶 stale
days-before-stale: 30 days-before-stale: 30
days-before-issue-stale: -1
days-before-close: -1 days-before-close: -1

View File

@@ -1,4 +1,4 @@
name: 'Pull Request Labeler' name: "Pull Request Labeler"
on: on:
- pull_request_target - pull_request_target

View File

@@ -11,6 +11,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18

View File

@@ -8,11 +8,13 @@ on:
- pnpm-lock.yaml - pnpm-lock.yaml
jobs: jobs:
build: lucide-angular:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
@@ -24,18 +26,5 @@ jobs:
- name: Build - name: Build
run: pnpm --filter lucide-angular build run: pnpm --filter lucide-angular build
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3.8.1
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test - name: Test
run: pnpm --filter lucide-angular test run: pnpm --filter lucide-angular test

View File

@@ -13,6 +13,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
@@ -27,7 +29,7 @@ jobs:
- name: Create font in ./lucide-font - name: Create font in ./lucide-font
run: pnpm build:font run: pnpm build:font
- name: 'Upload to Artifacts' - name: "Upload to Artifacts"
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: lucide-font name: lucide-font

View File

@@ -4,7 +4,6 @@ on:
pull_request: pull_request:
paths: paths:
- packages/lucide-preact/** - packages/lucide-preact/**
- packages/shared/**
- tools/build-icons/** - tools/build-icons/**
- tools/rollup-plugins/** - tools/rollup-plugins/**
- pnpm-lock.yaml - pnpm-lock.yaml
@@ -15,6 +14,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
@@ -23,5 +24,8 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter lucide-preact build
- name: Test - name: Test
run: pnpm --filter lucide-preact test run: pnpm --filter lucide-preact test

View File

@@ -4,7 +4,6 @@ on:
pull_request: pull_request:
paths: paths:
- packages/lucide-react-native/** - packages/lucide-react-native/**
- packages/shared/**
- tools/build-icons/** - tools/build-icons/**
- tools/rollup-plugins/** - tools/rollup-plugins/**
- pnpm-lock.yaml - pnpm-lock.yaml
@@ -15,6 +14,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
@@ -23,5 +24,8 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter lucide-react-native build
- name: Test - name: Test
run: pnpm --filter lucide-react-native test run: pnpm --filter lucide-react-native test

View File

@@ -4,18 +4,19 @@ on:
pull_request: pull_request:
paths: paths:
- packages/lucide-react/** - packages/lucide-react/**
- packages/shared/**
- tools/build-icons/** - tools/build-icons/**
- tools/rollup-plugins/** - tools/rollup-plugins/**
- scripts/generateNextJSAliases.mjs - scripts/generateNextJSAliases.mjs
- pnpm-lock.yaml - pnpm-lock.yaml
jobs: jobs:
build: lucide-react:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
@@ -27,18 +28,5 @@ jobs:
- name: Build - name: Build
run: pnpm --filter lucide-react build run: pnpm --filter lucide-react build
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3.8.1
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test - name: Test
run: pnpm --filter lucide-react test run: pnpm --filter lucide-react test

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@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3.8.1
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test
run: pnpm --filter lucide-react test

View File

@@ -4,17 +4,18 @@ on:
pull_request: pull_request:
paths: paths:
- packages/lucide-solid/** - packages/lucide-solid/**
- packages/shared/**
- tools/build-icons/** - tools/build-icons/**
- tools/rollup-plugins/** - tools/rollup-plugins/**
- pnpm-lock.yaml - pnpm-lock.yaml
jobs: jobs:
build: lucide-solid:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
@@ -26,18 +27,5 @@ jobs:
- name: Build - name: Build
run: pnpm --filter lucide-solid build run: pnpm --filter lucide-solid build
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3.8.1
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test - name: Test
run: pnpm --filter lucide-solid test run: pnpm --filter lucide-solid test

View File

@@ -13,6 +13,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18

View File

@@ -4,17 +4,18 @@ on:
pull_request: pull_request:
paths: paths:
- packages/lucide-svelte/** - packages/lucide-svelte/**
- packages/shared/**
- tools/build-icons/** - tools/build-icons/**
- tools/rollup-plugins/** - tools/rollup-plugins/**
- pnpm-lock.yaml - pnpm-lock.yaml
jobs: jobs:
build: lucide-svelte:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
@@ -26,18 +27,5 @@ jobs:
- name: Build - name: Build
run: pnpm --filter lucide-svelte build run: pnpm --filter lucide-svelte build
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3.8.1
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test - name: Test
run: pnpm --filter lucide-svelte test run: pnpm --filter lucide-svelte test

View File

@@ -4,17 +4,18 @@ on:
pull_request: pull_request:
paths: paths:
- packages/lucide-vue-next/** - packages/lucide-vue-next/**
- packages/shared/**
- tools/build-icons/** - tools/build-icons/**
- tools/rollup-plugins/** - tools/rollup-plugins/**
- pnpm-lock.yaml - pnpm-lock.yaml
jobs: jobs:
build: lucide-vue-next:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
@@ -26,18 +27,5 @@ jobs:
- name: Build - name: Build
run: pnpm --filter lucide-vue-next build run: pnpm --filter lucide-vue-next build
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3.8.1
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test - name: Test
run: pnpm --filter lucide-vue-next test run: pnpm --filter lucide-vue-next test

View File

@@ -4,17 +4,18 @@ on:
pull_request: pull_request:
paths: paths:
- packages/lucide-vue/** - packages/lucide-vue/**
- packages/shared/**
- tools/build-icons/** - tools/build-icons/**
- tools/rollup-plugins/** - tools/rollup-plugins/**
- pnpm-lock.yaml - pnpm-lock.yaml
jobs: jobs:
build: lucide-vue:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
@@ -26,18 +27,5 @@ jobs:
- name: Build - name: Build
run: pnpm --filter lucide-vue build run: pnpm --filter lucide-vue build
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3.8.1
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test - name: Test
run: pnpm --filter lucide-vue test run: pnpm --filter lucide-vue test

View File

@@ -9,11 +9,13 @@ on:
- pnpm-lock.yaml - pnpm-lock.yaml
jobs: jobs:
build: lucide:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
@@ -25,18 +27,5 @@ jobs:
- name: Build - name: Build
run: pnpm --filter lucide build run: pnpm --filter lucide build
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3.8.1
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test - name: Test
run: pnpm --filter lucide test run: pnpm --filter lucide test

View File

@@ -3,7 +3,7 @@ name: Add Changed Icons comment
on: on:
pull_request_target: pull_request_target:
paths: paths:
- 'icons/*' - 'icons/*.svg'
branches: branches:
- main - main
- fix-icon-preview - fix-icon-preview
@@ -68,16 +68,6 @@ jobs:
# input: +++ b/icons/accessibility.json%0A@@ -2,0 +3 @@%0A+ "contributors": ["hi"],%0A@@ -13 +14 @@%0A+}%0A # 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 # output: ::$ANNOTATION_SEVERITY file=icons/accessibility.json,line=2,endLine=3,title=$ANNOTATION_TITLE::$ANNOTATION_DESCRIPTION%0A%0A+ "contributors": ["hi"],%0A@@ -13 +14 @@%0A+}%0A
lint-aliases:
name: Check Uniqueness of Aliases
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Check Uniqueness of Aliases
run: "! cat <(printf \"%s\\n\" icons/*.json | while read -r name; do basename \"$name\" .json; done) <(jq -cr 'select(.aliases) | .aliases[] | if type==\"string\" then . else .name end' icons/*.json) | sort | uniq -c | grep -ve '^\\s*1 '"
generate-changed-icons-comment: generate-changed-icons-comment:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
@@ -104,10 +94,6 @@ jobs:
comment-author: 'github-actions[bot]' comment-author: 'github-actions[bot]'
body-includes: Added or changed icons body-includes: Added or changed icons
- uses: actions/setup-node@v4
- name: Install svgson for code preview (safer and faster than installing all deps)
run: npm install svgson
- name: Generate comment markup - name: Generate comment markup
run: node ./scripts/generateChangedIconsCommentMarkup.mjs >> comment-markup.md run: node ./scripts/generateChangedIconsCommentMarkup.mjs >> comment-markup.md
id: comment-markup id: comment-markup

View File

@@ -41,8 +41,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
package: package: [
[
'lucide', 'lucide',
'lucide-react', 'lucide-react',
'lucide-react-native', 'lucide-react-native',
@@ -56,6 +55,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
@@ -87,6 +88,8 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
@@ -117,6 +120,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
@@ -131,7 +136,7 @@ jobs:
- name: Create font in ./lucide-font - name: Create font in ./lucide-font
run: pnpm build:font run: pnpm build:font
- name: 'Upload to Artifacts' - name: "Upload to Artifacts"
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: lucide-font name: lucide-font
@@ -140,7 +145,10 @@ jobs:
post-release: post-release:
if: github.repository == 'lucide-icons/lucide' if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [pre-release, lucide-font] needs: [
pre-release,
lucide-font,
]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

9
.gitignore vendored
View File

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

View File

@@ -1,12 +1,5 @@
pnpm-lock.yaml pnpm-lock.yaml
# docs examples
docs/**/examples/
docs/.vitepress/.temp
docs/.vitepress/cache
docs/.vitepress/data
docs/.nitro
# lucide-angular # lucide-angular
packages/lucide-angular/.angular/cache packages/lucide-angular/.angular/cache

11
.vscode/settings.json vendored
View File

@@ -1,6 +1,13 @@
{ {
"cSpell.words": ["devs", "preact", "Preact"], "cSpell.words": [
"devs",
"preact",
"Preact"
],
"eslint.enable": true, "eslint.enable": true,
"eslint.validate": ["javascript", "svg"], "eslint.validate": [
"javascript",
"svg"
],
"svg.preview.background": "transparent" "svg.preview.background": "transparent"
} }

View File

@@ -49,7 +49,7 @@
"circle", "circle",
"<circle" "<circle"
], ],
"body": "<circle cx=\"${2:12}\" cy=\"${3:$2}\" r=\"${1|10,2,.5\" fill=\"currentColor|}\" />" "body": "<circle cx=\"${2:12}\" cy=\"${3:$2}\" r=\"${1|10,2,.5|}\" />"
}, },
"Ellipse": { "Ellipse": {
"scope": "xml", "scope": "xml",

View File

@@ -39,8 +39,6 @@ You can also [download an Adobe Illustrator template](https://github.com/lucide-
#### [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 submit multiple icons, please separate the icons and group them. That makes reviewing the icons easier and keep the thread clean and scoped. 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.
@@ -72,7 +70,7 @@ pnpm install # Install dependencies, including the workspace packages
### Packages -> PNPM Workspaces ### 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. 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). There are more workspaces defined, see [`pnpm-workspace.yaml`](https://github.com/lucide-icons/lucide/blob/main/pnpm-workspace.yaml).
@@ -127,7 +125,7 @@ When adding new features to for example the icon component for a framework. It i
### Local Testing ### 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 builded 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 ```sh
# in packages/lucide-react # in packages/lucide-react

275
README.md
View File

@@ -1,46 +1,185 @@
<p align="center"> <p align=center><img width="480" src="https://lucide.dev/lucide-logo-repo.svg" alt="Lucide Logo"></p>
<a href="https://github.com/lucide-icons/lucide#gh-light-mode-only">
<img src="https://lucide.dev/lucide-logo-repo.svg#gh-light-mode-only" alt="Lucide - Beautiful & consistent icon toolkit made by the community. Open-source project and a fork of Feather Icons." width="480">
</a>
<a href="https://github.com/lucide-icons/lucide#gh-dark-mode-only">
<img src="https://lucide.dev/lucide-logo-repo-dark.svg#gh-dark-mode-only" alt="Lucide - Beautiful & consistent icon toolkit made by the community. Open-source project and a fork of Feather Icons." width="480">
</a>
</p>
<p align="center"> <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://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://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> <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>
<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
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 | 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!
| ---- | ------- | ------- | --------- | ----- |
| <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) | ### Why choose Lucide over Feather Icons
| <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) | - More icons to work with: Lucide already has hundreds of icons more than Feather does.
| <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) | - Official libraries and integrations with popular frameworks and design tools.
| <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) | - Well maintained code base.
| <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) | - Active community, regularly growing and improving the set.
| <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) | ## Table of Contents
| <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) |
- [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 ### Figma
@@ -50,6 +189,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"> <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 ## Contributing
For more info on how to contribute please see the [contribution guidelines](https://github.com/lucide-icons/lucide/blob/main/CONTRIBUTING.md). For more info on how to contribute please see the [contribution guidelines](https://github.com/lucide-icons/lucide/blob/main/CONTRIBUTING.md).
@@ -62,7 +263,7 @@ Join the community on our [Discord](https://discord.gg/EH6nSts) server!
## License ## 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 ## Credits
@@ -73,13 +274,9 @@ Thank you to all the people who contributed to Lucide!
## Sponsors ## Sponsors
<a href="https://vercel.com?utm_source=lucide&utm_campaign=oss"> <a href="https://vercel.com?utm_source=lucide&utm_campaign=oss">
<img src="docs/public/vercel.svg" alt="Powered by Vercel" width="200" /> <img src="docs/public/vercel.svg" alt="Powered by Vercel" width="200" />
</a> </a>
<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> <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 🍺
<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", "$schema": "../category.schema.json",
"title": "Charts", "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

@@ -1,5 +1,5 @@
{ {
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Coding & development", "title": "Coding & development",
"icon": "code-xml" "icon": "code-2"
} }

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", "$schema": "../category.schema.json",
"title": "Home", "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", "$schema": "../category.schema.json",
"title": "Mathematics", "title": "Maths",
"icon": "divide" "icon": "divide"
} }

View File

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

View File

@@ -1,5 +1,5 @@
{ {
"$schema": "../category.schema.json", "$schema": "../category.schema.json",
"title": "Multimedia", "title": "Multimedia",
"icon": "circle-play" "icon": "play-circle"
} }

View File

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

View File

@@ -1,10 +0,0 @@
{
"dictionaries": ["en-us", "custom-words"],
"dictionaryDefinitions": [
{
"name": "custom-words",
"path": "./.cspell/custom-words.txt",
"addWords": true
}
]
}

View File

@@ -1,10 +1,11 @@
import iconMetaData from '../../data/iconMetaData'; import { eventHandler, setResponseHeader } from 'h3'
import iconMetaData from '../../data/iconMetaData'
export default eventHandler((event) => { export default eventHandler((event) => {
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400'); setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
setResponseHeader(event, 'Access-Control-Allow-Origin', '*'); setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
return Object.fromEntries( return Object.fromEntries(
Object.entries(iconMetaData).map(([name, { categories }]) => [name, categories]), Object.entries(iconMetaData).map(([name, { categories }]) => [ name, categories ])
); )
}); })

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 data = pathData.at(-1).slice(0, -4);
const [name] = pathData; const [name] = pathData;
const src = Buffer.from(data, 'base64') const src = Buffer.from(data, 'base64').toString('utf8');
.toString('utf8')
.replaceAll('\n', '')
.replace(/<svg[^>]*>|<\/svg>/g, '');
const children = []; const children = [];
@@ -28,25 +25,25 @@ export default eventHandler((event) => {
.map((_, idx, arr) => arr.slice(0, idx + 1).join('-')) .map((_, idx, arr) => arr.slice(0, idx + 1).join('-'))
.reverse() .reverse()
.find((groupName) => groupName in iconNodes); .find((groupName) => groupName in iconNodes);
if (!(name in iconNodes) && backdropName) { if (backdropName) {
const iconNode = iconNodes[backdropName]; const iconNode = iconNodes[backdropName];
const LucideIcon = createLucideIcon(backdropName, iconNode); const LucideIcon = createLucideIcon(backdropName, iconNode);
const svg = renderToStaticMarkup(createElement(LucideIcon)); const svg = renderToStaticMarkup(createElement(LucideIcon));
const backdropString = svg.replaceAll('\n', '').replace(/<svg[^>]*>|<\/svg>/g, ''); const backdropString = svg.replace(/<svg[^>]*>|<\/svg>/g, '');
children.push( children.push(
createElement(Backdrop, { createElement(Backdrop, {
backdropString, backdropString,
src, src,
color: '#777', color: name in iconNodes ? 'red' : '#777',
}), })
); );
} }
const svg = Buffer.from( const svg = Buffer.from(
// We can't use jsx here, is not supported here by nitro. // We can't use jsx here, is not supported here by nitro.
renderToString(createElement(SvgPreview, { src, showGrid: true }, children)), renderToString(createElement(SvgPreview, { src, showGrid: true }, children))
).toString('utf8'); ).toString('utf8');
defaultContentType(event, 'image/svg+xml'); defaultContentType(event, 'image/svg+xml');

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

@@ -28,7 +28,7 @@ export default eventHandler(async (event) => {
stroke-width="2" stroke-width="2"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
`, `
); );
const resvg = new Resvg(svg, { background: '#000' }); const resvg = new Resvg(svg, { background: '#000' });

View File

@@ -1,12 +1,12 @@
import { eventHandler, setResponseHeader, defaultContentType } from 'h3'; import { eventHandler, setResponseHeader, defaultContentType } from 'h3'
import { renderToString } from 'react-dom/server'; import { renderToString } from 'react-dom/server'
import { createElement } from 'react'; import { createElement } from 'react'
import SvgPreview from '../../../lib/SvgPreview/index.tsx'; import SvgPreview from '../../../lib/SvgPreview/index.tsx';
import createLucideIcon, { IconNode } from 'lucide-react/src/createLucideIcon'; import createLucideIcon, { IconNode } from 'lucide-react/src/createLucideIcon'
import { parseSync } from 'svgson'; import { parseSync } from 'svgson';
export default eventHandler((event) => { export default eventHandler((event) => {
const { params } = event.context; const { params } = event.context
const [strokeWidth, svgData] = params.data.split('/'); const [strokeWidth, svgData] = params.data.split('/');
const data = svgData.slice(0, -4); const data = svgData.slice(0, -4);
@@ -16,8 +16,8 @@ export default eventHandler((event) => {
const Icon = createLucideIcon( const Icon = createLucideIcon(
'icon', 'icon',
parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`).children.map( parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`).children.map(
({ name, attributes }) => [name, attributes], ({ name, attributes }) => [name, attributes]
) as IconNode, ) as IconNode
); );
const svg = Buffer.from( const svg = Buffer.from(
@@ -33,12 +33,12 @@ export default eventHandler((event) => {
@media screen and (prefers-color-scheme: dark) { @media screen and (prefers-color-scheme: dark) {
svg { stroke: #fff; fill: transparent !important; } svg { stroke: #fff; fill: transparent !important; }
} }
</style>`, </style>`
), )
).toString('utf8'); ).toString('utf8');
defaultContentType(event, 'image/svg+xml'); defaultContentType(event, 'image/svg+xml')
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000'); setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000')
return svg; return svg
}); })

View File

@@ -1,30 +1,30 @@
import { eventHandler, getQuery, setResponseHeader } from 'h3'; import { eventHandler, getQuery, setResponseHeader } from 'h3'
import iconNodes from '../../data/iconNodes'; import iconNodes from '../../data/iconNodes'
import { IconNodeWithKeys } from '../../theme/types'; import { IconNodeWithKeys } from '../../theme/types'
export default eventHandler((event) => { export default eventHandler((event) => {
const query = getQuery(event); const query = getQuery(event)
const withUniqueKeys = query.withUniqueKeys === 'true'; const withUniqueKeys = query.withUniqueKeys === 'true'
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400'); setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
setResponseHeader(event, 'Access-Control-Allow-Origin', '*'); setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
if (withUniqueKeys) { if (withUniqueKeys) {
return iconNodes; return iconNodes
} }
return Object.entries(iconNodes).reduce((acc, [name, iconNode]) => { return Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
if (withUniqueKeys) { if (withUniqueKeys) {
return [name, iconNode]; return [name, iconNode]
} }
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs}]) => { const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs}]) => {
return [name, attrs]; return [name, attrs]
}); })
acc[name] = newIconNode; acc[name] = newIconNode
return acc; return acc
}, {}); }, {})
}); })

View File

@@ -1,29 +1,29 @@
import { eventHandler, getQuery, setResponseHeader, createError } from 'h3'; import { eventHandler, getQuery, setResponseHeader, createError } from 'h3'
import iconNodes from '../../data/iconNodes'; import iconNodes from '../../data/iconNodes'
import createLucideIcon from 'lucide-react/src/createLucideIcon'; import createLucideIcon from 'lucide-react/src/createLucideIcon'
import { renderToString } from 'react-dom/server'; import { renderToString } from 'react-dom/server'
import { createElement } from 'react'; import { createElement } from 'react'
export default eventHandler((event) => { export default eventHandler((event) => {
const { params } = event.context; const { params } = event.context
const iconNode = iconNodes[params.iconName]; const iconNode = iconNodes[params.iconName]
if (iconNode == null) { if (iconNode == null) {
const error = createError({ const error = createError({
statusCode: 404, statusCode: 404,
message: `Icon "${params.iconName}" not found`, message: `Icon "${params.iconName}" not found`,
}); })
return sendError(event, error); return sendError(event, error)
} }
const width = getQuery(event).width || undefined; const width = getQuery(event).width || undefined
const height = getQuery(event).height || undefined; const height = getQuery(event).height || undefined
const color = getQuery(event).color || undefined; const color = getQuery(event).color || undefined
const strokeWidth = getQuery(event).strokeWidth || undefined; const strokeWidth = getQuery(event).strokeWidth || undefined
const LucideIcon = createLucideIcon(params.iconName, iconNode); const LucideIcon = createLucideIcon(params.iconName, iconNode)
const svg = Buffer.from( const svg = Buffer.from(
renderToString( renderToString(
@@ -32,13 +32,14 @@ export default eventHandler((event) => {
height, height,
color: color ? `#${color}` : undefined, color: color ? `#${color}` : undefined,
strokeWidth, strokeWidth,
}), }
), ))
).toString('utf8'); ).toString('utf8');
defaultContentType(event, 'image/svg+xml'); defaultContentType(event, 'image/svg+xml')
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000'); setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000')
setResponseHeader(event, 'Access-Control-Allow-Origin', '*'); setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
return svg; return svg
});
})

View File

@@ -1,8 +1,11 @@
import iconMetaData from '../../data/iconMetaData'; import { eventHandler, setResponseHeader } from 'h3'
import iconMetaData from '../../data/iconMetaData'
export default eventHandler((event) => { export default eventHandler((event) => {
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400'); setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
setResponseHeader(event, 'Access-Control-Allow-Origin', '*'); setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
return Object.fromEntries(Object.entries(iconMetaData).map(([name, { tags }]) => [name, tags])); return Object.fromEntries(
}); Object.entries(iconMetaData).map(([name, { tags }]) => [ name, tags ])
)
})

View File

@@ -1,3 +1,3 @@
export default eventHandler(() => { export default eventHandler(() => {
return { nitro: 'Is Awesome! asda' }; return { nitro: 'Is Awesome! asda' }
}); })

View File

@@ -1,10 +1,10 @@
import { fileURLToPath, URL } from 'node:url'; import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vitepress'; import { defineConfig } from 'vitepress'
import sidebar from './sidebar'; import sidebar from './sidebar';
const title = 'Lucide'; const title = "Lucide";
const socialTitle = 'Lucide Icons'; const socialTitle = "Lucide Icons";
const description = 'Beautiful & consistent icon toolkit made by the community.'; const description = "Beautiful & consistent icon toolkit made by the community."
// https://vitepress.dev/reference/site-config // https://vitepress.dev/reference/site-config
export default defineConfig({ export default defineConfig({
@@ -19,135 +19,86 @@ export default defineConfig({
{ {
find: /^.*\/VPIconAlignLeft\.vue$/, find: /^.*\/VPIconAlignLeft\.vue$/,
replacement: fileURLToPath( replacement: fileURLToPath(
new URL('./theme/components/overrides/VPIconAlignLeft.vue', import.meta.url), new URL('./theme/components/overrides/VPIconAlignLeft.vue', import.meta.url)
), )
}, },
{ {
find: /^.*\/VPFooter\.vue$/, find: /^.*\/VPFooter\.vue$/,
replacement: fileURLToPath( replacement: fileURLToPath(
new URL('./theme/components/overrides/VPFooter.vue', import.meta.url), new URL('./theme/components/overrides/VPFooter.vue', import.meta.url)
), )
}, }
{ ]
find: '~/.vitepress',
replacement: fileURLToPath(new URL('./', import.meta.url)),
},
],
}, },
}, },
head: [ head: [
[ [ 'script', {
'script',
{
src: 'https://analytics.lucide.dev/js/script.js', src: 'https://analytics.lucide.dev/js/script.js',
'data-domain': 'lucide.dev', 'data-domain': 'lucide.dev',
defer: '', defer: ''
}, }],
], [ 'meta', {
[ property:"og:locale",
'meta', content:"en_US"
{ }],
property: 'og:locale', [ 'meta', {
content: 'en_US', property:"og:type",
}, content:"website"
], }],
[ [ 'meta', {
'meta', property:"og:site_name",
{
property: 'og:type',
content: 'website',
},
],
[
'meta',
{
property: 'og:site_name',
content: title, content: title,
}, }],
], [ 'meta', {
[ property:"og:title",
'meta',
{
property: 'og:title',
content: socialTitle, content: socialTitle,
}, }],
], [ 'meta', {
[ property:"og:description",
'meta', content: description
{ }],
property: 'og:description', [ 'meta', {
content: description, property:"og:url",
}, content:"https://lucide.dev"
], }],
[ [ 'meta', {
'meta', property:"og:image",
{ content: "https://lucide.dev/og.png"
property: 'og:url', }],
content: 'https://lucide.dev', [ 'meta', {
}, property:"og:image:width",
], content:"1200"
[ }],
'meta', [ 'meta', {
{ property:"og:image:height",
property: 'og:image', content:"630"
content: 'https://lucide.dev/og.png', }],
}, [ 'meta', {
], property:"og:image:type",
[ content:"image/png"
'meta', }],
{ [ 'meta', {
property: 'og:image:width', property:"twitter:card",
content: '1200', content:"summary_large_image"
}, }],
], [ 'meta', {
[ property:"twitter:title",
'meta',
{
property: 'og:image:height',
content: '630',
},
],
[
'meta',
{
property: 'og:image:type',
content: 'image/png',
},
],
[
'meta',
{
property: 'twitter:card',
content: 'summary_large_image',
},
],
[
'meta',
{
property: 'twitter:title',
content: socialTitle, content: socialTitle,
}, }],
], [ 'meta', {
[ property:"twitter:description",
'meta', content: description
{ }],
property: 'twitter:description', [ 'meta', {
content: description, property:"twitter:image",
}, content:"https://lucide.dev/og.png"
], }],
[
'meta',
{
property: 'twitter:image',
content: 'https://lucide.dev/og.png',
},
],
], ],
themeConfig: { themeConfig: {
// https://vitepress.dev/reference/default-theme-config // https://vitepress.dev/reference/default-theme-config
logo: { logo: {
light: '/logo.light.svg', light: '/logo.light.svg',
dark: '/logo.dark.svg', dark: '/logo.dark.svg'
}, },
nav: [ nav: [
{ text: 'Icons', link: '/icons/' }, { text: 'Icons', link: '/icons/' },
@@ -159,21 +110,21 @@ export default defineConfig({
sidebar, sidebar,
socialLinks: [ socialLinks: [
{ icon: 'github', link: 'https://github.com/lucide-icons/lucide' }, { icon: 'github', link: 'https://github.com/lucide-icons/lucide' },
{ icon: 'discord', link: 'https://discord.gg/EH6nSts' }, { icon: 'discord', link: 'https://discord.gg/EH6nSts' }
], ],
footer: { footer: {
message: 'Released under the ISC License.', message: 'Released under the ISC License.',
copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`, copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`
}, },
editLink: { editLink: {
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path', pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path'
}, },
carbonAds: { carbonAds: {
code: 'CWYIC53U', code: 'CWYIC53U',
placement: 'lucidedev', placement: 'lucidedev'
}, }
}, },
sitemap: { sitemap: {
hostname: 'https://lucide.dev/', hostname: 'https://lucide.dev/'
}, }
}); })

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", "light": "/library-logos/tamagui.svg",
"dark": "/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

@@ -3,144 +3,87 @@
"order": 0, "order": 0,
"icon": "js", "icon": "js",
"shields": [ "shields": [
{ { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide", "href": "https://www.npmjs.com/package/lucide" },
"alt": "npm", { "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide", "href": "https://www.npmjs.com/package/lucide" }
"src": "https://img.shields.io/npm/v/lucide",
"href": "https://www.npmjs.com/package/lucide"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide",
"href": "https://www.npmjs.com/package/lucide"
}
] ]
}, },
"lucide-react": { "lucide-react": {
"order": 1, "order": 1,
"icon": "react", "icon": "react",
"shields": [ "shields": [
{ { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-react", "href": "https://www.npmjs.com/package/lucide-react" },
"alt": "npm", { "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-react", "href": "https://www.npmjs.com/package/lucide-react" }
"src": "https://img.shields.io/npm/v/lucide-react", ]
"href": "https://www.npmjs.com/package/lucide-react"
}, },
{ "lucide-vue": {
"alt": "npm", "order": 2,
"src": "https://img.shields.io/npm/dw/lucide-react", "icon": "vue",
"href": "https://www.npmjs.com/package/lucide-react" "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": { "lucide-vue-next": {
"order": 2, "order": 3,
"icon": "vue-next", "icon": "vue-next",
"shields": [ "shields": [
{ { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-vue-next", "href": "https://www.npmjs.com/package/lucide-vue-next" },
"alt": "npm", { "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-vue-next", "href": "https://www.npmjs.com/package/lucide-vue-next" }
"src": "https://img.shields.io/npm/v/lucide-vue-next",
"href": "https://www.npmjs.com/package/lucide-vue-next"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-vue-next",
"href": "https://www.npmjs.com/package/lucide-vue-next"
}
] ]
}, },
"lucide-svelte": { "lucide-svelte": {
"order": 3, "order": 4,
"icon": "svelte", "icon": "svelte",
"shields": [ "shields": [
{ { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-svelte", "href": "https://www.npmjs.com/package/lucide-svelte" },
"alt": "npm", { "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-svelte", "href": "https://www.npmjs.com/package/lucide-svelte" }
"src": "https://img.shields.io/npm/v/lucide-svelte",
"href": "https://www.npmjs.com/package/lucide-svelte"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-svelte",
"href": "https://www.npmjs.com/package/lucide-svelte"
}
] ]
}, },
"lucide-solid": { "lucide-solid": {
"order": 4, "order": 4,
"icon": "solid", "icon": "solid",
"shields": [ "shields": [
{ { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-solid", "href": "https://www.npmjs.com/package/lucide-solid" },
"alt": "npm", { "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-solid", "href": "https://www.npmjs.com/package/lucide-solid" }
"src": "https://img.shields.io/npm/v/lucide-solid",
"href": "https://www.npmjs.com/package/lucide-solid"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-solid",
"href": "https://www.npmjs.com/package/lucide-solid"
}
]
},
"lucide-react-native": {
"order": 5,
"icon": "react-native",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-react-native",
"href": "https://www.npmjs.com/package/lucide-react-native"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-react-native",
"href": "https://www.npmjs.com/package/lucide-react-native"
}
]
},
"lucide-angular": {
"order": 6,
"icon": "angular",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-angular",
"href": "https://www.npmjs.com/package/lucide-angular"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-angular",
"href": "https://www.npmjs.com/package/lucide-angular"
}
] ]
}, },
"lucide-preact": { "lucide-preact": {
"order": 7, "order": 5,
"icon": "preact", "icon": "preact",
"shields": [ "shields": [
{ { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-preact", "href": "https://www.npmjs.com/package/lucide-preact" },
"alt": "npm", { "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-preact", "href": "https://www.npmjs.com/package/lucide-preact" }
"src": "https://img.shields.io/npm/v/lucide-preact", ]
"href": "https://www.npmjs.com/package/lucide-preact"
}, },
{ "lucide-react-native": {
"alt": "npm", "order": 6,
"src": "https://img.shields.io/npm/dw/lucide-preact", "icon": "react-native",
"href": "https://www.npmjs.com/package/lucide-preact" "shields": [
} { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-react-native", "href": "https://www.npmjs.com/package/lucide-react-native" },
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-react-native", "href": "https://www.npmjs.com/package/lucide-react-native" }
]
},
"lucide-angular": {
"order": 7,
"icon": "angular",
"shields": [
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-angular", "href": "https://www.npmjs.com/package/lucide-angular" },
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-angular", "href": "https://www.npmjs.com/package/lucide-angular" }
] ]
}, },
"lucide-static": { "lucide-static": {
"order": 8, "order": 8,
"icon": "svg", "icon": "svg",
"shields": [ "shields": [
{ { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-static", "href": "https://www.npmjs.com/package/lucide-static" },
"alt": "npm", { "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-static", "href": "https://www.npmjs.com/package/lucide-static" }
"src": "https://img.shields.io/npm/v/lucide-static", ]
"href": "https://www.npmjs.com/package/lucide-static"
}, },
{ "lucide-flutter": {
"alt": "npm", "order": 9,
"src": "https://img.shields.io/npm/dw/lucide-static", "icon": "flutter",
"href": "https://www.npmjs.com/package/lucide-static" "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", "source": "https://github.com/swisnl/nuxt-lucide-icons",
"documentation": "https://github.com/swisnl/nuxt-lucide-icons/blob/main/README.md" "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

@@ -1,48 +0,0 @@
[
{
"name": "Eric Fennis",
"title": "Creator of Lucide & Software engineer @nedap",
"image": "https://github.com/ericfennis.png?size=192",
"sponsor": "https://github.com/sponsors/ericfennis",
"socialLinks": [
{
"icon": "github",
"link": "https://github.com/ericfennis"
},
{
"icon": "x",
"link": "https://x.com/ericfennis"
}
]
},
{
"name": "Karsa Rigó",
"title": "Maintainer of Lucide & Software engineer @sztaki",
"image": "https://github.com/karsa-mistmere.png?size=192",
"socialLinks": [
{
"icon": "github",
"link": "https://github.com/karsa-mistmere"
},
{
"icon": "linkedin",
"link": "https://www.linkedin.com/in/karsamistmere"
}
]
},
{
"name": "Jakob Guddas",
"title": "Maintainer of Lucide & Software engineer @LEGO",
"image": "https://github.com/jguddas.png?size=192",
"socialLinks": [
{
"icon": "github",
"link": "https://github.com/jguddas"
},
{
"icon": "linkedin",
"link": "https://www.linkedin.com/in/jguddas"
}
]
}
]

View File

@@ -3,92 +3,55 @@ import React from 'react';
interface BackdropProps { interface BackdropProps {
src: string; src: string;
color?: string; color?: string;
outline?: boolean;
backdropString: string; backdropString: string;
} }
const Backdrop = ({ const Backdrop = ({ src, color = 'red', backdropString }: BackdropProps): JSX.Element => {
src,
color = 'red',
outline = true,
backdropString,
}: BackdropProps): JSX.Element => {
const id = React.useId();
return ( return (
<> <>
<defs xmlns="http://www.w3.org/2000/svg"> <defs xmlns="http://www.w3.org/2000/svg">
<pattern <pattern
id={`pattern-${id}`} id="pattern"
width=".1" width=".1"
height=".1" height=".1"
patternUnits="userSpaceOnUse" patternUnits="userSpaceOnUse"
patternTransform="rotate(45 50 50)" patternTransform="rotate(45 50 50)"
> >
<line <line stroke={color} strokeWidth={0.1} y2={1} />
stroke={color} <line stroke={color} strokeWidth={0.1} y2={1} />
strokeWidth={0.1}
y2={1}
/>
<line
stroke={color}
strokeWidth={0.1}
y2={1}
/>
</pattern> </pattern>
</defs> </defs>
<mask <mask id="svg-preview-backdrop-mask-outline" maskUnits="userSpaceOnUse">
id={`svg-preview-backdrop-mask-${id}`} <g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} />
maskUnits="userSpaceOnUse" <g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} />
>
<g
stroke="#fff"
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
<g dangerouslySetInnerHTML={{ __html: src }} />
</mask> </mask>
<mask <mask id="svg-preview-backdrop-mask-fill" maskUnits="userSpaceOnUse">
id={`svg-preview-backdrop-mask-outline-${id}`} <g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} />
maskUnits="userSpaceOnUse" <g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} />
> <g strokeWidth={1.75} dangerouslySetInnerHTML={{ __html: backdropString }} />
<rect
x="0"
y="0"
width="24"
height="24"
fill="#fff"
stroke="none"
/>
<g
strokeWidth={1.75}
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
</mask> </mask>
<g mask={`url(#svg-preview-backdrop-mask-${id})`}> <g strokeWidth={2.25} stroke="url(#pattern)" mask={'url(#svg-preview-backdrop-mask-outline)'}>
<rect <rect x="0" y="0" width="24" height="24" fill="url(#pattern)" opacity={0.5} stroke="none" />
x="0"
y="0"
width="24"
height="24"
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> </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 { PathProps, Path } from './types';
import { getPaths, assert } from './utils'; import { getPaths, assert } from './utils';
export const darkModeCss = ` const Grid = ({
@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 = ({
radius, radius,
fill = '#fff', fill = '#fff',
...props ...props
@@ -26,11 +10,7 @@ export const Grid = ({
strokeWidth: number; strokeWidth: number;
radius: number; radius: number;
} & PathProps<'stroke', 'strokeWidth'>) => ( } & PathProps<'stroke', 'strokeWidth'>) => (
<g <g className="svg-preview-grid-group" strokeLinecap="butt" {...props}>
className="svg-preview-grid-group"
strokeLinecap="butt"
{...props}
>
<rect <rect
className="svg-preview-grid-rect" className="svg-preview-grid-rect"
width={24 - props.strokeWidth} width={24 - props.strokeWidth}
@@ -41,29 +21,11 @@ export const Grid = ({
fill={fill} fill={fill}
/> />
<path <path
strokeDasharray={'0 0.1 ' + '0.1 0.15 '.repeat(11) + '0 0.15'}
strokeWidth={0.1}
d={ d={
props.d || props.d ||
new Array(Math.floor(24 - 1)) new Array(Math.floor(24 - 1))
.fill(null) .fill(null)
.map((_, i) => i) .flatMap((_, i) => [
.filter((i) => i % 3 !== 2)
.flatMap((i) => [
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
])
.join('')
}
/>
<path
d={
props.d ||
new Array(Math.floor(24 - 1))
.fill(null)
.map((_, i) => i)
.filter((i) => i % 3 === 2)
.flatMap((i) => [
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`, `M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`, `M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
]) ])
@@ -82,21 +44,15 @@ const Shadow = ({
paths: Path[]; paths: Path[];
} & PathProps<'stroke' | 'strokeWidth' | 'strokeOpacity', 'd'>) => { } & PathProps<'stroke' | 'strokeWidth' | 'strokeOpacity', 'd'>) => {
const groupedPaths = Object.entries( const groupedPaths = Object.entries(
paths.reduce( paths.reduce((groups, val) => {
(groups, val) => {
const key = val.c.id; const key = val.c.id;
groups[key] = [...(groups[key] || []), val]; groups[key] = [...(groups[key] || []), val];
return groups; return groups;
}, }, {} as Record<number, Path[]>)
{} as Record<number, Path[]>,
),
); );
return ( return (
<> <>
<g <g className="svg-preview-shadow-mask-group" {...props}>
className="svg-preview-shadow-mask-group"
{...props}
>
{groupedPaths.map(([id, paths]) => ( {groupedPaths.map(([id, paths]) => (
<mask <mask
id={`svg-preview-shadow-mask-${id}`} id={`svg-preview-shadow-mask-${id}`}
@@ -105,15 +61,7 @@ const Shadow = ({
strokeWidth={props.strokeWidth} strokeWidth={props.strokeWidth}
stroke="#000" stroke="#000"
> >
<rect <rect x={0} y={0} width={24} height={24} fill="#fff" stroke="none" rx={radius} />
x={0}
y={0}
width={24}
height={24}
fill="#fff"
stroke="none"
rx={radius}
/>
<path <path
d={paths d={paths
.flatMap(({ prev, next }) => [ .flatMap(({ prev, next }) => [
@@ -126,16 +74,9 @@ const Shadow = ({
</mask> </mask>
))} ))}
</g> </g>
<g <g className="svg-preview-shadow-group" {...props}>
className="svg-preview-shadow-group"
{...props}
>
{paths.map(({ d, c: { id } }, i) => ( {paths.map(({ d, c: { id } }, i) => (
<path <path key={i} mask={`url(#svg-preview-shadow-mask-${id})`} d={d} />
key={i}
mask={`url(#svg-preview-shadow-mask-${id})`}
d={d}
/>
))} ))}
<path <path
d={paths d={paths
@@ -153,16 +94,9 @@ const ColoredPath = ({
paths, paths,
...props ...props
}: { paths: Path[]; colors: string[] } & PathProps<never, 'd' | 'stroke'>) => ( }: { paths: Path[]; colors: string[] } & PathProps<never, 'd' | 'stroke'>) => (
<g <g className="svg-preview-colored-path-group" {...props}>
className="svg-preview-colored-path-group"
{...props}
>
{paths.map(({ d, c }, i) => ( {paths.map(({ d, c }, i) => (
<path <path key={i} d={d} stroke={colors[(c.name === 'path' ? i : c.id) % colors.length]} />
key={i}
d={d}
stroke={colors[(c.name === 'path' ? i : c.id) % colors.length]}
/>
))} ))}
</g> </g>
); );
@@ -204,15 +138,7 @@ const ControlPath = ({
key={i} key={i}
maskUnits="userSpaceOnUse" maskUnits="userSpaceOnUse"
> >
<rect <rect x="0" y="0" width="24" height="24" fill="#fff" stroke="none" rx={radius} />
x="0"
y="0"
width="24"
height="24"
fill="#fff"
stroke="none"
rx={radius}
/>
<path d={`M${prev.x} ${prev.y}h.01`} /> <path d={`M${prev.x} ${prev.y}h.01`} />
<path d={`M${next.x} ${next.y}h.01`} /> <path d={`M${next.x} ${next.y}h.01`} />
</mask> </mask>
@@ -220,10 +146,7 @@ const ControlPath = ({
); );
})} })}
</g> </g>
<g <g className="svg-preview-control-path-group" {...props}>
className="svg-preview-control-path-group"
{...props}
>
{controlPaths.map(({ d, showMarker }, i) => ( {controlPaths.map(({ d, showMarker }, i) => (
<path <path
key={i} key={i}
@@ -232,33 +155,18 @@ const ControlPath = ({
/> />
))} ))}
</g> </g>
<g <g className="svg-preview-control-path-marker-group" {...props}>
className="svg-preview-control-path-marker-group"
{...props}
>
<path <path
d={controlPaths d={controlPaths
.flatMap(({ prev, next, showMarker }) => .flatMap(({ prev, next, showMarker }) =>
showMarker ? [`M${prev.x} ${prev.y}h.01`, `M${next.x} ${next.y}h.01`] : [], showMarker ? [`M${prev.x} ${prev.y}h.01`, `M${next.x} ${next.y}h.01`] : []
) )
.join('')} .join('')}
/> />
{controlPaths.map(({ d, prev, next, startMarker, endMarker }, i) => ( {controlPaths.map(({ d, prev, next, startMarker, endMarker }, i) => (
<React.Fragment key={i}> <React.Fragment key={i}>
{startMarker && ( {startMarker && <circle cx={prev.x} cy={prev.y} r={pointSize / 2} />}
<circle {endMarker && <circle cx={next.x} cy={next.y} r={pointSize / 2} />}
cx={prev.x}
cy={prev.y}
r={pointSize / 2}
/>
)}
{endMarker && (
<circle
cx={next.x}
cy={next.y}
r={pointSize / 2}
/>
)}
</React.Fragment> </React.Fragment>
))} ))}
</g> </g>
@@ -274,16 +182,15 @@ const Radii = ({
any any
>) => { >) => {
return ( return (
<g <g className="svg-preview-radii-group" {...props}>
className="svg-preview-radii-group"
{...props}
>
{paths.map( {paths.map(
({ c, prev, next, circle }, i) => ({ c, prev, next, circle }, i) =>
circle && ( circle && (
<React.Fragment key={i}> <React.Fragment key={i}>
{c.name !== 'circle' && ( {c.name !== "circle" && (
<path d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`} /> <path
d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`}
/>
)} )}
<circle <circle
cy={circle.y} cy={circle.y}
@@ -293,7 +200,7 @@ const Radii = ({
stroke={ stroke={
(Math.round(circle.x * 100) / 100) % 1 !== 0 || (Math.round(circle.x * 100) / 100) % 1 !== 0 ||
(Math.round(circle.y * 100) / 100) % 1 !== 0 (Math.round(circle.y * 100) / 100) % 1 !== 0
? 'red' ? "red"
: undefined : undefined
} }
/> />
@@ -301,7 +208,11 @@ const Radii = ({
cy={circle.y} cy={circle.y}
cx={circle.x} cx={circle.x}
r={circle.r} r={circle.r}
stroke={(Math.round(circle.r * 1000) / 1000) % 1 !== 0 ? 'red' : undefined} stroke={
(Math.round(circle.r * 1000) / 1000) % 1 !== 0
? "red"
: undefined
}
/> />
</React.Fragment> </React.Fragment>
), ),
@@ -317,29 +228,15 @@ const Handles = ({
'strokeWidth' | 'stroke' | 'strokeDasharray' | 'strokeOpacity', 'strokeWidth' | 'stroke' | 'strokeDasharray' | 'strokeOpacity',
any any
>) => { >) => {
console.log(paths);
return ( return (
<g <g className="svg-preview-handles-group" {...props}>
className="svg-preview-handles-group"
{...props}
>
{paths.map(({ c, prev, next, cp1, cp2 }) => ( {paths.map(({ c, prev, next, cp1, cp2 }) => (
<> <>
{cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />} {cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />}
{cp1 && ( {cp1 && <circle cy={cp1.y} cx={cp1.x} r={0.25} />}
<circle
cy={cp1.y}
cx={cp1.x}
r={0.25}
/>
)}
{cp2 && <path d={`M${next.x} ${next.y} ${cp2.x} ${cp2.y}`} />} {cp2 && <path d={`M${next.x} ${next.y} ${cp2.x} ${cp2.y}`} />}
{cp2 && ( {cp2 && <circle cy={cp2.y} cx={cp2.x} r={0.25} />}
<circle
cy={cp2.y}
cx={cp2.x}
r={0.25}
/>
)}
</> </>
))} ))}
</g> </g>
@@ -355,6 +252,19 @@ const SvgPreview = React.forwardRef<
>(({ src, children, showGrid = false, ...props }, ref) => { >(({ src, children, showGrid = false, ...props }, ref) => {
const paths = typeof src === 'string' ? getPaths(src) : src; 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 ( return (
<svg <svg
ref={ref} ref={ref}
@@ -370,27 +280,9 @@ const SvgPreview = React.forwardRef<
{...props} {...props}
> >
<style>{darkModeCss}</style> <style>{darkModeCss}</style>
{showGrid && ( {showGrid && <Grid strokeWidth={0.1} stroke="#777" strokeOpacity={0.3} radius={1} />}
<Grid <Shadow paths={paths} strokeWidth={4} stroke="#777" radius={1} strokeOpacity={0.15} />
strokeWidth={0.1} <Handles paths={paths} strokeWidth={0.12} stroke="#777" strokeOpacity={0.6} />
stroke="#777"
strokeOpacity={0.3}
radius={1}
/>
)}
<Shadow
paths={paths}
strokeWidth={4}
stroke="#777"
radius={1}
strokeOpacity={0.15}
/>
<Handles
paths={paths}
strokeWidth={0.12}
stroke="#777"
strokeOpacity={0.6}
/>
<ColoredPath <ColoredPath
paths={paths} paths={paths}
colors={[ colors={[
@@ -415,19 +307,8 @@ const SvgPreview = React.forwardRef<
stroke="#777" stroke="#777"
strokeOpacity={0.3} strokeOpacity={0.3}
/> />
<ControlPath <ControlPath radius={1} paths={paths} pointSize={1} stroke="#fff" strokeWidth={0.125} />
radius={1} <Handles paths={paths} strokeWidth={0.12} stroke="#FFF" strokeOpacity={0.3} />
paths={paths}
pointSize={1}
stroke="#fff"
strokeWidth={0.125}
/>
<Handles
paths={paths}
strokeWidth={0.12}
stroke="#FFF"
strokeOpacity={0.3}
/>
{children} {children}
</svg> </svg>
); );

View File

@@ -16,7 +16,7 @@ export type Path = {
export type PathProps< export type PathProps<
RequiredProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>, RequiredProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
NeverProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>, NeverProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>
> = Required<Pick<React.SVGProps<SVGElement & SVGRectElement & SVGCircleElement>, RequiredProps>> & > = Required<Pick<React.SVGProps<SVGElement & SVGRectElement & SVGCircleElement>, RequiredProps>> &
Omit< Omit<
React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>, React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>,

View File

@@ -51,7 +51,7 @@ export const getCommands = (src: string) =>
getNodes(src) getNodes(src)
.map(convertToPathNode) .map(convertToPathNode)
.flatMap(({ d, name }, idx) => .flatMap(({ d, name }, idx) =>
new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })), new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name }))
); );
export const getPaths = (src: string) => { export const getPaths = (src: string) => {
@@ -60,10 +60,10 @@ export const getPaths = (src: string) => {
let prev: Point | undefined = undefined; let prev: Point | undefined = undefined;
let start: Point | undefined = undefined; let start: Point | undefined = undefined;
const addPath = ( const addPath = (
c: (typeof commands)[number], c: typeof commands[number],
next: Point, next: Point,
d?: string, d?: string,
extras?: { circle?: Path['circle']; cp1?: Path['cp1']; cp2?: Path['cp2'] }, extras?: { circle?: Path['circle']; cp1?: Path['cp1']; cp2?: Path['cp2'] }
) => { ) => {
assert(prev); assert(prev);
paths.push({ paths.push({
@@ -153,7 +153,7 @@ export const getPaths = (src: string) => {
{ {
cp1: { x: prev.x - reflectedCp1.x, y: prev.y - reflectedCp1.y }, cp1: { x: prev.x - reflectedCp1.x, y: prev.y - reflectedCp1.y },
cp2: { x: c.x2, y: c.y2 }, cp2: { x: c.x2, y: c.y2 },
}, }
); );
break; break;
} }
@@ -169,7 +169,7 @@ export const getPaths = (src: string) => {
assert(prev); assert(prev);
const backTrackCP = ( const backTrackCP = (
index: number, index: number,
currentPoint: { x: number; y: number }, currentPoint: { x: number; y: number }
): { x: number; y: number } => { ): { x: number; y: number } => {
const previousCommand = commands[index - 1]; const previousCommand = commands[index - 1];
if (!previousCommand) { if (!previousCommand) {
@@ -211,7 +211,7 @@ export const getPaths = (src: string) => {
{ {
cp1: { x: prevCP.x, y: prevCP.y }, cp1: { x: prevCP.x, y: prevCP.y },
cp2: { x: prevCP.x, y: prevCP.y }, cp2: { x: prevCP.x, y: prevCP.y },
}, }
); );
break; break;
} }
@@ -226,13 +226,13 @@ export const getPaths = (src: string) => {
c.lArcFlag, c.lArcFlag,
c.sweepFlag, c.sweepFlag,
c.x, c.x,
c.y, c.y
); );
addPath( addPath(
c, c,
c, c,
`M ${prev.x} ${prev.y} A${c.rX} ${c.rY} ${c.xRot} ${c.lArcFlag} ${c.sweepFlag} ${c.x} ${c.y}`, `M ${prev.x} ${prev.y} A${c.rX} ${c.rY} ${c.xRot} ${c.lArcFlag} ${c.sweepFlag} ${c.x} ${c.y}`,
{ circle: c.rX === c.rY ? { ...center, r: c.rX } : undefined }, { circle: c.rX === c.rY ? { ...center, r: c.rX } : undefined }
); );
break; break;
} }
@@ -253,7 +253,7 @@ export const arcEllipseCenter = (
fa: number, fa: number,
fs: number, fs: number,
x2: number, x2: number,
y2: number, y2: number
) => { ) => {
const phi = (a * Math.PI) / 180; const phi = (a * Math.PI) / 180;
@@ -280,7 +280,7 @@ export const arcEllipseCenter = (
sign * sign *
Math.sqrt( Math.sqrt(
Math.max(rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p, 0) / Math.max(rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p, 0) /
(rx * rx * y1p * y1p + ry * ry * x1p * x1p), (rx * rx * y1p * y1p + ry * ry * x1p * x1p)
); );
const V2 = [(rx * y1p) / ry, (-ry * x1p) / rx]; const V2 = [(rx * y1p) / ry, (-ry * x1p) / rx];

View File

@@ -1,34 +1,28 @@
import fs from 'fs'; import fs from "fs";
import path from 'path'; import path from "path";
import { Category, IconEntity } from '../theme/types'; import {Category, IconEntity} from "../theme/types";
const directory = path.join(process.cwd(), '../categories'); const directory = path.join(process.cwd(), "../categories");
export function getAllCategoryFiles(): Category[] { export function getAllCategoryFiles(): Category[] {
const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json'); const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json');
return fileNames.map((fileName) => { return fileNames.map((fileName) => {
const name = path.basename(fileName, '.json'); const name = path.basename(fileName, '.json')
const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8'); const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8')
const parsedFileContent = JSON.parse(fileContent); const parsedFileContent = JSON.parse(fileContent)
return { return {
name, name,
title: parsedFileContent.title, title: parsedFileContent.title,
}; }
}); });
} }
export function mapCategoryIconCount( export function mapCategoryIconCount(categories: Category[], icons: { categories: IconEntity['categories'] }[]) {
categories: Category[],
icons: { categories: IconEntity['categories'] }[],
) {
return categories.map((category) => ({ return categories.map((category) => ({
...category, ...category,
iconCount: icons.reduce( iconCount: icons.reduce((acc, curr) => (curr.categories.includes(category.name) ? ++acc : acc), 0)
(acc, curr) => (curr.categories.includes(category.name) ? ++acc : acc), }))
0,
),
}));
} }

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

@@ -1,33 +1,33 @@
import { bundledLanguages, type ThemeRegistration } from 'shikiji'; import {
import { getHighlighter } from 'shikiji'; bundledLanguages,
type ThemeRegistration
} from 'shikiji'
import {
getHighlighter,
} from 'shikiji'
type CodeExampleType = { type CodeExampleType = {
title: string; title: string,
language: string; language: string,
code: string; code: string,
}[]; }[]
const getIconCodes = (): CodeExampleType => { const getIconCodes = (): CodeExampleType => {
return [ return [
{ {
language: 'js', language: 'html',
title: 'Vanilla', title: 'HTML',
code: `\ code: `<i data-lucide="Name"></i>`
import { createIcons, icons } from 'lucide';
createIcons({ icons });
document.body.append('<i data-lucide="$Name"></i>');\
`,
}, },
{ {
language: 'tsx', language: 'tsx',
title: 'React', title: 'React',
code: `import { $PascalCase } from 'lucide-react'; code: `import { PascalCase } from 'lucide-react';
const App = () => { const App = () => {
return ( return (
<$PascalCase /> <PascalCase />
); );
}; };
@@ -38,11 +38,11 @@ export default App;
language: 'vue', language: 'vue',
title: 'Vue', title: 'Vue',
code: `<script setup> code: `<script setup>
import { $PascalCase } from 'lucide-vue-next'; import { PascalCase } from 'lucide-vue-next';
</script> </script>
<template> <template>
<$PascalCase /> <PascalCase />
</template> </template>
`, `,
}, },
@@ -50,20 +50,20 @@ import { $PascalCase } from 'lucide-vue-next';
language: 'svelte', language: 'svelte',
title: 'Svelte', title: 'Svelte',
code: `<script> code: `<script>
import { $PascalCase } from 'lucide-svelte'; import { PascalCase } from 'lucide-svelte';
</script> </script>
<$PascalCase /> <PascalCase />
`, `,
}, },
{ {
language: 'tsx', language: 'tsx',
title: 'Preact', title: 'Preact',
code: `import { $PascalCase } from 'lucide-preact'; code: `import { PascalCase } from 'lucide-preact';
const App = () => { const App = () => {
return ( return (
<$PascalCase /> <PascalCase />
); );
}; };
@@ -73,11 +73,11 @@ export default App;
{ {
language: 'tsx', language: 'tsx',
title: 'Solid', title: 'Solid',
code: `import { $PascalCase } from 'lucide-solid'; code: `import { PascalCase } from 'lucide-solid';
const App = () => { const App = () => {
return ( return (
<$PascalCase /> <PascalCase />
); );
}; };
@@ -88,16 +88,16 @@ export default App;
language: 'tsx', language: 'tsx',
title: 'Angular', title: 'Angular',
code: `// app.module.ts code: `// app.module.ts
import { LucideAngularModule, $PascalCase } from 'lucide-angular'; import { LucideAngularModule, PascalCase } from 'lucide-angular';
@NgModule({ @NgModule({
imports: [ imports: [
LucideAngularModule.pick({ $PascalCase }) LucideAngularModule.pick({ PascalCase })
], ],
}) })
// app.component.html // app.component.html
<lucide-icon name="$Name"></lucide-icon> <lucide-icon name="Name"></lucide-icon>
`, `,
}, },
{ {
@@ -107,39 +107,38 @@ import { LucideAngularModule, $PascalCase } from 'lucide-angular';
@import ('~lucide-static/font/Lucide.css'); @import ('~lucide-static/font/Lucide.css');
</style> </style>
<div class="icon-$Name"></div> <div class="icon-Name"></div>
`, `,
}, }
]; ]
}; }
export type ThemeOptions = export type ThemeOptions =
| ThemeRegistration | ThemeRegistration
| { light: ThemeRegistration; dark: ThemeRegistration }; | { light: ThemeRegistration; dark: ThemeRegistration }
const highLightCode = async (code: string, lang: string, active?: boolean) => { const highLightCode = async (code: string, lang: string, active?: boolean) => {
const highlighter = await getHighlighter({ const highlighter = await getHighlighter({
themes: ['github-light', 'github-dark'], themes: ['github-light', 'github-dark'],
langs: Object.keys(bundledLanguages), langs: Object.keys(bundledLanguages)
}); })
const highlightedCode = highlighter const highlightedCode = highlighter.codeToHtml(code, {
.codeToHtml(code, {
lang, lang,
themes: { themes: {
light: 'github-light', light: 'github-light',
dark: 'github-dark', dark: 'github-dark'
}, },
defaultColor: false, defaultColor: false
}) }).replace('shiki-themes', 'shiki-themes vp-code')
.replace('shiki-themes', 'shiki-themes vp-code');
return `<div class="language-${lang} ${active ? 'active' : ''}"> return `<div class="language-${lang} ${active ? 'active' : ''}">
<button title="Copy Code" class="copy"></button> <button title="Copy Code" class="copy"></button>
<span class="lang">${lang}</span> <span class="lang">${lang}</span>
${highlightedCode} ${highlightedCode}
</div>`; </div>`
}; }
export default async function createCodeExamples() { export default async function createCodeExamples() {
const codes = getIconCodes(); const codes = getIconCodes();
@@ -154,7 +153,7 @@ export default async function createCodeExamples() {
language: language, language: language,
code: codeString, code: codeString,
}; };
}); })
return Promise.all(codeExamplePromises); return Promise.all(codeExamplePromises);
} }

View File

@@ -1,36 +1,38 @@
import { promises as fs, constants } from 'fs'; import { promises as fs, constants } from 'fs';
import path from 'path'; import path from 'path';
import yaml from 'js-yaml'
import { PackageItem } from '../theme/types'; import { PackageItem } from '../theme/types';
const fileExist = (filePath) => const fileExist = (filePath) => fs.access(filePath, constants.F_OK).then(() => true).catch(() => false)
fs
.access(filePath, constants.F_OK)
.then(() => true)
.catch(() => false);
const fetchPackages = async (): Promise<PackageItem[]> => { const fetchPackages = async (): Promise<PackageItem[]> => {
const docsDir = path.resolve(process.cwd(), '../packages'); const docsDir = path.resolve(process.cwd(), '../packages');
const fileNames = await ( const fileNames = await (await fs.readdir(docsDir)).map(filename => ({filename, directory: docsDir}))
await fs.readdir(docsDir)
).map((filename) => ({ filename, directory: docsDir }));
const packageJsons = await Promise.all( const packageJsons = await Promise.all(fileNames.map( async ({filename, directory}) => {
fileNames.map(async ({ filename, directory }) => { const filePath = path.resolve(directory, filename)
const filePath = path.resolve(directory, filename);
const fileStat = await fs.lstat(filePath); const fileStat = await fs.lstat(filePath);
if(!fileStat.isDirectory()) return null; if(!fileStat.isDirectory()) return null;
const jsonFilePath = path.resolve(filePath, 'package.json'); const jsonFilePath = path.resolve(filePath, 'package.json')
if (await fileExist(jsonFilePath)) { if (await fileExist(jsonFilePath)) {
return JSON.parse(await fs.readFile(jsonFilePath, 'utf-8')); return JSON.parse(
await fs.readFile(jsonFilePath, 'utf-8')
)
} }
return null; const ymlFilePath = path.resolve(filePath, 'pubspec.yaml')
}), if(await fileExist(ymlFilePath)) {
return yaml.load(
await fs.readFile(ymlFilePath, 'utf-8')
); );
}
return packageJsons; return null
}; }))
return packageJsons
}
export default fetchPackages; export default fetchPackages;

View File

@@ -1,15 +1,17 @@
export type IconContent = [icon: string, src:string]; export type IconContent = [icon: string, src:string];
async function generateZip(icons: IconContent[]) { async function generateZip(icons: IconContent[]) {
const JSZip = (await import('jszip')).default; const JSZip = (await import('jszip')).default
const zip = new JSZip(); const zip = new JSZip();
const addingZipPromises = icons.map(([name, src]) => zip.file(`${name}.svg`, src)); const addingZipPromises = icons.map(([name, src]) =>
zip.file(`${name}.svg`, src),
);
await Promise.all(addingZipPromises); await Promise.all(addingZipPromises)
return zip.generateAsync({ type: 'blob' }); return zip.generateAsync({ type: 'blob' });
} }
export default generateZip; export default generateZip

View File

@@ -1,15 +1,17 @@
import { createLucideIcon } from 'lucide-react/src/lucide-react'; import { createLucideIcon } from "lucide-react/src/lucide-react"
import { type LucideProps, type IconNode } from 'lucide-react/src/createLucideIcon'; import { type LucideProps, type IconNode } from "lucide-react/src/createLucideIcon"
import { IconEntity } from '../theme/types'; import { IconEntity } from "../theme/types"
import { renderToStaticMarkup } from 'react-dom/server'; import { renderToStaticMarkup } from 'react-dom/server';
import { IconContent } from './generateZip'; import { IconContent } from "./generateZip";
const getFallbackZip = (icons: IconEntity[], params: LucideProps) => { const getFallbackZip = (icons: IconEntity[], params: LucideProps) => {
return icons.map<IconContent>((icon) => { return icons
const Icon = createLucideIcon(icon.name, icon.iconNode as IconNode); .map<IconContent>((icon) => {
const src = renderToStaticMarkup(<Icon {...params} />); const Icon = createLucideIcon(icon.name, icon.iconNode as IconNode)
return [icon.name, src]; const src = renderToStaticMarkup(<Icon {...params} />)
}); return [icon.name, src]
}; })
}
export default getFallbackZip;
export default getFallbackZip

View File

@@ -1,34 +1,34 @@
import fs from 'fs'; import fs from "fs";
import path from 'path'; import path from "path";
import { IconNodeWithKeys } from '../theme/types'; import { IconNodeWithKeys } from "../theme/types";
import iconNodes from '../data/iconNodes'; import iconNodes from '../data/iconNodes'
import releaseMeta from '../data/releaseMetaData.json'; import releaseMeta from "../data/releaseMetaData.json";
const DATE_OF_FORK = '2020-06-08T16:39:52+0100'; const DATE_OF_FORK = '2020-06-08T16:39:52+0100';
const directory = path.join(process.cwd(), '../icons'); const directory = path.join(process.cwd(), "../icons");
export interface GetDataOptions { export interface GetDataOptions {
withChildKeys?: boolean; withChildKeys?: boolean
} }
export async function getData(name: string) { export async function getData(name: string) {
const jsonPath = path.join(directory, `${name}.json`); const jsonPath = path.join(directory, `${name}.json`);
const jsonContent = fs.readFileSync(jsonPath, 'utf8'); const jsonContent = fs.readFileSync(jsonPath, "utf8");
const { tags, categories, contributors } = JSON.parse(jsonContent); const { tags, categories, contributors } = JSON.parse(jsonContent);
const iconNode = iconNodes[name]; const iconNode = iconNodes[name]
const releaseData = releaseMeta?.[name] ?? { const releaseData = releaseMeta?.[name] ?? {
createdRelease: { "createdRelease": {
version: '0.0.0', "version": "0.0.0",
date: DATE_OF_FORK, "date": DATE_OF_FORK
}, },
changedRelease: { "changedRelease": {
version: '0.0.0', "version": "0.0.0",
date: DATE_OF_FORK, "date": DATE_OF_FORK
}, }
}; }
return { return {
name, name,
@@ -36,11 +36,11 @@ export async function getData(name: string) {
categories, categories,
iconNode, iconNode,
contributors, contributors,
...releaseData, ...releaseData
}; };
} }
export async function getAllData(): Promise<{ name: string; iconNode: IconNodeWithKeys }[]> { export async function getAllData(): Promise<{ name: string, iconNode: IconNodeWithKeys}[]> {
const names = Object.keys(iconNodes); const names = Object.keys(iconNodes);
return Promise.all(names.map((name) => getData(name))); return Promise.all(names.map((name) => getData(name)));

View File

@@ -1,54 +1,50 @@
import { DefaultTheme, UserConfig } from 'vitepress'; import { DefaultTheme, UserConfig } from "vitepress"
const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = { const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
guide: [ 'guide':[
{ {
text: 'Introduction', text: 'Introduction',
items: [ items: [
{ text: 'What is lucide?', link: '/guide/' }, { text: 'What is lucide?', link: '/guide/' },
{ text: 'Installation', link: '/guide/installation' }, { text: 'Installation', link: '/guide/installation' },
{ text: 'Comparison', link: '/guide/comparison' }, { text: 'Comparison', link: '/guide/comparison' }
], ]
}, },
{ {
text: 'Basics', text: 'Basics',
items: [ items: [
{ {
text: 'Color', text: 'Color',
link: '/guide/basics/color', link: '/guide/basics/color'
}, },
{ {
text: 'Sizing', text: 'Sizing',
link: '/guide/basics/sizing', link: '/guide/basics/sizing'
}, },
{ {
text: 'Stroke width', text: 'Stroke width',
link: '/guide/basics/stroke-width', link: '/guide/basics/stroke-width'
}, },
], ]
}, },
// TODO: Add this section // TODO: Add this section
{ {
text: 'Advanced', text: 'Advanced',
items: [ items: [
{ // {
text: 'Accessibility', // text: 'Accessibility',
link: '/guide/advanced/accessibility', // link: '/guide/advanced/accessibility'
}, // },
{ {
text: 'Global styling', text: 'Global styling',
link: '/guide/advanced/global-styling', link: '/guide/advanced/global-styling'
}, },
// { // {
// text: 'Animations', // text: 'Animations',
// }, // },
{ {
text: 'Filled icons', text: 'Filled icons',
link: '/guide/advanced/filled-icons', link: '/guide/advanced/filled-icons'
},
{
text: 'Aliased Names',
link: '/guide/advanced/aliased-names',
}, },
// { // {
// text: 'Combining icons', // text: 'Combining icons',
@@ -59,77 +55,75 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
// { // {
// text: 'Auto importing' // text: 'Auto importing'
// }, // },
], ]
}, },
{ {
text: 'Packages', text: 'Packages',
items: [ items: [
{ {
text: 'Lucide', text: 'Lucide',
link: '/guide/packages/lucide', link: '/guide/packages/lucide'
}, },
{ {
text: 'Lucide React', text: 'Lucide React',
link: '/guide/packages/lucide-react', link: '/guide/packages/lucide-react'
},
{
text: 'Lucide Vue',
link: '/guide/packages/lucide-vue-next',
},
{
text: 'Lucide Svelte',
link: '/guide/packages/lucide-svelte',
},
{
text: 'Lucide Solid',
link: '/guide/packages/lucide-solid',
}, },
{ {
text: 'Lucide React Native', text: 'Lucide React Native',
link: '/guide/packages/lucide-react-native', link: '/guide/packages/lucide-react-native'
}, },
{ {
text: 'Lucide Angular', text: 'Lucide Vue',
link: '/guide/packages/lucide-angular', link: '/guide/packages/lucide-vue-next'
},
{
text: 'Lucide Svelte',
link: '/guide/packages/lucide-svelte'
},
{
text: 'Lucide Solid',
link: '/guide/packages/lucide-solid'
}, },
{ {
text: 'Lucide Preact', text: 'Lucide Preact',
link: '/guide/packages/lucide-preact', link: '/guide/packages/lucide-preact'
},
{
text: 'Lucide Angular',
link: '/guide/packages/lucide-angular'
}, },
{ {
text: 'Lucide Static', text: 'Lucide Static',
link: '/guide/packages/lucide-static', link: '/guide/packages/lucide-static'
}, },
], ]
}, },
{ {
text: 'Contributing', text: 'Contributing',
items: [ items: [
{ {
text: 'Icon Design Principles', text: 'Icon Design Principles',
link: '/guide/design/icon-design-guide', link: '/guide/design/icon-design-guide'
}, },
{ {
text: 'Designing in Illustrator', text: 'Designing in Illustrator',
link: '/guide/design/illustrator-guide', link: '/guide/design/illustrator-guide'
}, },
{ {
text: 'Designing in InkScape', text: 'Designing in InkScape',
link: '/guide/design/inkscape-guide', link: '/guide/design/inkscape-guide'
}, },
{ {
text: 'Designing in Figma', text: 'Designing in Figma',
link: '/guide/design/figma-guide', link: '/guide/design/figma-guide'
}, },
{ ]
text: 'Designing in Affinity Designer',
link: '/guide/design/affinity-designer-guide',
},
],
}, },
], ],
// This should be here to keep the sidebar shown on the icons page // This should be here to keep the sidebar shown on the icons page
icons: [{ text: '', link: '/' }], 'icons': [
}; { text: '', link: '/' },
],
}
export default sidebar; export default sidebar

View File

@@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import { useRouter } from 'vitepress'; import { useRouter } from 'vitepress';
const { go } = useRouter() const { go } = useRouter()
@@ -7,16 +8,7 @@ const props = defineProps<{
href?: string href?: string
}>() }>()
const isExternal = computed(() => props.href?.startsWith('http') ?? false)
const component = computed(() => props.href ? 'a' : 'div') 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> </script>
<template> <template>
@@ -24,9 +16,7 @@ const onClick = computed(() => {
:is="component" :is="component"
:href="href" :href="href"
class="badge" class="badge"
:target="target" @click="props?.href ? go(href) : undefined"
:rel="rel"
@click="onClick"
> >
<slot/> <slot/>
</component> </component>

View File

@@ -1,11 +1,11 @@
<template> <template>
<div class="card-grid-flex"> <div class="grid">
<slot /> <slot />
</div> </div>
</template> </template>
<style> <style scoped>
.card-grid-flex { .grid {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: stretch; align-items: stretch;
@@ -15,20 +15,20 @@
margin: -8px; margin: -8px;
} }
.card-grid-flex > * { .grid > * {
flex-basis: 100%; flex-basis: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 8px; padding: 8px;
} }
@media (min-width: 960px) { @media (min-width: 960px) {
.card-grid-flex > * { .grid > * {
flex-basis: 50%; flex-basis: 50%;
} }
} }
@media (min-width: 1280px) { @media (min-width: 1280px) {
.card-grid-flex > * { .grid > * {
flex-basis: 33.33%; flex-basis: 33.33%;
} }
} }

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

@@ -24,10 +24,40 @@ const headingElement = computed(() => `h${props.headingLevel}`)
font-size: 32px; font-size: 32px;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
margin-bottom: 36px; margin-bottom: 32px;
} }
section { section {
margin-bottom: 96px; margin-bottom: 96px;
} }
.grid {
display: flex;
flex-wrap: wrap;
align-items: stretch;
justify-content: center;
align-content: space-evenly;
box-sizing: border-box;
margin: -8px;
}
.grid > * {
flex-basis: 100%;
box-sizing: border-box;
padding: 8px;
}
@media (min-width: 960px) {
.grid > * {
flex-basis: 50%;
}
}
@media (min-width: 1280px) {
.grid > * {
flex-basis: 33.33%;
}
}
</style> </style>

View File

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

View File

@@ -1,18 +1,16 @@
export default { export default {
async load() { async load() {
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest') const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest').then(res => {
.then((res) => {
if (res.ok) { if (res.ok) {
const releaseData = res.json() as Promise<{ tag_name: string }>; const releaseData = res.json() as Promise<{ tag_name: string }>
return releaseData; return releaseData
} }
return null; return null
}) }).then(res => res.tag_name)
.then((res) => res?.tag_name);
return { return {
version, version
}; }
}, }
}; }

View File

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

View File

@@ -1,17 +1,16 @@
import iconNodes from '../../../data/iconNodes'; import iconNodes from '../../../data/iconNodes'
const getRandomItem = <Item>(items: Item[]): Item => const getRandomItem = <Item>(items: Item[]): Item => items[Math.floor(Math.random()*items.length)];
items[Math.floor(Math.random() * items.length)];
export default { export default {
async load() { async load() {
const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode })); const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode }))
const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons)); const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons))
return { return {
icons: randomIcons, icons: randomIcons,
iconsCount: icons.length, iconsCount: icons.length,
}; }
}, }
}; }

View File

@@ -28,6 +28,8 @@ function insert() {
const replaceIndex = random(0, 48) const replaceIndex = random(0, 48)
const newIcon = getRandomNewIcon() const newIcon = getRandomNewIcon()
// items.value.splice(replaceIndex, 0, newIcon);
items.value[replaceIndex] = newIcon items.value[replaceIndex] = newIcon
} }
@@ -74,6 +76,7 @@ onBeforeUnmount(() => {
<style scoped> <style scoped>
.card-wrapper { .card-wrapper {
/* padding: 0 24px; */
margin-left: auto; margin-left: auto;
margin-bottom: auto; margin-bottom: auto;
margin-top: 48px; margin-top: 48px;
@@ -84,10 +87,13 @@ onBeforeUnmount(() => {
border-radius: 8px; border-radius: 8px;
width: 100%; width: 100%;
height:100%; height:100%;
/* box-shadow: var(--vp-shadow-2); */
max-height: 220px; max-height: 220px;
max-width: 560px; max-width: 560px;
margin: 0 auto; margin: 0 auto;
position: relative; position: relative;
/* max-height: 240px; */
/* margin-top: 96px; */
} }
.card-grid { .card-grid {
@@ -101,6 +107,7 @@ onBeforeUnmount(() => {
max-width: 512px; max-width: 512px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
/* white-space: nowrap; */
} }
.list-enter-active { .list-enter-active {

View File

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

View File

@@ -47,7 +47,7 @@ export default {
logo: '/framework-logos/flutter.svg', logo: '/framework-logos/flutter.svg',
label: 'Lucide documentation for Flutter', label: 'Lucide documentation for Flutter',
}, },
], ]
}; }
}, }
}; }

View File

@@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import HomeContainer from './HomeContainer.vue' import HomeContainer from './HomeContainer.vue'
import HomeSectionTitle from './HomeSectionTitle.vue'
import { useRouter } from 'vitepress'; import { useRouter } from 'vitepress';
import { data } from './HomePackagesSection.data' import { data } from './HomePackagesSection.data'
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'; import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
@@ -10,7 +9,7 @@ const { go } = useRouter()
<template> <template>
<HomeContainer> <HomeContainer>
<HomeSectionTitle>Available For:</HomeSectionTitle> <h2 class="section-title">Available For:</h2>
<div class="packages-list"> <div class="packages-list">
<a <a
v-for="{ name, logo } in data.packages" v-for="{ name, logo } in data.packages"
@@ -35,6 +34,14 @@ const { go } = useRouter()
</template> </template>
<style scoped> <style scoped>
.section-title {
color: var(--vp-c-text-2);
font-weight: 500;
line-height: 32px;
font-size: 16px;
text-align: center;
margin-bottom: 16px;
}
.packages-list { .packages-list {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

View File

@@ -1,26 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{
headingLevel: 1 | 2 | 3 | 4 | 5 | 6,
}>()
const headingElement = computed(() => `h${props.headingLevel ?? 2}`)
</script>
<template>
<component :is="headingElement" class="section-title">
<slot />
</component>
</template>
<style scoped>
.section-title {
color: var(--vp-c-text-2);
font-weight: 500;
line-height: 32px;
font-size: 16px;
text-align: center;
margin-bottom: 16px;
}
</style>

View File

@@ -1,59 +0,0 @@
<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';
</script>
<template>
<HomeSectionTitle :headingLevel="3"> Sponsor the Lucide maintainers </HomeSectionTitle>
<Card class="sponsor-card">
<img
src="/company-logos/open-collective-light.svg"
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"
/>
<VPButton
href="https://opencollective.com/lucide-icons"
class="sponsor-button"
text="Become a sponsor"
/>
</Card>
</template>
<style scoped>
.sponsor-card {
margin: 0 auto;
max-width: 500px;
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
}
.sponsor-button {
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;
justify-content: space-between;
}
}
</style>

View File

@@ -1,91 +0,0 @@
<script setup lang="ts">
import { useData } from 'vitepress';
import HomeContainer from './HomeContainer.vue'
import GridSection from '../base/GridSection.vue'
import TeamMemberCard, { TeamMember } from './TeamMemberCard.vue'
import teamData from '../../../data/teamData.json'
import HomeSponsorCard from './HomeSponsorCard.vue';
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue'
const { theme } = useData()
</script>
<template>
<HomeContainer>
<GridSection
title="Meet the team"
:headingLevel="2"
class="team-members"
>
<TeamMemberCard
v-for="teamMember in (teamData as TeamMember[])"
v-bind="teamMember"
/>
</GridSection>
<HomeSponsorCard />
<VPDocAsideCarbonAds
:carbon-ads="theme.carbonAds"
class="ad-card"
/>
</HomeContainer>
</template>
<style scoped>
.team-members {
gap: 24px;
margin-top: 48px;
margin-bottom: 48px;
}
@media (min-width: 640px) {
.team-members :deep(.card-grid > *) {
flex-basis: 50%;
}
}
@media (min-width: 768px) {
.team-members :deep(.card-grid > *) {
flex-basis: 33.33%;
}
}
.ad-card {
margin: 32px auto 0;
width: 100%;;
max-width: 500px;
display: flex;
flex-direction: row;
justify-content: space-between;
display: none;
}
@media (min-width: 960px) {
.ad-card {
display: block;
}
}
.ad-card :deep(.VPCarbonAds) {
width: 100%;
text-align: left;
min-height: auto;
}
.ad-card :deep(.VPCarbonAds .carbon-wrap) {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 24px;
}
.ad-card :deep(.VPCarbonAds .carbon-text) {
padding-top: 0;
}
.ad-card :deep(.VPCarbonAds .carbon-poweredby) {
text-align: right;
margin-top: -24px;
}
</style>

View File

@@ -1,92 +0,0 @@
<script lang="ts">
export interface TeamMember {
name: string
title: string
image: string
sponsor?: string
socialLinks: DefaultTheme.SocialLink[]
}
</script>
<script setup lang="ts">
import { DefaultTheme } from 'vitepress';
import Card from '../base/Card.vue'
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
import VPSocialLinks from 'vitepress/dist/client/theme-default/components/VPSocialLinks.vue'
defineProps<TeamMember>()
</script>
<template>
<div>
<Card class="member-card">
<img :src="image" :alt="name" class="member-image"/>
<h3 class="member-name">
{{name}}
</h3>
<p class="member-title">
{{title}}
</p>
<div class="member-links">
<VPButton
v-if="sponsor"
:href="sponsor"
text="Sponsor"
theme="sponsor"
class="sponsor-button"
size="medium"
/>
<VPSocialLinks
:links="socialLinks"
/>
</div>
</Card>
</div>
</template>
<style scoped>
.member-card {
flex-basis: 100%;
height:100%;
}
.member-image {
width: 64px;
height: 64px;
border-radius: 32px;
margin: 0 auto;
background-color: var(--vp-c-bg);
}
.member-name {
text-align: center;
margin-top: 16px;
font-size: 21px;
font-weight: 500;
color: var(--textColor);
}
.member-title {
flex-grow: 1;
padding-top: 8px;
line-height: 24px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-text-2);
text-align: center;
text-wrap: balance;
margin-bottom: 16px;;
}
.sponsor-button {
width: auto;
}
.member-links {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
</style>

View File

@@ -1,93 +0,0 @@
<script setup lang="ts">
import { useData } from 'vitepress';
import { useSessionStorage } from '@vueuse/core';
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)
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)
}"
class="floating-ad"
v-if="theme.carbonAds"
>
<IconButton @click="showAd = false" class="hide-button">
<component :is="CloseIcon" :size="20" absoluteStrokeWidth />
</IconButton>
<VPDocAsideCarbonAds
:carbon-ads="theme.carbonAds"
/>
</div>
</template>
<style scoped>
.floating-ad {
display: none;
position: fixed;
bottom: 32px;
width: 224px;
right: 32px;
transition: opacity 0.5s, transform 0.25s ease;
}
.floating-ad.drawer-open {
transform: translateY(-288px);
}
.floating-ad.hide-ad {
transform: translateX(224px);
opacity: 0;
}
.floating-ad.drawer-open.hide-ad {
transform: translateY(-288px) translateX(224px);
}
.floating-ad.drawer-open, .floating-ad.hide-ad {
transition: opacity 0.25s, transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
}
@media (min-width: 1280px) {
.floating-ad {
display: block;
}
}
@media (min-width: 1440px) {
.floating-ad {
right: calc(((100% - (var(--vp-layout-max-width) - var(--vp-sidebar-width))) - 272px) / 2);
}
}
.hide-button {
padding: 4px;
position: absolute;
top: 8px;
right: 8px;
background-color: transparent;
}
</style>

View File

@@ -1,15 +1,16 @@
import { getAllData } from '../../../lib/icons'; import { getAllData } from '../../../lib/icons';
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories'; import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
import iconsMetaData from '../../../data/iconMetaData'; import iconsMetaData from '../../../data/iconMetaData'
export default { export default {
async load() { async load() {
let categories = getAllCategoryFiles(); let categories = getAllCategoryFiles()
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData)); categories = mapCategoryIconCount(categories, Object.values(iconsMetaData))
return { return {
categories, categories,
}; }
}, }
}; }

View File

@@ -1,16 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue' import { ref, computed } from 'vue'
import { useData } from 'vitepress' import { useData } from 'vitepress'
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue' import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
import { isActive } from 'vitepress/dist/client/shared' import { isActive } from 'vitepress/dist/client/shared'
import { useActiveAnchor } from '../../composables/useActiveAnchor' import { useActiveAnchor } from '../../composables/useActiveAnchor'
import { data } from './CategoryList.data' import { data } from './CategoryList.data'
import CategoryListItem from './CategoryListItem.vue' import CategoryListItem from './CategoryListItem.vue'
import SidebarTitle from './SidebarTitle.vue'
import { useCategoryView } from '../../composables/useCategoryView'
const { page } = useData() const { page } = useData()
const { categoryCounts } = useCategoryView();
const categoriesIsActive = computed(() => { const categoriesIsActive = computed(() => {
return isActive(page.value.relativePath, '/icons/categories'); return isActive(page.value.relativePath, '/icons/categories');
@@ -28,32 +25,22 @@ const headers = computed(() => {
level: 2, level: 2,
link: `${linkPrefix}#${name}`, link: `${linkPrefix}#${name}`,
title, title,
iconCount: categoryCounts.value[name] ?? iconCount, iconCount
name
})) }))
}) })
const container = ref() const container = ref()
const marker = ref() const marker = ref()
const { setActiveLinkDebounced } = useActiveAnchor(container, marker) useActiveAnchor(container, marker)
watch(headers, () => {
setTimeout(() => {
setActiveLinkDebounced()
}, 200)
})
</script> </script>
<template> <template>
<div class="category-list" ref="container"> <div class="category-list" ref="container">
<SidebarTitle> <VPLink class="sidebar-title" href="/icons/" :class="{ 'active': overviewIsActive } ">
View
</SidebarTitle>
<VPLink class="sidebar-link sidebar-text" href="/icons/" :class="{ 'active': overviewIsActive } ">
All All
</VPLink> </VPLink>
<VPLink class="sidebar-link sidebar-text" href="/icons/categories" :class="{ 'active': categoriesIsActive } "> <VPLink class="sidebar-title" href="/icons/categories" :class="{ 'active': categoriesIsActive } ">
Categories Categories
</VPLink> </VPLink>
<div class="content"> <div class="content">
@@ -66,20 +53,17 @@ watch(headers, () => {
</template> </template>
<style scoped> <style scoped>
.sidebar-text { .sidebar-title {
font-weight: 500;
color: var(--vp-c-text-2);
margin-bottom: 6px;
line-height: 24px; line-height: 24px;
font-size: 14px; font-size: 14px;
display: block; display: block;
transition: color 0.25s; transition: color 0.25s;
padding: 4px 0;
} }
.sidebar-link { .sidebar-title:hover, .sidebar-title.active {
font-weight: 500;
color: var(--vp-c-text-2);
}
.sidebar-link:hover, .sidebar-link.active {
color: var(--vp-c-brand); color: var(--vp-c-brand);
} }
.content { .content {

View File

@@ -7,7 +7,6 @@ interface Header {
slug: string; slug: string;
iconCount: number; iconCount: number;
link: string; link: string;
name: string;
children: Header[]; children: Header[];
} }
@@ -15,35 +14,36 @@ type MenuItem = Omit<Header, 'slug' | 'children'> & {
children?: MenuItem[]; children?: MenuItem[];
}; };
defineProps<{ const props = defineProps<{
headers: MenuItem[]; headers: MenuItem[];
root?: boolean; root?: boolean;
}>(); }>();
const { selectedCategory } = useCategoryView(); const { selectedCategory } = useCategoryView();
function onClick(categoryName: string) { function onClick(event: Event) {
selectedCategory.value = categoryName; const target =
(event.target as HTMLElement).nodeName === 'span'
? (event.target as HTMLElement).parentNode
: (event.target as HTMLElement);
const href = (target as HTMLAnchorElement)?.href;
const heading = document.querySelector<HTMLAnchorElement>(categoryName); if (href) {
const id = '#' + href.split('#')[1];
const decodedId = decodeURIComponent(id);
selectedCategory.value = decodedId.replace('#', '');
const heading = document.querySelector<HTMLAnchorElement>(decodedId);
heading?.focus(); heading?.focus();
}
window.history.pushState({}, '', `/icons/categories#${categoryName}`)
} }
</script> </script>
<template> <template>
<ul :class="root ? 'root' : 'nested'"> <ul :class="root ? 'root' : 'nested'">
<li v-for="{ children, link, title, iconCount, name } in headers"> <li v-for="{ children, link, title, iconCount } in headers">
<a <a class="outline-link" :href="link" @click="onClick" :title="title">
class="outline-link"
:href="link"
@click="onClick(name)"
:title="title"
:class="{
inactive: iconCount === 0,
}"
>
<span> <span>
{{ title }} {{ title }}
</span> </span>
@@ -83,10 +83,6 @@ function onClick(categoryName: string) {
transition: color 0.25s; transition: color 0.25s;
} }
.outline-link.inactive {
color: var(--vp-c-text-4);
}
.outline-link.nested { .outline-link.nested {
padding-left: 13px; padding-left: 13px;
} }
@@ -97,8 +93,4 @@ function onClick(categoryName: string) {
font-size: 11px; font-size: 11px;
font-weight: 400; font-weight: 400;
} }
.outline-link.inactive .icon-count {
opacity: 0;
}
</style> </style>

View File

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

View File

@@ -11,32 +11,15 @@ import IconInfo from './IconInfo.vue';
import Badge from '../base/Badge.vue'; import Badge from '../base/Badge.vue';
import { computedAsync } from '@vueuse/core'; import { computedAsync } from '@vueuse/core';
import { satisfies } from 'semver'; import { satisfies } from 'semver';
import { useExternalLibs } from '../../composables/useExternalLibs';
const props = defineProps<{ const props = defineProps<{
iconName: string | null iconName: string
}>() }>()
const { externalIconNodes } = useExternalLibs()
const { go } = useRouter()
const icon = computedAsync<IconEntity | null>(async () => { const icon = computedAsync<IconEntity | null>(async () => {
if (props.iconName) { 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}`)
}
}
}
return null return null
}, null) }, null)
@@ -53,6 +36,8 @@ function onClose() {
emit('close') emit('close')
} }
const { go } = useRouter()
const CloseIcon = createLucideIcon('Close', x) const CloseIcon = createLucideIcon('Close', x)
const Expand = createLucideIcon('Expand', expand) const Expand = createLucideIcon('Expand', expand)
</script> </script>
@@ -66,8 +51,10 @@ const Expand = createLucideIcon('Expand', expand)
v-if="icon.createdRelease" v-if="icon.createdRelease"
class="version" class="version"
:href="releaseTagLink(icon.createdRelease.version)" :href="releaseTagLink(icon.createdRelease.version)"
target="_blank"
rel="noreferrer noopener"
>v{{ icon.createdRelease.version }}</Badge> >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" /> <component :is="Expand" />
</IconButton> </IconButton>
<IconButton @click="onClose"> <IconButton @click="onClose">
@@ -157,11 +144,11 @@ const Expand = createLucideIcon('Expand', expand)
} }
.drawer-enter-active { .drawer-enter-active {
transition: opacity 0.5s, transform 0.25s ease; transition: all 0.2s cubic-bezier(.21,.8,.46,.9);
} }
.drawer-leave-active { .drawer-leave-active {
transition: opacity 0.25s ease, transform 1.6s ease-out; transition: all 0.4s cubic-bezier(1, 0.5, 0.8, 1);
} }
.drawer-enter-from, .drawer-enter-from,

View File

@@ -4,7 +4,7 @@ import IconItem from './IconItem.vue'
const emit = defineEmits(['setActiveIcon']) const emit = defineEmits(['setActiveIcon'])
defineProps<{ const props = defineProps<{
icons: IconEntity[] icons: IconEntity[]
activeIcon?: string activeIcon?: string
overlayMode?: boolean overlayMode?: boolean
@@ -25,10 +25,8 @@ function setActiveIcon(name: string) {
:key="icon.name" :key="icon.name"
> >
<IconItem <IconItem
:iconNode="icon.iconNode" v-bind="icon"
:name="icon.name" @setActiveIcon="setActiveIcon(icon.name)"
:externalLibrary="icon.externalLibrary"
@setActiveIcon="setActiveIcon"
:active="activeIcon === icon.name" :active="activeIcon === icon.name"
customizable customizable
:overlayMode="overlayMode" :overlayMode="overlayMode"
@@ -42,6 +40,7 @@ function setActiveIcon(name: string) {
.icons { .icons {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
/* padding: 32px 32px 96px; */
gap: 8px; gap: 8px;
width: 100%; width: 100%;
} }

View File

@@ -7,8 +7,6 @@ import CopyCodeButton from './CopyCodeButton.vue';
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'; import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
import {useData, useRouter} from 'vitepress'; import {useData, useRouter} from 'vitepress';
import { computed } from 'vue'; import { computed } from 'vue';
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
import { diamond } from '../../../data/iconNodes'
const props = defineProps<{ const props = defineProps<{
icon: IconEntity icon: IconEntity
@@ -22,21 +20,13 @@ const tags = computed(() => {
if (!props.icon || !props?.icon?.tags) return [] if (!props.icon || !props?.icon?.tags) return []
return props.icon.tags.join(' • ') return props.icon.tags.join(' • ')
}) })
const DiamondIcon = createLucideIcon('Diamond', diamond)
</script> </script>
<template> <template>
<div class="icon-info"> <div class="icon-info">
<div class="icon-name-wrapper">
<IconDetailName class="icon-name"> <IconDetailName class="icon-name">
{{ icon.name }} {{ icon.name }}
</IconDetailName> </IconDetailName>
<div v-if="icon.externalLibrary" class="icon-external-lib">
<DiamondIcon fill="currentColor" :size="12"/>
{{ icon.externalLibrary }}
</div>
</div>
<div class="tags-scroller" v-if="tags.length"> <div class="tags-scroller" v-if="tags.length">
<p class="icon-tags horizontal-scroller"> <p class="icon-tags horizontal-scroller">
{{ tags }} {{ tags }}
@@ -54,10 +44,10 @@ const DiamondIcon = createLucideIcon('Diamond', diamond)
<div class="group buttons"> <div class="group buttons">
<VPButton <VPButton
v-if="!page?.relativePath?.startsWith?.(icon.externalLibrary ? `icons/${icon.externalLibrary}/${icon.name}`: `icons/${icon.name}`)" v-if="!page?.relativePath?.startsWith?.(`icons/${icon.name}`)"
:href="icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}`: `/icons/${icon.name}`" :href="`/icons/${icon.name}`"
text="See in action" 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"/> <CopySVGButton :name="icon.name" :popoverPosition="popoverPosition"/>
<CopyCodeButton :name="icon.name" :popoverPosition="popoverPosition"/> <CopyCodeButton :name="icon.name" :popoverPosition="popoverPosition"/>
@@ -77,27 +67,9 @@ const DiamondIcon = createLucideIcon('Diamond', diamond)
text-transform: capitalize; text-transform: capitalize;
} }
.icon-name { .icon-name {
margin-right: -36px;
}
.icon-name-wrapper {
display: flex;
align-items: center;
gap: 2px;
margin-bottom: 4px; 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;
}
.icon-tags { .icon-tags {
font-size: 16px; font-size: 16px;
color: var(--vp-c-text-2); color: var(--vp-c-text-2);

View File

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

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