This commit is contained in:
Eric Fennis
2024-01-05 16:24:30 +01:00
951 changed files with 23088 additions and 12090 deletions
+5
View File
@@ -4,3 +4,8 @@ coverage
lib
tests
node_modules
.eslintrc.js
docs/images
docs/guide/basics/examples
docs/guide/advanced/examples
packages/lucide-react/dynamicIconImports.js
+41 -2
View File
@@ -1,10 +1,13 @@
const DEFAULT_ATTRS = require('./scripts/render/default-attrs.json');
module.exports = {
root: true,
env: {
browser: true,
node: true,
},
extends: ['airbnb-base', 'prettier'],
plugins: ['import', 'prettier'],
plugins: ['import', 'prettier', '@html-eslint'],
rules: {
'no-console': 'off',
'no-param-reassign': 'off',
@@ -15,6 +18,7 @@ module.exports = {
{
singleQuote: true,
trailingComma: 'all',
printWidth: 100
},
],
'import/no-extraneous-dependencies': [
@@ -33,8 +37,43 @@ module.exports = {
},
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./site/tsconfig.json', './packages/*/tsconfig.json'],
project: ['./docs/tsconfig.json', './packages/*/tsconfig.json'],
ecmaVersion: 'latest',
sourceType: 'module',
},
overrides: [
{
files: ['./icons/*.svg'],
parser: '@html-eslint/parser',
rules: {
'prettier/prettier': 'off',
'@html-eslint/require-doctype': 'off',
'@html-eslint/no-duplicate-attrs': 'error',
'@html-eslint/no-inline-styles': 'error',
'@html-eslint/require-attrs': [
'error',
...Object.entries(DEFAULT_ATTRS)
.map(([attr, value]) => ({ tag: 'svg', attr, value: String(value) }))
],
'@html-eslint/indent': ['error', 2],
"@html-eslint/no-multiple-empty-lines": ["error", { "max": 0 }],
'@html-eslint/no-extra-spacing-attrs': [
'error',
{
enforceBeforeSelfClose: true,
},
],
'@html-eslint/require-closing-tags': [
'error',
{
selfClosing: 'always',
allowSelfClosingCustom: true,
},
],
'@html-eslint/element-newline': 'error',
'@html-eslint/no-trailing-spaces': 'error',
'@html-eslint/quotes': 'error',
}
},
],
};
@@ -0,0 +1,49 @@
name: Icon request
description: Suggest a new icon for this project
labels: ['🙌 icon request']
body:
- type: markdown
attributes:
value: |
Before submitting an icon request check if it has already been requested. If there is an open request, please add a 👍.
**Important note**: No new brand logos are allowed, see https://github.com/lucide-icons/lucide/issues/670.
Existing brand icons will also be phased out. For brand icons, consider using https://simpleicons.org, which offers purpose-built SVGs that are also on a 24×24px grid.
- type: input
id: name
attributes:
label: Icon name
description: What should this icon depict? For multiple icons, provide a comma-separated list.
validations:
required: true
- type: textarea
id: use-cases
attributes:
label: Use cases
description: Why do you need this icon? Include at least two real-life use cases per requested icon, avoiding generic descriptions like "it's a car icon".
placeholder: e.g. I need a star icon to use in my rating system.
validations:
required: true
- type: textarea
id: design-ideas
attributes:
label: Design ideas
description: What should this icon look like? Provide simple, minimalistic icon examples from other sets or your own drafts to help us visualize your request.
validations:
required: true
- type: checkboxes
id: checklist
attributes:
label: Checklist
description: Please review the following checklist before submitting your request.
options:
- label: I have searched if someone has submitted a similar issue before and there weren't any. (Please make sure to also search closed issues, as this issue might already have been resolved.)
required: true
- label: I have searched existing icons to make sure it does not already exist and I didn't find any.
required: true
- label: I am not requesting a brand logo and the art is not protected by copyright.
required: true
- label: I am not requesting an icon that includes religious, political imagery or hate symbols.
required: true
- label: I have provided appropriate use cases for the icon(s) requested.
required: true
+90
View File
@@ -0,0 +1,90 @@
name: Bug report
description: Create a report to help us improve
labels: ['🐛 bug']
body:
- type: markdown
attributes:
value: |
Before reporting an issue, please search to see if someone has filed a similar issue before. If there is already an open issue, please add a 👍 and/or leave a comment with additional information.
- type: checkboxes
id: packages
attributes:
label: Package
description: Which Lucide packages are affected? You may select more than one.
options:
- label: lucide
- label: lucide-angular
- label: lucide-flutter
- label: lucide-preact
- label: lucide-react
- label: lucide-react-native
- label: lucide-solid
- label: lucide-svelte
- label: lucide-vue
- label: lucide-vue-next
- label: Figma plugin
- label: source/main
- label: other/not relevant
validations:
required: true
- type: input
id: version
attributes:
label: Version
description: What version of Lucide are you running?
placeholder: e.g. 0.289.1
validations:
required: true
- type: checkboxes
id: browsers
attributes:
label: Browser
description: In which browser(s) are you experiencing the issue? You may select more than one.
options:
- label: Chrome/Chromium
- label: Firefox
- label: Safari
- label: Edge
- label: iOS Safari
- label: Opera
- label: Other/not relevant
validations:
required: false
- type: checkboxes
id: operating-systems
attributes:
label: Operating system
description: In which operating systems a you experiencing the issue? You may select more than one.
options:
- label: Windows
- label: Linux
- label: macOS
- label: Other/not relevant
- type: textarea
id: description
attributes:
label: Description
description: Try to describe in detail the problem you're running into and provide additional context about your working environment if necessary.
placeholder: e.g. When I do X, Y happens instead of Z
validations:
required: true
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to reproduce
description: Please provide a detailed guide on how this issue can be reproduced or a live example with a working reproduction on Codesandbox, JSFiddle or similar.
placeholder: |
1. Import `check` icon
2. Add to a React component/view
3. Run the react app
4. Notice that the `check` isn't rendering correctly which seems a encoding problem
validations:
required: true
- type: checkboxes
id: checklist
attributes:
label: Checklist
description: Please review the following checklist before submitting your issue.
options:
- label: I have searched if someone has submitted a similar issue before and there weren't any. (Please make sure to also search closed issues, as this issue might already have been resolved.)
required: true
@@ -0,0 +1,62 @@
name: lucide.dev bug report
description: Help us improve the Lucide site
labels: ['🐛 bug', '🌍 site']
body:
- type: markdown
attributes:
value: |
Before reporting an issue, please search to see if someone has filed a similar issue before. If there is already an open issue, please add a 👍 and/or leave a comment with additional information.
- type: checkboxes
id: browsers
attributes:
label: Browser
description: In which browser(s) are you experiencing the issue? You may select more than one.
options:
- label: Chrome/Chromium
- label: Firefox
- label: Safari
- label: Edge
- label: iOS Safari
- label: Opera
- label: Other/not relevant
validations:
required: false
- type: checkboxes
id: operating-systems
attributes:
label: Operating system
description: In which operating systems are you experiencing the issue? You may select more than one.
options:
- label: Windows
- label: Linux
- label: macOS
- label: Other/not relevant
- type: textarea
id: description
attributes:
label: Description
description: Try to describe in detail the problem you're running into and provide additional context about your working environment if necessary.
placeholder: e.g. When I do X, Y happens instead of Z
validations:
required: true
- type: textarea
id: stepsToReproduce
attributes:
label: Steps to reproduce
description: Please provide a detailed guide on how this issue can be reproduced.
placeholder: |
1. I click on an icon
2. I click on `Copy SVG` in the drawer
3. I paste from the clipboard
4. A base64 encoded data URI is inserted.
...
validations:
required: true
- type: checkboxes
id: checklist
attributes:
label: Checklist
description: Please review the following checklist before submitting your issue.
options:
- label: I have searched if someone has submitted a similar issue before and there weren't any. (Please make sure to also search closed issues, as this issue might already have been resolved.)
required: true
@@ -0,0 +1,53 @@
name: Feature request
description: Share with us your ideas on how Lucide could be improved upon.
labels: ['💡 idea']
body:
- type: markdown
attributes:
value: |
Before submitting a new feature request, please search to see if someone has filed a similar request before. If there is already an open issue, please add a 👍 and/or leave a comment with additional information.
- type: checkboxes
id: packages
attributes:
label: Package
description: Which Lucide project do you wish this feature were added to? You may select more than one.
options:
- label: lucide
- label: lucide-angular
- label: lucide-flutter
- label: lucide-preact
- label: lucide-react
- label: lucide-react-native
- label: lucide-solid
- label: lucide-svelte
- label: lucide-vue
- label: lucide-vue-next
- label: Figma plugin
- label: all JS packages
- label: site
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: Try to describe in detail the feature you wish existed.
placeholder: e.g. I want to be able to set extra CSS classes on icon components.
validations:
required: true
- type: textarea
id: use-cases
attributes:
label: Use cases
description: Why do you need this feature? Provide real-life use cases as to why this feature will be useful for others.
placeholder: e.g. I could use the extra classes to add animation or global custom styling to some icons.
validations:
required: true
- type: checkboxes
id: checklist
attributes:
label: Checklist
description: Please check the following items before submitting your issue.
options:
- label: I have searched the existing issues to make sure this bug has not already been reported.
required: true
-68
View File
@@ -1,68 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
labels: "🐛 bug"
---
<!--
Before reporting an issue, please search to see if someone has filed a similar issue before. If there is already an open issue, please add a 👍 and/or leave a comment with additional information.
-->
## Prerequisites
* Version:
* Are you running from source/main:
* Are you using a released build:
* Operating system:
* Bits:
## Step to reproduce
*(Type here)*
### Actual behavior
## Any message or error
*(Type here)*
## Resources
* Links
* Screenshots
Here is what a great bug report would look like:
```
## Prerequisites
Version: Release v3.1.0
Running from: Import using webpack
Operating system: Mac OSX
Bits: 64 bits
## Step to reproduce
- Import `check` icon
- Add to a React component/view
- Run the react app
- Notice that the `check` isn't rendering correctly which seems a encoding problem
### Actual behavior:
- Import `check` icon
- Add to a React component/view
- Run the react app
- Check is displayed with correct encoding (e.g UTF-8)
## Any message or error
No console output
...
## Resources
No resources
...
```
-18
View File
@@ -1,18 +0,0 @@
---
name: Icon request
about: Suggest an new icon for this project
labels: "🙌 icon request"
---
<!--
Before creating an icon request, please search to see if someone has requested the icon already. If there is an open request, please add a 👍.
A note about brand logos and related material : We follow the decision from Feather Icons (https://github.com/feathericons/feather/issues/763) to deprecate icons relating to brands.
You will find some in the set, but we won't add any new ones. https://simpleicons.org has 24x24 SVG icons for this purpose.
-->
## Icon Request
* Icon name:
* Use case:
* _Screenshots_ of similar icons:
-15
View File
@@ -1,15 +0,0 @@
---
name: New icon
about: Add a new icon to the library
labels: "🎨 <icon"
---
<!-- Thanks for submitting an icon! Please make sure you read the icon design guide
at https://github.com/lucide-icons/lucide/blob/main/docs/icon-design-guide.md beforehand,
and please fill everything below. -->
- **Name of the icon** : <!-- `icon` -->
- **Tags (alternative names for this icon)** (add them in as a separate json file using the same icon name) :
- **What is the purpose of this icon?** : <!-- Shows that one can click it to... / Is used to denote or label... -->
- **100% scale preview** : <!-- upload an image -->
- **Have you considered alternative possibilities** for its naming or design? :
+3 -3
View File
@@ -9,12 +9,12 @@ inputs:
runs:
using: "composite"
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
- uses: pnpm/action-setup@v2.0.1
- uses: pnpm/action-setup@v2
name: Install pnpm
id: pnpm-install
with:
+3 -3
View File
@@ -9,12 +9,12 @@ inputs:
runs:
using: "composite"
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
- uses: pnpm/action-setup@v2.0.1
- uses: pnpm/action-setup@v2
name: Install pnpm
id: pnpm-install
with:
+48 -18
View File
@@ -1,62 +1,92 @@
# For changed dependencies
📦 dependencies:
- pnpm-lock.yaml
- changed-files:
- any-glob-to-any-file:
- pnpm-lock.yaml
# For changes in documentation
📖 documentation:
- docs/*.md
- docs/**/*.md
- changed-files:
- any-glob-to-any-file:
- docs/*.md
- docs/**/*.md
# For changes in the site, but not markdown files
🌍 site:
- 'docs/**'
- changed-files:
- any-glob-to-any-file:
- 'docs/**'
# For changes in the metadata
🫧 metadata:
- 'icons/*.json'
- categories/*
- changed-files:
- any-glob-to-any-file:
- 'icons/*.json'
- categories/*
# For changes or added icons
🎨 icon:
- 'icons/*.svg'
- changed-files:
- any-glob-to-any-file:
- 'icons/*.svg'
# For changes in the lucide package
🧳 lucide package:
- 'packages/lucide/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide/*'
# For changes in the lucide React package
⚛️ react package:
- 'packages/lucide-react/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-react/*'
# For changes in the lucide React Native package
⚛️ react native package:
- 'packages/lucide-react-native/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-react-native/*'
# For changes in the lucide vue packages
💎 vue package:
- 'packages/lucide-vue/*'
- 'packages/lucide-vue-next/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-vue/*'
- 'packages/lucide-vue-next/*'
# For changes in the lucide angular package
🅰️ angular package:
- 'packages/lucide-angular/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-angular/*'
# For changes in the lucide preact package
⚛️ preact package:
- 'packages/lucide-preact/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-preact/*'
# For changes in the lucide svelte package
🧣 svelte package:
- 'packages/lucide-svelte/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-svelte/*'
# For changes in the lucide solid package
🪝 solid package:
- 'packages/lucide-solid/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-solid/*'
# For changes in the lucide static package
🪨 static package:
- 'packages/lucide-static/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-static/*'
# For changes in the lucide flutter package
🏹 flutter package:
- 'packages/lucide-flutter/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-flutter/*'
+55
View File
@@ -0,0 +1,55 @@
<!-- Thank you for contributing! -->
<!-- Insert `closes #issueNumber` here if merging this PR will resolve an existing issue -->
## What is the purpose of this pull request?
<!-- Please choose one of the following, and put an "x" next to it. -->
- [ ] New Icon
- [ ] Bug fix
- [ ] New Feature
- [ ] Documentation update
- [ ] Other:
### Description
<!-- Please insert your description here and provide info about the "what" this PR is contribution -->
### Icon use case <!-- ONLY for new icons, remove this part if not icon PR -->
<!-- What is the purpose of this icon? For each icon added, please insert at least two real life use cases (the more the better). Text like "it's a car icon" is not accepted. -->
### Alternative icon designs <!-- ONLY for new icons, remove this part if not icon PR -->
<!-- If you have any alternative icon designs, please attach them here. -->
## Icon Design Checklist <!-- ONLY for new icons, remove this part if not icon PR -->
### Concept <!-- ONLY for new icons -->
<!-- All of these requirements must be fulfilled. -->
- [ ] I have provided valid use cases for each icon.
- [ ] I have not added any a brand or logo icon.
- [ ] I have not used any hate symbols.
- [ ] I have not included any religious or political imagery.
### Author, credits & license<!-- ONLY for new icons. -->
<!-- Please choose one of the following, and put an "x" next to it. -->
- [ ] The icons are solely my own creation.
- [ ] The icons were originally created in #<issueNumber> by @<githubUser>
- [ ] I've based them on the following Lucide icons: <!-- provide the list of icons -->
- [ ] I've based them on the following design: <!-- provide source URL and license permitting use -->
### Naming <!-- ONLY for new icons -->
<!-- All of these requirements must be fulfilled. -->
- [ ] I've read and followed the [naming conventions](https://lucide.dev/guide/design/icon-design-guide#naming-conventions)
- [ ] I've named icons by what they are rather than their use case.
- [ ] I've provided meta JSON files in `icons/[iconName].json`.
### Design <!-- ONLY for new icons -->
<!-- All of these requirements must be fulfilled. -->
- [ ] I've read and followed the [icon design guidelines](https://lucide.dev/guide/design/icon-design-guide)
- [ ] 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 are visually centered.
- [ ] I've correctly optimized all icons to two points of precision.
## Before Submitting <!-- For every PR! -->
<!-- All of these requirements must be fulfilled. -->
- [ ] I've read the [Contribution Guidelines](https://github.com/lucide-icons/lucide/blob/main/CONTRIBUTING.md).
- [ ] I've checked if there was an existing PR that solves the same issue.
+12 -27
View File
@@ -15,33 +15,17 @@ jobs:
VERSION: ${{ steps.new-version.outputs.NEW_VERSION }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
node-version: 16
- uses: pnpm/action-setup@v2.0.1
name: Install pnpm
id: pnpm-install
version: 8
- uses: actions/setup-node@v4
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile
- name: Fetch tags
run: git fetch --all --tags
@@ -50,9 +34,6 @@ jobs:
id: latest-tag
run: echo "LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)" >> $GITHUB_OUTPUT
- name: Install dependencies
run: pnpm install
- name: Check if we can patch
run: .github/workflows/version-up.sh --minor
@@ -66,7 +47,10 @@ jobs:
CHANGE_LOG=$(pnpm run generate:changelog --old-tag=${{ steps.latest-tag.outputs.LATEST_TAG }})
CHANGE_LOG=$(tail -n +5 <<< $CHANGE_LOG)
echo $CHANGE_LOG
echo "CHANGE_LOG=$CHANGE_LOG" >> $GITHUB_OUTPUT
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
echo "CHANGE_LOG<<$EOF" >> $GITHUB_OUTPUT
echo "$CHANGE_LOG" >> $GITHUB_OUTPUT
echo "$EOF" >> $GITHUB_OUTPUT
env:
GITHUB_API_KEY: ${{ secrets.GITHUB_TOKEN }}
@@ -86,5 +70,6 @@ jobs:
if: github.repository == 'lucide-icons/lucide'
needs: create-release
uses: './.github/workflows/release.yml'
secrets: inherit
with:
version: ${{ needs.create-release.outputs.VERSION }}
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-pr-message: This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 5 days.
+1 -1
View File
@@ -9,4 +9,4 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4
- uses: actions/labeler@v5
+25
View File
@@ -0,0 +1,25 @@
name: Linting
on:
pull_request:
branches:
- '**'
jobs:
linting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Run Linter
run: pnpm lint
+8 -23
View File
@@ -4,39 +4,24 @@ on:
pull_request:
paths:
- packages/lucide-angular/**
- tools/build-icons/**
- pnpm-lock.yaml
jobs:
lucide-angular:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
node-version: 16
- uses: pnpm/action-setup@v2.0.1
name: Install pnpm
id: pnpm-install
version: 8
- uses: actions/setup-node@v4
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter lucide-angular build
+11 -30
View File
@@ -4,52 +4,33 @@ on:
pull_request:
paths:
- icons/**
- tools/build-font/**
- pnpm-lock.yaml
jobs:
lucide-font:
runs-on: ubuntu-latest
container: ericfennis/lucide-font:latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.4.1
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
node-version: 16
- uses: pnpm/action-setup@v2.0.1
name: Install pnpm
id: pnpm-install
version: 8
- uses: actions/setup-node@v4
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --filter outline-svg
run: pnpm install --frozen-lockfile
- name: Outline svg Icons
run: pnpm build:outline-icons
- name: Create directory
run: mkdir lucide-font
- name: Build font
run: fontcustom compile "./outlined" -h -n "lucide" -o ./lucide-font -F
- name: Create font in ./lucide-font
run: pnpm build:font
- name: "Upload to Artifacts"
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v3
with:
name: lucide-font
path: lucide-font
+9 -23
View File
@@ -4,39 +4,25 @@ on:
pull_request:
paths:
- packages/lucide-preact/**
- tools/build-icons/**
- tools/rollup-plugins/**
- pnpm-lock.yaml
jobs:
lucide-preact:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
node-version: 16
- uses: pnpm/action-setup@v2.0.1
name: Install pnpm
id: pnpm-install
version: 8
- uses: actions/setup-node@v4
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter lucide-preact build
+9 -23
View File
@@ -4,39 +4,25 @@ on:
pull_request:
paths:
- packages/lucide-react-native/**
- tools/build-icons/**
- tools/rollup-plugins/**
- pnpm-lock.yaml
jobs:
lucide-react-native:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
node-version: 16
- uses: pnpm/action-setup@v2.0.1
name: Install pnpm
id: pnpm-install
version: 8
- uses: actions/setup-node@v4
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter lucide-react-native build
+10 -23
View File
@@ -4,39 +4,26 @@ on:
pull_request:
paths:
- packages/lucide-react/**
- tools/build-icons/**
- tools/rollup-plugins/**
- scripts/generateNextJSAliases.mjs
- pnpm-lock.yaml
jobs:
lucide-react:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
node-version: 16
- uses: pnpm/action-setup@v2.0.1
name: Install pnpm
id: pnpm-install
version: 8
- uses: actions/setup-node@v4
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter lucide-react build
+9 -23
View File
@@ -4,39 +4,25 @@ on:
pull_request:
paths:
- packages/lucide-solid/**
- tools/build-icons/**
- tools/rollup-plugins/**
- pnpm-lock.yaml
jobs:
lucide-solid:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
node-version: 16
- uses: pnpm/action-setup@v2.0.1
name: Install pnpm
id: pnpm-install
version: 8
- uses: actions/setup-node@v4
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter lucide-solid build
+8 -23
View File
@@ -4,39 +4,24 @@ on:
pull_request:
paths:
- packages/lucide-static/**
- tools/build-icons/**
- pnpm-lock.yaml
jobs:
lucide-static:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
node-version: 16
- uses: pnpm/action-setup@v2.0.1
name: Install pnpm
id: pnpm-install
version: 8
- uses: actions/setup-node@v4
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter lucide-static build
+9 -23
View File
@@ -4,39 +4,25 @@ on:
pull_request:
paths:
- packages/lucide-svelte/**
- tools/build-icons/**
- tools/rollup-plugins/**
- pnpm-lock.yaml
jobs:
lucide-svelte:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
node-version: 16
- uses: pnpm/action-setup@v2.0.1
name: Install pnpm
id: pnpm-install
version: 8
- uses: actions/setup-node@v4
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter lucide-svelte build
+9 -23
View File
@@ -4,39 +4,25 @@ on:
pull_request:
paths:
- packages/lucide-vue-next/**
- tools/build-icons/**
- tools/rollup-plugins/**
- pnpm-lock.yaml
jobs:
lucide-vue-next:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
node-version: 16
- uses: pnpm/action-setup@v2.0.1
name: Install pnpm
id: pnpm-install
version: 8
- uses: actions/setup-node@v4
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter lucide-vue-next build
+9 -23
View File
@@ -4,39 +4,25 @@ on:
pull_request:
paths:
- packages/lucide-vue/**
- tools/build-icons/**
- tools/rollup-plugins/**
- pnpm-lock.yaml
jobs:
lucide-vue:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
node-version: 16
- uses: pnpm/action-setup@v2.0.1
name: Install pnpm
id: pnpm-install
version: 8
- uses: actions/setup-node@v4
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter lucide-vue build
+9 -23
View File
@@ -4,39 +4,25 @@ on:
pull_request:
paths:
- packages/lucide/**
- tools/build-icons/**
- tools/rollup-plugins/**
- pnpm-lock.yaml
jobs:
lucide:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
node-version: 16
- uses: pnpm/action-setup@v2.0.1
name: Install pnpm
id: pnpm-install
version: 8
- uses: actions/setup-node@v4
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter lucide build
+115 -26
View File
@@ -4,26 +4,95 @@ on:
pull_request_target:
paths:
- 'icons/*.svg'
permissions:
pull-requests: write
contents: write
branches:
- main
jobs:
add-changed-icons-comment:
lint-contributors:
if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: refs/pull/${{ github.event.pull_request.number }}/merge
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v35
uses: tj-actions/changed-files@v41
with:
files: icons/*
- uses: actions/setup-node@v4
- name: Install simple-git (safer and faster than installing all deps)
run: npm install simple-git
- name: Generate annotations
run: node ./scripts/updateContributors.mjs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FETCH_DEPTH: ${{ github.event.pull_request.commits }}
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
- name: Generate annotations
env:
ANNOTATION_SEVERITY: notice
ANNOTATION_TITLE: Contributors have changed!
ANNOTATION_DESCRIPTION: Don't add people who have only performed automatic optimizations.
run: |
git diff --unified=0 -- icons/*.json | # diff icon metadata (unified=0 gives the correct chunk line number)
perl -ne '/^(\+|- |@)/ && print' | # get chunks (lines that start with "+++", "@@", "+ ", "- ")
perl -pe 's/\n/%0A/' | # url encode line breaks (\n -> %0A)
perl -pe 's/%0A(\+\+\+ b\/)/\n\1/g' | # split chunks(one chunk per line)
perl -pe "s/\+\+\+ b\/([^@]*)%0A@@ -(\d+)[^\s]* \+(\d+)[^@]*@@(.*)/::$ANNOTATION_SEVERITY file=\1,line=\2,endLine=\3,title=$ANNOTATION_TITLE::$ANNOTATION_DESCRIPTION%0A\4/"
# Example for the previous substitution
# input: +++ b/icons/accessibility.json%0A@@ -2,0 +3 @@%0A+ "contributors": ["hi"],%0A@@ -13 +14 @@%0A+}%0A
# output: ::$ANNOTATION_SEVERITY file=icons/accessibility.json,line=2,endLine=3,title=$ANNOTATION_TITLE::$ANNOTATION_DESCRIPTION%0A%0A+ "contributors": ["hi"],%0A@@ -13 +14 @@%0A+}%0A
# - name: Fail if contributors have changed
# run: git diff --exit-code -- icons/*.json
generate-changed-icons-comment-data:
if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
generate-1px-stroke-width: ${{ steps.generate-1px-stroke-width.outputs.body }}
generate-2px-stroke-width: ${{ steps.generate-2px-stroke-width.outputs.body }}
generate-3px-stroke-width: ${{ steps.generate-3px-stroke-width.outputs.body }}
generate-24px-dpi-preview: ${{ steps.generate-24px-dpi-preview.outputs.body }}
generate-cohesion-check-random: ${{ steps.generate-cohesion-check-random.outputs.body }}
generate-cohesion-check-squares: ${{ steps.generate-cohesion-check-squares.outputs.body }}
generate-x-rays: ${{ steps.generate-x-rays.outputs.body }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: refs/pull/${{ github.event.pull_request.number }}/merge
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v41
with:
files: icons/*.svg
- name: Generate 24px dpi preview
id: generate-24px-dpi-preview
env:
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
run: |
delimiter="$(openssl rand -hex 8)"
echo "body<<$delimiter" >> $GITHUB_OUTPUT
while IFS= read -r file; do
cat "$file" | # get file content
tr '\n' ' ' | # remove line breaks
sed -e 's/<svg[^>]*>/<svg>/g' | # remove attributes from svg element
base64 -w 0 | # encode svg
sed "s|.*|<img title=\"$file\" alt=\"$file\" src=\"https://lucide.dev/api/gh-icon/dpi/24/&.svg\"/> |"
done <<< "$CHANGED_FILES" | tr '\n' ' ' >> $GITHUB_OUTPUT
echo >> $GITHUB_OUTPUT
echo "$delimiter" >> $GITHUB_OUTPUT
- name: Generate cohesion check random
id: generate-cohesion-check-random
env:
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
run: |
delimiter="$(openssl rand -hex 8)"
echo "body<<$delimiter" >> $GITHUB_OUTPUT
@@ -33,11 +102,13 @@ jobs:
sed -e 's/<svg[^>]*>/<svg>/g' | # remove attributes from svg element
base64 -w 0 | # encode svg
sed "s|.*|<img title=\"$file\" alt=\"$file\" src=\"https://lucide.dev/api/gh-icon/stroke-width/2/&.svg\"/> |"
done | tr '\n' ' ' >> $GITHUB_OUTPUT
done <<< "$CHANGED_FILES" | tr '\n' ' ' >> $GITHUB_OUTPUT
echo >> $GITHUB_OUTPUT
echo "$delimiter" >> $GITHUB_OUTPUT
- name: Generate cohesion check squares
id: generate-cohesion-check-squares
env:
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
run: |
delimiter="$(openssl rand -hex 8)"
echo "body<<$delimiter" >> $GITHUB_OUTPUT
@@ -47,35 +118,39 @@ jobs:
sed -e 's/<svg[^>]*>/<svg>/g' | # remove attributes from svg element
base64 -w 0 | # encode svg
sed "s|.*|<img title=\"$file\" alt=\"$file\" src=\"https://lucide.dev/api/gh-icon/stroke-width/2/&.svg\"/> |"
done | tr '\n' ' ' >> $GITHUB_OUTPUT
done <<< "$CHANGED_FILES" | tr '\n' ' ' >> $GITHUB_OUTPUT
echo >> $GITHUB_OUTPUT
echo "$delimiter" >> $GITHUB_OUTPUT
- name: Generate 1px stroke-width
id: generate-1px-stroke-width
env:
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
run: |
delimiter="$(openssl rand -hex 8)"
echo "body<<$delimiter" >> $GITHUB_OUTPUT
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
while IFS= read -r file; do
cat "$file" | # get file content
tr '\n' ' ' | # remove line breaks
sed -e 's/<svg[^>]*>/<svg>/g' | # remove attributes from svg element
base64 -w 0 | # encode svg
sed "s|.*|<img title=\"$file\" alt=\"$file\" src=\"https://lucide.dev/api/gh-icon/stroke-width/1/&.svg\"/> |"
done | tr '\n' ' ' >> $GITHUB_OUTPUT
done <<< "$CHANGED_FILES" | tr '\n' ' ' >> $GITHUB_OUTPUT
echo >> $GITHUB_OUTPUT
echo "$delimiter" >> $GITHUB_OUTPUT
- name: Generate 2px stroke-width
id: generate-2px-stroke-width
env:
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
run: |
delimiter="$(openssl rand -hex 8)"
echo "body<<$delimiter" >> $GITHUB_OUTPUT
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
while IFS= read -r file; do
cat "$file" | # get file content
tr '\n' ' ' | # remove line breaks
sed -e 's/<svg[^>]*>/<svg>/g' | # remove attributes from svg element
base64 -w 0 | # encode svg
sed "s|.*|<img title=\"$file\" alt=\"$file\" src=\"https://lucide.dev/api/gh-icon/stroke-width/2/&.svg\"/> |"
done | tr '\n' ' ' >> $GITHUB_OUTPUT
done <<< "$CHANGED_FILES" | tr '\n' ' ' >> $GITHUB_OUTPUT
echo >> $GITHUB_OUTPUT
echo "$delimiter" >> $GITHUB_OUTPUT
- name: Generate 3px stroke-width
@@ -83,29 +158,39 @@ jobs:
run: |
delimiter="$(openssl rand -hex 8)"
echo "body<<$delimiter" >> $GITHUB_OUTPUT
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
while IFS= read -r file; do
cat "$file" | # get file content
tr '\n' ' ' | # remove line breaks
sed -e 's/<svg[^>]*>/<svg>/g' | # remove attributes from svg element
base64 -w 0 | # encode svg
sed "s|.*|<img title=\"$file\" alt=\"$file\" src=\"https://lucide.dev/api/gh-icon/stroke-width/3/&.svg\"/> |"
done | tr '\n' ' ' >> $GITHUB_OUTPUT
done <<< "$CHANGED_FILES" | tr '\n' ' ' >> $GITHUB_OUTPUT
echo >> $GITHUB_OUTPUT
echo "$delimiter" >> $GITHUB_OUTPUT
- name: Generate X-rays
id: generate-x-rays
env:
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
run: |
delimiter="$(openssl rand -hex 8)"
echo "body<<$delimiter" >> $GITHUB_OUTPUT
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
while IFS= read -r file; do
cat "$file" | # get file content
tr '\n' ' ' | # remove line breaks
sed -e 's/<svg[^>]*>/<svg>/g' | # remove attributes from svg element
base64 -w 0 | # encode svg
sed "s|.*|<img width=\"400\" title=\"$file\" alt=\"$file\" src=\"https://lucide.dev/api/gh-icon/$(basename ${file//\.svg/})/&.svg\"/> |"
done | tr '\n' ' ' >> $GITHUB_OUTPUT
done <<< "$CHANGED_FILES" | tr '\n' ' ' >> $GITHUB_OUTPUT
echo >> $GITHUB_OUTPUT
echo "$delimiter" >> $GITHUB_OUTPUT
add-changed-icons-comment:
if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest
permissions:
pull-requests: write
needs: [generate-changed-icons-comment-data]
steps:
- name: Find Comment
uses: peter-evans/find-comment@v2
id: fc
@@ -114,27 +199,31 @@ jobs:
comment-author: 'github-actions[bot]'
body-includes: Added or changed icons
- name: Create or update comment
uses: peter-evans/create-or-update-comment@v2
uses: peter-evans/create-or-update-comment@v3
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
### Added or changed icons
${{ steps.generate-2px-stroke-width.outputs.body }}<br/>
${{ needs.generate-changed-icons-comment-data.outputs.generate-2px-stroke-width }}<br/>
<details>
<summary>Preview cohesion</summary>
${{ steps.generate-cohesion-check-squares.outputs.body }}<br/>
${{ steps.generate-2px-stroke-width.outputs.body }}<br/>
${{ steps.generate-cohesion-check-random.outputs.body }}<br/>
${{ needs.generate-changed-icons-comment-data.outputs.generate-cohesion-check-squares }}<br/>
${{ needs.generate-changed-icons-comment-data.outputs.generate-2px-stroke-width }}<br/>
${{ needs.generate-changed-icons-comment-data.outputs.generate-cohesion-check-random }}<br/>
</details>
<details>
<summary>Preview stroke widths</summary>
${{ steps.generate-1px-stroke-width.outputs.body }}<br/>
${{ steps.generate-2px-stroke-width.outputs.body }}<br/>
${{ steps.generate-3px-stroke-width.outputs.body }}<br/>
${{ needs.generate-changed-icons-comment-data.outputs.generate-1px-stroke-width }}<br/>
${{ needs.generate-changed-icons-comment-data.outputs.generate-2px-stroke-width }}<br/>
${{ needs.generate-changed-icons-comment-data.outputs.generate-3px-stroke-width }}<br/>
</details>
<details>
<summary>DPI Preview (24px)</summary>
${{ needs.generate-changed-icons-comment-data.outputs.generate-24px-dpi-preview }}
</details>
<details>
<summary>Icon X-rays</summary>
${{ steps.generate-x-rays.outputs.body }}
${{ needs.generate-changed-icons-comment-data.outputs.generate-x-rays }}
</details>
edit-mode: replace
+28 -79
View File
@@ -9,6 +9,7 @@ on:
inputs:
version:
required: true
description: Version
type: string
workflow_dispatch:
@@ -38,6 +39,7 @@ jobs:
runs-on: ubuntu-latest
needs: pre-release
strategy:
fail-fast: false
matrix:
package: [
'lucide',
@@ -51,36 +53,20 @@ jobs:
'lucide-svelte',
]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 18
- uses: pnpm/action-setup@v2.0.1
name: Install pnpm
id: pnpm-install
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Set Auth Token
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
run: npm config set //registry.npmjs.org/:_authToken ${{ inputs.NPM_TOKEN || secrets.NPM_TOKEN }}
- name: Set new version
run: pnpm --filter ${{ matrix.package }} version --new-version ${{ needs.pre-release.outputs.VERSION }} --no-git-tag-version
@@ -99,32 +85,15 @@ jobs:
runs-on: ubuntu-latest
needs: [pre-release, lucide-font]
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v2
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/download-artifact@v3
- uses: pnpm/action-setup@v2
with:
node-version: 16
- uses: pnpm/action-setup@v2.0.1
name: Install pnpm
id: pnpm-install
version: 8
- uses: actions/setup-node@v4
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -148,47 +117,27 @@ jobs:
if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest
needs: pre-release
container: ericfennis/lucide-font:latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.4.1
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
node-version: 16
- uses: pnpm/action-setup@v2.0.1
name: Install pnpm
id: pnpm-install
version: 8
- uses: actions/setup-node@v4
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --filter outline-svg
run: pnpm install --frozen-lockfile
- name: Outline svg Icons
run: pnpm build:outline-icons
- name: Create directory
run: mkdir lucide-font
- name: Build font
run: fontcustom compile "./outlined" -h -n "lucide" -o ./lucide-font -F
- name: Create font in ./lucide-font
run: pnpm build:font
- name: "Upload to Artifacts"
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v3
with:
name: lucide-font
path: lucide-font
@@ -202,8 +151,8 @@ jobs:
]
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v2
- uses: actions/checkout@v4
- uses: actions/download-artifact@v3
- name: Zip font and icons
run: |
zip -r lucide-font-${{ needs.pre-release.outputs.VERSION }}.zip lucide-font
+3
View File
@@ -18,6 +18,9 @@ packages/**/src/icons/*.ts
packages/**/src/icons/*.tsx
packages/**/src/aliases.ts
packages/**/src/dynamicIconImports.ts
packages/**/dynamicIconImports.js
packages/**/dynamicIconImports.d.ts
packages/**/dynamicIconImports.js.map
packages/**/LICENSE
categories.json
tags.json
-1
View File
@@ -4,7 +4,6 @@ stats
node_modules
tests
scripts
site
src
build
babel.config.js
+7 -1
View File
@@ -3,5 +3,11 @@
"devs",
"preact",
"Preact"
]
],
"eslint.enable": true,
"eslint.validate": [
"javascript",
"svg"
],
"svg.preview.background": "transparent"
}
+7 -8
View File
@@ -33,7 +33,7 @@ Here you can find instructions on how to implement the guidelines with different
#### [Adobe Illustrator Guide](https://lucide.dev/docs/illustrator-guide)
You can also [download an Adobe Illustrator template](https://lucide.dev/templates/illustrator-template.ai).
You can also [download an Adobe Illustrator template](https://github.com/lucide-icons/lucide/blob/main/docs/public/templates/illustrator_template.ai).
#### [Inkscape Guide](https://lucide.dev/docs/inkscape-guide)
@@ -144,16 +144,19 @@ Root directories
```sh
lucide
|
├── docs
│ ├── guide
├── icons
├── packages
├── scripts
└── site
└── scripts
```
### Docs
The lucide.dev website is using [vitepress](https://vitepress.dev/) to generate the static website. The markdown files are located in the docs directory.
### Guides
Detailed documentation about: installation, guides, packages, design guides etc.
### Icons
@@ -170,10 +173,6 @@ Includes all the (npm) packages of lucide.
Includes usefully scripts to automate certain jobs. Big part of the scripts is the template generation, for example it generates icon components for all the packages. These scripts are usually executed from the "scripts" section in the package.json.
### site
The lucide.dev website is using [vitepress](https://vitepress.dev/) to generate the static website. The markdown files are located in the docs directory.
## Documentation
The documentation files are located in the [docs](https://github.com/lucide-icons/lucide/tree/main/docs) directory. All these markdown files will be loaded in the build of the lucide.dev website.
+6 -14
View File
@@ -11,14 +11,14 @@
Community-run fork of [Feather Icons](https://github.com/feathericons/feather), open for anyone to contribute icons.
It began after growing disaffection 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.
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.
Lucide is trying to expand the icon set as much as possible while staying faithful to the original simplistic design language. We do this as a community of devs and designers and hope that you'll join us!
### Why choose Lucide over Feather Icons
- More icons to work with: Lucide already has hundreds of icons more than Feather does.
- Official librairies and integrations with popular frameworks and design tools.
- Official libraries and integrations with popular frameworks and design tools.
- Well maintained code base.
- Active community, regularly growing and improving the set.
@@ -35,7 +35,6 @@ Lucide is trying to expand the icon set as much as possible while staying faithf
- [Static (svg sprite, font, icons ..)](#static-svg-sprite-font-icons-)
- [Figma](#figma)
- [Laravel](#laravel)
- [Flutter](#flutter)
- [Svelte](#svelte)
- [Solid](#solid)
- [Hyva](#hyva)
@@ -200,16 +199,6 @@ composer require mallardduck/blade-lucide-icons
For more details, see the [documentation](https://github.com/mallardduck/blade-lucide-icons/blob/main/README.md).
### Flutter
Implementation of Lucide icon library for Flutter applications.
```sh
flutter pub add lucide_icons
```
For more details, see the [pub.dev](https://pub.dev/packages/lucide_icons).
### Svelte
Implementation of the lucide icon library for Svelte applications.
@@ -285,6 +274,9 @@ Thank you to all the people who contributed to Lucide!
## Sponsors
<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 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>
+1 -1
View File
@@ -1,5 +1,5 @@
{
"$schema": "../category.schema.json",
"title": "File icons",
"icon": "layout"
"icon": "panels-top-left"
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"$schema": "../category.schema.json",
"title": "Layout",
"icon": "layout"
"icon": "panels-top-left"
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"$schema": "../category.schema.json",
"title": "Transportation",
"icon": "train"
"icon": "train-front"
}
+6 -5
View File
@@ -12,8 +12,10 @@
},
"title": "Lucide Icons category schema",
"type": "object",
"additionalProperties": false,
"required": ["$schema", "icon", "title"],
"properties": {
"title": {
"$schema": {
"type": "string"
},
"description": {
@@ -22,13 +24,12 @@
"icon": {
"type": "string"
},
"title": {
"type": "string"
},
"weight": {
"type": "integer"
}
},
"required": [
"title",
"icon"
],
"description": "A JSON Schema for categories defined by Lucide Icons."
}
+34 -22
View File
@@ -1,41 +1,53 @@
import { eventHandler, setResponseHeader, defaultContentType } from 'h3'
import { renderToString, renderToStaticMarkup } from 'react-dom/server'
import { createElement } from 'react'
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
import { renderToString, renderToStaticMarkup } from 'react-dom/server';
import { createElement } from 'react';
import SvgPreview from '../../lib/SvgPreview/index.tsx';
import iconNodes from '../../data/iconNodes'
import createLucideIcon from 'lucide-react/src/createLucideIcon'
import iconNodes from '../../data/iconNodes';
import createLucideIcon from 'lucide-react/src/createLucideIcon';
import Backdrop from '../../lib/SvgPreview/Backdrop.tsx';
export default eventHandler((event) => {
const { params } = event.context
const { params } = event.context;
const [name, svgData] = params.data.split('/');
const data = svgData.slice(0, -4);
const pathData = params.data.split('/');
const data = pathData.at(-1).slice(0, -4);
const [name] = pathData;
const src = Buffer.from(data, 'base64').toString('utf8');
const children = []
const children = [];
if (name in iconNodes) {
const iconNode = iconNodes[name]
// Finds the longest matching icon to be use as the backdrop.
// For `square-dashed-bottom-code` it suggests `square-dashed-bottom-code`.
// For `square-dashed-bottom-i-dont-exist` it suggests `square-dashed-bottom`.
const backdropName = name
.split('-')
.map((_, idx, arr) => arr.slice(0, idx + 1).join('-'))
.reverse()
.find((groupName) => groupName in iconNodes);
if (backdropName) {
const iconNode = iconNodes[backdropName];
const LucideIcon = createLucideIcon(name, iconNode)
const svg = renderToStaticMarkup(createElement(LucideIcon))
const LucideIcon = createLucideIcon(backdropName, iconNode);
const svg = renderToStaticMarkup(createElement(LucideIcon));
const backdropString = svg.replace(/<svg[^>]*>|<\/svg>/g, '');
children.push(createElement(Backdrop, { backdropString, src }))
children.push(
createElement(Backdrop, {
backdropString,
src,
color: name in iconNodes ? 'red' : '#777',
})
);
}
const svg = Buffer.from(
// We can't use jsx here, is not supported here by nitro.
renderToString(createElement(SvgPreview, {src, showGrid: true}, children)).replace(
/>/,
'><style>@media screen and (prefers-color-scheme: dark) { svg { stroke: #fff } }</style>'
)
renderToString(createElement(SvgPreview, { src, showGrid: true }, children))
).toString('utf8');
defaultContentType(event, 'image/svg+xml')
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000')
defaultContentType(event, 'image/svg+xml');
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
return svg
})
return svg;
});
@@ -0,0 +1,73 @@
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
import { Resvg, initWasm } from '@resvg/resvg-wasm';
import wasm from './loadWasm';
var initializedResvg = initWasm(wasm);
export default eventHandler(async (event) => {
const { params = {} } = event.context;
await initializedResvg;
const imageSize = 96;
const [iconSizeString, svgData] = params.data.split('/');
const iconSize = parseInt(iconSizeString, 10);
const data = svgData.slice(0, -4);
const src = Buffer.from(data, 'base64').toString('utf8');
const svg = (src.includes('<svg') ? src : `<svg>${src}</svg>`)
.replace(/(\r\n|\n|\r)/gm, '')
.replace(
/<svg[^>]*/,
`<svg
xmlns="http://www.w3.org/2000/svg"
width="${iconSize}"
height="${iconSize}"
viewBox="0 0 24 24"
fill="none"
stroke="#fff"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
`
);
const resvg = new Resvg(svg, { background: '#000' });
const pngData = resvg.render();
const pngBuffer = Buffer.from(pngData.asPng());
defaultContentType(event, 'image/svg+xml');
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
return `
<svg xmlns="http://www.w3.org/2000/svg" width="${imageSize}" height="${imageSize}" viewBox="0 0 ${imageSize} ${imageSize}">
<style>
@media screen and (prefers-color-scheme: light) {
#fallback-background { fill: transparent; }
}
@media screen and (prefers-color-scheme: dark) {
#fallback-background { fill: transparent; }
rect { fill: #fff; }
}
</style>
<mask id="mask">
<image
width="${imageSize}"
height="${imageSize}"
href="data:image/png;base64,${pngBuffer.toString('base64')}"
image-rendering="pixelated"
/>
</mask>
<rect
id="fallback-background"
width="${imageSize}"
height="${imageSize}" ry="${imageSize / 24}"
fill="#fff"
/>
<rect
width="${imageSize}"
height="${imageSize}"
fill="#000"
mask="url(#mask)"
/>
</svg>`;
});
@@ -0,0 +1,15 @@
import fs from 'fs';
import module from 'node:module';
/* WASM_IMPORT */
let wasm;
if (process.env.NODE_ENV === 'development') {
const require = module.createRequire(import.meta.url);
wasm = fs.readFileSync(require.resolve('@resvg/resvg-wasm/index_bg.wasm'));
} else {
wasm = resvg_wasm;
}
export default wasm;
@@ -22,10 +22,19 @@ export default eventHandler((event) => {
const svg = Buffer.from(
// We can't use jsx here, is not supported here by nitro.
renderToString(createElement(Icon, { strokeWidth })).replace(
/>/,
'><style>@media screen and (prefers-color-scheme: dark) { svg { stroke: #fff } }</style>'
)
renderToString(createElement(Icon, { strokeWidth }))
.replace(/fill\="none"/, 'fill="#fff"')
.replace(
/>/,
`><style>
@media screen and (prefers-color-scheme: light) {
svg { fill: transparent !important; }
}
@media screen and (prefers-color-scheme: dark) {
svg { stroke: #fff; fill: transparent !important; }
}
</style>`
)
).toString('utf8');
defaultContentType(event, 'image/svg+xml')
+9 -36
View File
@@ -1,14 +1,6 @@
import { fileURLToPath, URL } from 'node:url'
import path from 'path';
import { defineConfig } from 'vitepress'
import { createWriteStream } from 'node:fs'
import { resolve } from 'node:path'
import { SitemapStream } from 'sitemap'
import sidebar from './sidebar';
import fs from 'fs';
const links = []
const title = "Lucide";
const socialTitle = "Lucide Icons";
@@ -20,6 +12,7 @@ export default defineConfig({
description,
cleanUrls: true,
outDir: '.vercel/output/static',
srcExclude: ['**/README.md'],
vite: {
resolve: {
alias: [
@@ -40,7 +33,7 @@ export default defineConfig({
},
head: [
[ 'script', {
src: 'https://plausible.io/js/script.js',
src: 'https://analytics.lucide.dev/js/script.js',
'data-domain': 'lucide.dev',
defer: ''
}],
@@ -111,6 +104,7 @@ export default defineConfig({
{ text: 'Icons', link: '/icons/' },
{ text: 'Guide', link: '/guide/' },
{ text: 'Packages', link: '/packages' },
{ text: 'Showcase', link: '/showcase' },
{ text: 'License', link: '/license' },
],
sidebar,
@@ -125,33 +119,12 @@ export default defineConfig({
editLink: {
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path'
},
},
transformHtml: (_, id, { pageData }) => {
if (/[\\/]404\.html$/.test(id)) {
return
carbonAds: {
code: 'CWYIC53U',
placement: 'lucidedev'
}
if (pageData.relativePath.startsWith('icons/')) {
links.push({
url: pageData.relativePath.replace(/((^|\/)index)?\.md$/, '$2'),
lastmod: pageData?.params?.changedRelease?.date
})
return
}
links.push({
url: pageData.relativePath.replace(/((^|\/)index)?\.md$/, '$2'),
lastmod: pageData.lastUpdated
})
},
buildEnd: async ({ outDir }) => {
const sitemap = new SitemapStream({
hostname: 'https://lucide.dev/'
})
const writeStream = createWriteStream(resolve(outDir, 'sitemap.xml'))
sitemap.pipe(writeStream)
links.forEach((link) => sitemap.write(link))
sitemap.end()
await new Promise((r) => writeStream.on('finish', r))
},
sitemap: {
hostname: 'https://lucide.dev/'
}
})
+50
View File
@@ -0,0 +1,50 @@
[
{
"name": "Vercel",
"url": "https://vercel.com",
"image": {
"light": "/company-logos/vercel-light.svg",
"dark": "/company-logos/vercel-dark.svg"
}
},
{
"name": "Supabase",
"url": "https://supabase.com",
"image": {
"light": "/company-logos/supabase-light.svg",
"dark": "/company-logos/supabase-dark.svg"
}
},
{
"name": "Obsidian",
"url": "https://obsidian.md",
"image": {
"light": "/company-logos/obsidian-light.svg",
"dark": "/company-logos/obsidian-dark.svg"
}
},
{
"name": "Open Collective",
"url": "https://opencollective.com",
"image": {
"light": "/company-logos/open-collective-light.svg",
"dark": "/company-logos/open-collective-dark.svg"
}
},
{
"name": "Super",
"url": "https://super.so",
"image": {
"light": "/company-logos/super-light.svg",
"dark": "/company-logos/super-dark.svg"
}
},
{
"name": "Noodle",
"url": "https://noodle.run/",
"image": {
"light": "/company-logos/noodle-light.svg",
"dark": "/company-logos/noodle-dark.svg"
}
}
]
@@ -0,0 +1,18 @@
[
{
"name": "Shadcn/ui",
"url": "https://ui.shadcn.com/",
"image": {
"light": "/library-logos/shadcn-ui-light.svg",
"dark": "/library-logos/shadcn-ui-dark.svg"
}
},
{
"name": "Tamagui",
"url": "https://tamagui.dev/",
"image": {
"light": "/library-logos/tamagui.svg",
"dark": "/library-logos/tamagui.svg"
}
}
]
+11 -23
View File
@@ -1,11 +1,12 @@
import React from 'react';
interface BackdropProps {
src: string
backdropString: string
src: string;
color?: string;
backdropString: string;
}
const Backdrop = ({ src, backdropString }: BackdropProps): JSX.Element => {
const Backdrop = ({ src, color = 'red', backdropString }: BackdropProps): JSX.Element => {
return (
<>
<defs xmlns="http://www.w3.org/2000/svg">
@@ -16,8 +17,8 @@ const Backdrop = ({ src, backdropString }: BackdropProps): JSX.Element => {
patternUnits="userSpaceOnUse"
patternTransform="rotate(45 50 50)"
>
<line stroke="red" strokeWidth={0.1} y2={1} />
<line stroke="red" strokeWidth={0.1} y2={1} />
<line stroke={color} strokeWidth={0.1} y2={1} />
<line stroke={color} strokeWidth={0.1} y2={1} />
</pattern>
</defs>
<mask id="svg-preview-backdrop-mask-outline" maskUnits="userSpaceOnUse">
@@ -29,20 +30,8 @@ const Backdrop = ({ src, backdropString }: BackdropProps): JSX.Element => {
<g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} />
<g strokeWidth={1.75} dangerouslySetInnerHTML={{ __html: backdropString }} />
</mask>
<g
strokeWidth={2.25}
stroke="url(#pattern)"
mask={'url(#svg-preview-backdrop-mask-outline)'}
>
<rect
x="0"
y="0"
width="24"
height="24"
fill="url(#pattern)"
opacity={0.5}
stroke="none"
/>
<g strokeWidth={2.25} stroke="url(#pattern)" mask={'url(#svg-preview-backdrop-mask-outline)'}>
<rect x="0" y="0" width="24" height="24" fill="url(#pattern)" opacity={0.5} stroke="none" />
</g>
<rect
x="0"
@@ -58,14 +47,13 @@ const Backdrop = ({ src, backdropString }: BackdropProps): JSX.Element => {
y="0"
width="24"
height="24"
fill="red"
fill={color}
opacity={0.5}
stroke="none"
mask={'url(#svg-preview-backdrop-mask-fill)'}
/>
</>
)
}
);
};
export default Backdrop;
+66 -14
View File
@@ -4,7 +4,7 @@ import { getPaths, assert } from './utils';
const Grid = ({
radius,
fill,
fill = '#fff',
...props
}: {
strokeWidth: number;
@@ -12,6 +12,7 @@ const Grid = ({
} & PathProps<'stroke', 'strokeWidth'>) => (
<g className="svg-preview-grid-group" strokeLinecap="butt" {...props}>
<rect
className="svg-preview-grid-rect"
width={24 - props.strokeWidth}
height={24 - props.strokeWidth}
x={props.strokeWidth / 2}
@@ -182,18 +183,62 @@ const Radii = ({
>) => {
return (
<g className="svg-preview-radii-group" {...props}>
{paths
.filter(({ circle }) => circle)
.map(({ c, prev, next, circle: { x, y, r } }) =>
c.name === 'circle' ? (
<path d={`M${x} ${y}h.01`} />
) : (
<>
<path d={`M${prev.x} ${prev.y} ${x} ${y} ${next.x} ${next.y}`} />
<circle cy={y} cx={x} r={r} />
</>
)
)}
{paths.map(
({ c, prev, next, circle }, i) =>
circle && (
<React.Fragment key={i}>
{c.name !== "circle" && (
<path
d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`}
/>
)}
<circle
cy={circle.y}
cx={circle.x}
r={0.25}
strokeDasharray="0"
stroke={
(Math.round(circle.x * 100) / 100) % 1 !== 0 ||
(Math.round(circle.y * 100) / 100) % 1 !== 0
? "red"
: undefined
}
/>
<circle
cy={circle.y}
cx={circle.x}
r={circle.r}
stroke={
(Math.round(circle.r * 1000) / 1000) % 1 !== 0
? "red"
: undefined
}
/>
</React.Fragment>
),
)}
</g>
);
};
const Handles = ({
paths,
...props
}: { paths: Path[] } & PathProps<
'strokeWidth' | 'stroke' | 'strokeDasharray' | 'strokeOpacity',
any
>) => {
console.log(paths);
return (
<g className="svg-preview-handles-group" {...props}>
{paths.map(({ c, prev, next, cp1, cp2 }) => (
<>
{cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />}
{cp1 && <circle cy={cp1.y} cx={cp1.x} r={0.25} />}
{cp2 && <path d={`M${next.x} ${next.y} ${cp2.x} ${cp2.y}`} />}
{cp2 && <circle cy={cp2.y} cx={cp2.x} r={0.25} />}
</>
))}
</g>
);
};
@@ -207,7 +252,12 @@ const SvgPreview = React.forwardRef<
>(({ src, children, showGrid = false, ...props }, ref) => {
const paths = typeof src === 'string' ? getPaths(src) : src;
const darkModeCss = `@media screen and (prefers-color-scheme: dark) {
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,
@@ -232,6 +282,7 @@ const SvgPreview = React.forwardRef<
<style>{darkModeCss}</style>
{showGrid && <Grid strokeWidth={0.1} 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
paths={paths}
colors={[
@@ -257,6 +308,7 @@ const SvgPreview = React.forwardRef<
strokeOpacity={0.3}
/>
<ControlPath radius={1} paths={paths} pointSize={1} stroke="#fff" strokeWidth={0.125} />
<Handles paths={paths} strokeWidth={0.12} stroke="#FFF" strokeOpacity={0.3} />
{children}
</svg>
);
+3 -1
View File
@@ -8,7 +8,9 @@ export type Path = {
prev: Point;
next: Point;
isStart: boolean;
circle: { x: number; y: number; r: number };
circle?: { x: number; y: number; r: number };
cp1?: Point;
cp2?: Point;
c: ReturnType<typeof getCommands>[number];
};
+21 -7
View File
@@ -63,7 +63,7 @@ export const getPaths = (src: string) => {
c: typeof commands[number],
next: Point,
d?: string,
circle?: Path['circle']
extras?: { circle?: Path['circle']; cp1?: Path['cp1']; cp2?: Path['cp2'] }
) => {
assert(prev);
paths.push({
@@ -71,7 +71,7 @@ export const getPaths = (src: string) => {
d: d || `M ${prev.x} ${prev.y} L ${next.x} ${next.y}`,
prev,
next,
circle,
...extras,
isStart: start === prev,
});
prev = next;
@@ -110,7 +110,10 @@ export const getPaths = (src: string) => {
}
case SVGPathData.CURVE_TO: {
assert(prev);
addPath(c, c, `M ${prev.x} ${prev.y} ${encodeSVGPath(c)}`);
addPath(c, c, `M ${prev.x} ${prev.y} ${encodeSVGPath(c)}`, {
cp1: { x: c.x1, y: c.y1 },
cp2: { x: c.x2, y: c.y2 },
});
break;
}
case SVGPathData.SMOOTH_CURVE_TO: {
@@ -146,13 +149,20 @@ export const getPaths = (src: string) => {
y1: prev.y - reflectedCp1.y,
x2: c.x2,
y2: c.y2,
})}`
})}`,
{
cp1: { x: prev.x - reflectedCp1.x, y: prev.y - reflectedCp1.y },
cp2: { x: c.x2, y: c.y2 },
}
);
break;
}
case SVGPathData.QUAD_TO: {
assert(prev);
addPath(c, c, `M ${prev.x} ${prev.y} ${encodeSVGPath(c)}`);
addPath(c, c, `M ${prev.x} ${prev.y} ${encodeSVGPath(c)}`, {
cp1: { x: c.x1, y: c.y1 },
cp2: { x: c.x1, y: c.y1 },
});
break;
}
case SVGPathData.SMOOTH_QUAD_TO: {
@@ -197,7 +207,11 @@ export const getPaths = (src: string) => {
y: c.y,
x1: prevCP.x,
y1: prevCP.y,
})}`
})}`,
{
cp1: { x: prevCP.x, y: prevCP.y },
cp2: { x: prevCP.x, y: prevCP.y },
}
);
break;
}
@@ -218,7 +232,7 @@ export const getPaths = (src: string) => {
c,
c,
`M ${prev.x} ${prev.y} A${c.rX} ${c.rY} ${c.xRot} ${c.lArcFlag} ${c.sweepFlag} ${c.x} ${c.y}`,
c.rX === c.rY ? { ...center, r: c.rX } : undefined
{ circle: c.rX === c.rY ? { ...center, r: c.rX } : undefined }
);
break;
}
+39 -98
View File
@@ -1,41 +1,29 @@
import {
BUNDLED_LANGUAGES,
type IThemeRegistration
} from 'shiki'
bundledLanguages,
type ThemeRegistration
} from 'shikiji'
import {
getHighlighter,
} from 'shiki-processor'
} from 'shikiji'
type CodeExampleType = {
title: string,
lang: string,
codes: {
language?: string,
code: string,
metastring?: string,
}[],
language: string,
code: string,
}[]
const getIconCodes = (): CodeExampleType => {
return [
{
lang: 'html',
language: 'html',
title: 'HTML',
codes: [
{
language: 'html',
code: `<i data-lucide-name="Name"></i>
`,
},
],
code: `<i data-lucide="Name"></i>`
},
{
lang: 'tsx',
language: 'tsx',
title: 'React',
codes: [
{
language: 'tsx',
code: `import { PascalCase } from 'lucide-react';
code: `import { PascalCase } from 'lucide-react';
const App = () => {
return (
@@ -45,17 +33,11 @@ const App = () => {
export default App;
`,
},
],
},
{
lang: 'vue',
title: 'Vue 3',
codes: [
{
language: 'vue',
code: `<script setup>
language: 'vue',
title: 'Vue',
code: `<script setup>
import { PascalCase } from 'lucide-vue-next';
</script>
@@ -63,33 +45,21 @@ export default App;
<PascalCase />
</template>
`,
},
],
},
{
lang: 'svelte',
language: 'svelte',
title: 'Svelte',
codes: [
{
language: 'svelte',
code: `<script>
code: `<script>
import { PascalCase } from 'lucide-svelte';
</script>
<PascalCase />
`,
},
],
},
{
lang: 'preact',
language: 'tsx',
title: 'Preact',
codes: [
{
language: 'tsx',
code: `import { PascalCase } from 'lucide-preact';
code: `import { PascalCase } from 'lucide-preact';
const App = () => {
return (
@@ -99,17 +69,11 @@ const App = () => {
export default App;
`,
},
],
},
{
lang: 'solid',
language: 'tsx',
title: 'Solid',
codes: [
{
language: 'tsx',
code: `import { PascalCase } from 'lucide-solid';
code: `import { PascalCase } from 'lucide-solid';
const App = () => {
return (
@@ -119,17 +83,11 @@ const App = () => {
export default App;
`,
},
],
},
{
lang: 'angular',
language: 'tsx',
title: 'Angular',
codes: [
{
language: 'tsx',
code: `// app.module.ts
code: `// app.module.ts
import { LucideAngularModule, PascalCase } from 'lucide-angular';
@NgModule({
@@ -141,54 +99,38 @@ import { LucideAngularModule, PascalCase } from 'lucide-angular';
// app.component.html
<lucide-icon name="Name"></lucide-icon>
`,
},
],
},
{
lang: 'html',
language: 'html',
title: 'Icon Font',
codes: [
{
language: 'html',
code: `<style>
code: `<style>
@import ('~lucide-static/font/Lucide.css');
</style>
<div class="icon-Name"></div>
`,
},
],
},
{
lang: 'dart',
title: 'Flutter',
codes: [
{
language: 'dart',
code: `Icon(LucideIcons.Name);
`,
},
],
},
}
]
}
export type ThemeOptions =
| IThemeRegistration
| { light: IThemeRegistration; dark: IThemeRegistration }
| ThemeRegistration
| { light: ThemeRegistration; dark: ThemeRegistration }
const highLightCode = async (code: string, lang: string, active?: boolean) => {
const highlighter = await getHighlighter({
themes: ['material-theme-palenight'],
langs: [...BUNDLED_LANGUAGES],
processors: []
themes: ['github-light', 'github-dark'],
langs: Object.keys(bundledLanguages)
})
const highlightedCode = highlighter.codeToHtml(code, {
lang,
// lineOptions,
theme: 'material-theme-palenight'
}).replace('background-color: #292D3E', '')
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>
@@ -201,16 +143,15 @@ const highLightCode = async (code: string, lang: string, active?: boolean) => {
export default async function createCodeExamples() {
const codes = getIconCodes();
const codeExamplePromises = codes.map(async (codeTemplate, index) => {
const { title, lang, codes } = codeTemplate;
const codeExamplePromises = codes.map(async ({ title, language, code }, index) => {
const isFirst = index === 0;
const code = await highLightCode(codes[0].code, codes[0].language || lang, isFirst);
const codeString = await highLightCode(code, language, isFirst);
return {
title,
language: codes[0].language || lang,
code,
language: language,
code: codeString,
};
})
+48 -29
View File
@@ -10,28 +10,53 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
{ text: 'Comparison', link: '/guide/comparison' }
]
},
// {
// text: 'Using Icons',
// items: [
// {
// text: 'How to use icons',
// link: 'how-to-use-icons'
// },
// {
// text: 'Styling icons',
// link: 'styling-icons'
// },
// {
// text: 'Accessibility',
// link: 'accessibility'
// },
// {
// text: 'What should I use',
// link: 'what-should-i-use'
// },
// ]
// },
{
text: 'Basics',
items: [
{
text: 'Color',
link: '/guide/basics/color'
},
{
text: 'Sizing',
link: '/guide/basics/sizing'
},
{
text: 'Stroke width',
link: '/guide/basics/stroke-width'
},
]
},
// TODO: Add this section
{
text: 'Advanced',
items: [
// {
// text: 'Accessibility',
// link: '/guide/advanced/accessibility'
// },
{
text: 'Global styling',
link: '/guide/advanced/global-styling'
},
// {
// text: 'Animations',
// },
{
text: 'Filled icons',
link: '/guide/advanced/filled-icons'
},
// {
// text: 'Combining icons',
// },
// {
// text: 'Dynamic imports'
// },
// {
// text: 'Auto importing'
// },
]
},
{
text: 'Packages',
items: [
@@ -103,15 +128,9 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
]
},
],
// This should be here to keep the sidebar shown on the icons page
'icons': [
{ text: '', link: '/' },
// { text: 'Categorized', link: '/icons/categorized' },
// {
// text: 'Categories',
// items: [
// ...(getAllCategoryFiles().map((category) => ({ text: category, link: `/icons/category/${category}` })))
// ]
// }
],
}
@@ -7,5 +7,6 @@
<style scoped>
.container {
padding: 32px;
padding-top: 33px;
}
</style>
@@ -36,6 +36,7 @@ const component = computed(() => props.href ? 'a' : 'div')
border-radius: 6px;
background-color: var(--vp-c-bg-alt);
color: var(--vp-c-text-1);
text-decoration: none;
/* width: 56px;
height: 56px; */
font-size: 16px;
@@ -0,0 +1,56 @@
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{
href?: string;
}>()
const isExternal = computed(() => {
return props.href?.startsWith('http') ?? false
})
const component = computed(() => {
return props.href ? 'a' : 'div'
})
const rel = computed(() => {
return isExternal.value ? 'noreferrer noopener' : undefined
})
</script>
<template>
<component
:is="component"
:href="href"
:rel="rel"
class="card"
>
<slot />
</component>
</template>
<style scoped>
.card {
border: 1px solid var(--vp-c-bg-soft);
border-radius: 12px;
background-color: var(--vp-c-bg-soft);
display: flex;
flex-direction: column;
padding: 24px;
text-decoration: none;
}
.card[href] {
display: block;
border: 1px solid var(--vp-c-bg-soft);
border-radius: 12px;
height: 100%;
background-color: var(--vp-c-bg-soft);
transition: border-color .25s,background-color .25s;
}
.card[href]:hover {
border-color: var(--vp-c-brand-1);
}
</style>
@@ -0,0 +1,36 @@
<template>
<div class="grid">
<slot />
</div>
</template>
<style scoped>
.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>
@@ -54,7 +54,7 @@ const value = computed({
flex-shrink: 0;
}
.color-picker {
background: var(--color-picker-bg, var(--vp-c-bg-soft));
background: var(--color-picker-bg, var(--vp-c-bg-alt));
border-radius: 8px;
color: var(--vp-c-text-2);
padding: 4px 8px;
@@ -71,10 +71,10 @@ const value = computed({
.color-input-text {
width: 100%;
height: 100%;
padding: 0 8px;
padding: 0 0 0 8px;
border: none;
background: transparent;
color: var(--vp-c-text-2);
color: var(--vp-c-text-1);
font-size: 14px;
text-align: left;
border-radius: 8px;
@@ -0,0 +1,63 @@
<script setup lang="ts">
import { computed } from 'vue';
import CardGrid from './CardGrid.vue';
const props = defineProps<{
title: string,
headingLevel: 1 | 2 | 3 | 4 | 5 | 6,
}>()
const headingElement = computed(() => `h${props.headingLevel}`)
</script>
<template>
<section>
<component :is="headingElement" class="name">{{ title }}</component>
<CardGrid>
<slot />
</CardGrid>
</section>
</template>
<style scoped>
.name {
font-size: 32px;
font-weight: bold;
text-align: center;
margin-bottom: 32px;
}
section {
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>
@@ -49,7 +49,7 @@ const percentage = computed<string>(() => `${((Number(props.modelValue) - props.
width: 100%;
line-height: 10px;
height: 20px;
--bar-color: var(--slider-bar-color, var(--vp-c-bg-soft));
--bar-color: var(--slider-bar-color, var(--vp-input-switch-bg-color));
}
.slider:hover input{
@@ -10,7 +10,7 @@ import { data } from './HomeHeroBefore.data'
:href="`https://github.com/lucide-icons/lucide/releases/tag/${data.version}`"
target="_blank"
rel="noreferrer noopener"
>{{ data.version }}</Badge>
>v{{ data.version }}</Badge>
</HomeContainer>
</template>
@@ -67,7 +67,10 @@ watch(absoluteStrokeWidth, (enabled) => {
Lucide has a lot of customization options to match the icons with your UI.
</p>
<div class="customizer">
<div
class="customizer"
style="--color-picker-bg: var(--vp-input-switch-bg-color)"
>
<InputField
id="icon-color"
label="Color"
@@ -1,48 +1,50 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import { useCategoryView } from '../../composables/useCategoryView'
import { onMounted } from 'vue';
import { useCategoryView } from '../../composables/useCategoryView';
interface Header {
level: number
title: string
slug: string
iconCount: number
link: string
children: Header[]
level: number;
title: string;
slug: string;
iconCount: number;
link: string;
children: Header[];
}
type MenuItem = Omit<Header, 'slug' | 'children'> & {
children?: MenuItem[]
}
children?: MenuItem[];
};
const props = defineProps<{
headers: MenuItem[]
root?: boolean
}>()
headers: MenuItem[];
root?: boolean;
}>();
const { selectedCategory } = useCategoryView()
const { selectedCategory } = useCategoryView();
function onClick(event: Event) {
const target = (event.target as HTMLElement).nodeName === 'span' ? (event.target as HTMLElement).parentNode : event.target as HTMLElement
const id = '#' + (target as HTMLAnchorElement).href!.split('#')[1]
const decodedId = decodeURIComponent(id)
const target =
(event.target as HTMLElement).nodeName === 'span'
? (event.target as HTMLElement).parentNode
: (event.target as HTMLElement);
const href = (target as HTMLAnchorElement)?.href;
selectedCategory.value = decodedId.replace('#', '')
if (href) {
const id = '#' + href.split('#')[1];
const decodedId = decodeURIComponent(id);
const heading = document.querySelector<HTMLAnchorElement>(decodedId)
heading?.focus()
selectedCategory.value = decodedId.replace('#', '');
const heading = document.querySelector<HTMLAnchorElement>(decodedId);
heading?.focus();
}
}
</script>
<template>
<ul :class="root ? 'root' : 'nested'">
<li v-for="{ children, link, title, iconCount } in headers">
<a
class="outline-link"
:href="link"
@click="onClick"
:title="title"
>
<a class="outline-link" :href="link" @click="onClick" :title="title">
<span>
{{ title }}
</span>
@@ -10,6 +10,7 @@ import { useRouter } from 'vitepress';
import IconInfo from './IconInfo.vue';
import Badge from '../base/Badge.vue';
import { computedAsync } from '@vueuse/core';
import { satisfies } from 'semver';
const props = defineProps<{
iconName: string
@@ -25,6 +26,12 @@ const icon = computedAsync<IconEntity | null>(async () => {
const emit = defineEmits(['close'])
const isOpen = computed(() => !!icon.value)
function releaseTagLink(version) {
const shouldAddV = satisfies(version, `<0.266.0`)
return `https://github.com/lucide-icons/lucide/releases/tag/${shouldAddV ? 'v' : ''}${version}`
}
function onClose() {
emit('close')
}
@@ -43,7 +50,7 @@ const Expand = createLucideIcon('Expand', expand)
<Badge
v-if="icon.createdRelease"
class="version"
:href="`https://github.com/lucide-icons/lucide/releases/tag/v${icon.createdRelease.version}`"
:href="releaseTagLink(icon.createdRelease.version)"
target="_blank"
rel="noreferrer noopener"
>v{{ icon.createdRelease.version }}</Badge>
@@ -1,93 +1,92 @@
<script setup lang="ts">
import { ref, computed, defineAsyncComponent } from 'vue'
import type { IconEntity, Category } from '../../types'
import useSearch from '../../composables/useSearch'
import InputSearch from '../base/InputSearch.vue'
import useSearchInput from '../../composables/useSearchInput'
import StickyBar from './StickyBar.vue'
import IconsCategory from './IconsCategory.vue'
import { useFetch } from '@vueuse/core'
import useFetchTags from '../../composables/useFetchTags'
import useFetchCategories from '../../composables/useFetchCategories'
import { ref, computed, defineAsyncComponent } from 'vue';
import type { IconEntity, Category } from '../../types';
import useSearch from '../../composables/useSearch';
import InputSearch from '../base/InputSearch.vue';
import useSearchInput from '../../composables/useSearchInput';
import StickyBar from './StickyBar.vue';
import IconsCategory from './IconsCategory.vue';
import { useFetch } from '@vueuse/core';
import useFetchTags from '../../composables/useFetchTags';
import useFetchCategories from '../../composables/useFetchCategories';
const props = defineProps<{
icons: IconEntity[]
categories: Category[]
iconCategories: Record<string, string[]>
}>()
icons: IconEntity[];
categories: Category[];
iconCategories: Record<string, string[]>;
}>();
const activeIconName = ref(null)
const { searchInput, searchQuery, searchQueryThrottled } = useSearchInput()
const activeIconName = ref(null);
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
const isSearching = computed(() => !!searchQuery.value)
const isSearching = computed(() => !!searchQuery.value);
function setActiveIconName(name: string) {
activeIconName.value = name
activeIconName.value = name;
}
const { execute: fetchTags, data: tags } = useFetchTags()
const { execute: fetchCategories, data: categoriesMap } = useFetchCategories()
const { execute: fetchTags, data: tags } = useFetchTags();
const { execute: fetchCategories, data: categoriesMap } = useFetchCategories();
const mappedIcons = computed(() => {
if(tags.value == null) {
return props.icons
if (tags.value == null) {
return props.icons;
}
return props.icons.map((icon) => {
const iconTags = tags.value[icon.name]
const iconCategories = categoriesMap.value?.[icon.name] ?? []
const iconTags = tags.value[icon.name];
const iconCategories = categoriesMap.value?.[icon.name] ?? [];
return {
...icon,
tags: iconTags,
categories: iconCategories,
}
})
})
};
});
});
const searchResults = useSearch(searchQuery, mappedIcons, [
{ name: 'name', weight: 2 },
{ name: 'tags', weight: 1 },
])
]);
const categories = computed(() => {
if( !props.categories?.length || !props.icons?.length ) return []
if (!props.categories?.length || !props.icons?.length) return [];
return props.categories.map(({ name, title }) => {
const categoryIcons = props.icons.filter((icon) => {
const iconCategories = props.iconCategories[icon.name]
return props.categories
.map(({ name, title }) => {
const categoryIcons = props.icons.filter((icon) => {
const iconCategories = props.iconCategories[icon.name];
return iconCategories?.includes(name)
return iconCategories?.includes(name);
});
const searchedCategoryIcons = isSearching
? categoryIcons.filter((icon) =>
searchResults.value.some((item) => item?.name === icon?.name)
)
: categoryIcons;
return {
title,
name,
icons: searchedCategoryIcons,
};
})
const searchedCategoryIcons = isSearching
? categoryIcons.filter(icon => searchResults.value.some((item) => item?.name === icon?.name))
: categoryIcons;
return {
title,
name,
icons: searchedCategoryIcons,
};
})
.filter(({ icons }) => icons.length)
})
.filter(({ icons }) => icons.length);
});
function onFocusSearchInput() {
if (tags.value == null) {
fetchTags()
fetchTags();
}
if (categoriesMap.value == null) {
fetchCategories()
fetchCategories();
}
}
const NoResults = defineAsyncComponent(() =>
import('./NoResults.vue')
)
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
const IconDetailOverlay = defineAsyncComponent(() =>
import('./IconDetailOverlay.vue')
)
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
</script>
<template>
@@ -100,11 +99,7 @@ const IconDetailOverlay = defineAsyncComponent(() =>
@focus="onFocusSearchInput"
/>
</StickyBar>
<NoResults
v-if="categories.length === 0"
:searchQuery="searchQuery"
@clear="searchQuery = ''"
/>
<NoResults v-if="categories.length === 0" :searchQuery="searchQuery" @clear="searchQuery = ''" />
<IconsCategory
v-for="category in categories"
:key="category.name"
@@ -1,21 +1,21 @@
<script setup lang="ts">
import { ref, computed, watch, defineAsyncComponent } from 'vue'
import type { IconEntity } from '../../types'
import { useMediaQuery, useOffsetPagination } from '@vueuse/core'
import IconGrid from './IconGrid.vue'
import InputSearch from '../base/InputSearch.vue'
import useSearch from '../../composables/useSearch'
import EndOfPage from '../base/EndOfPage.vue'
import useSearchInput from '../../composables/useSearchInput'
import StickyBar from './StickyBar.vue'
import useFetchTags from '../../composables/useFetchTags'
import useFetchCategories from '../../composables/useFetchCategories'
import { ref, computed, watch, defineAsyncComponent } from 'vue';
import type { IconEntity } from '../../types';
import { useMediaQuery, useOffsetPagination } from '@vueuse/core';
import IconGrid from './IconGrid.vue';
import InputSearch from '../base/InputSearch.vue';
import useSearch from '../../composables/useSearch';
import EndOfPage from '../base/EndOfPage.vue';
import useSearchInput from '../../composables/useSearchInput';
import StickyBar from './StickyBar.vue';
import useFetchTags from '../../composables/useFetchTags';
import useFetchCategories from '../../composables/useFetchCategories';
const props = defineProps<{
icons: IconEntity[]
}>()
icons: IconEntity[];
}>();
const activeIconName = ref(null)
const activeIconName = ref(null);
const isExtraLargeScreen = useMediaQuery('(min-width: 1440px)');
const isLargeScreen = useMediaQuery('(min-width: 1280px)');
@@ -23,84 +23,78 @@ const isMediumScreen = useMediaQuery('(min-width: 960px)');
const isSmallScreen = useMediaQuery('(min-width: 640px)');
const pageSize = computed(() => {
if(isExtraLargeScreen.value) {
if (isExtraLargeScreen.value) {
return 16 * 20;
}
if(isLargeScreen.value) {
if (isLargeScreen.value) {
return 16 * 12;
}
if(isMediumScreen.value) {
if (isMediumScreen.value) {
return 13 * 12;
}
if(isSmallScreen.value) {
if (isSmallScreen.value) {
return 10 * 10;
}
return 10 * 5;
})
});
const { execute: fetchTags, data: tags } = useFetchTags()
const { execute: fetchCategories, data: categories } = useFetchCategories()
const { execute: fetchTags, data: tags } = useFetchTags();
const { execute: fetchCategories, data: categories } = useFetchCategories();
const mappedIcons = computed(() => {
if(tags.value == null) {
return props.icons
if (tags.value == null) {
return props.icons;
}
return props.icons.map((icon) => {
const iconTags = tags.value[icon.name]
const iconCategories = categories.value?.[icon.name] ?? []
const iconTags = tags.value[icon.name];
const iconCategories = categories.value?.[icon.name] ?? [];
return {
...icon,
tags: iconTags,
categories: iconCategories,
}
})
})
};
});
});
const { searchInput, searchQuery, searchQueryThrottled } = useSearchInput()
const searchResults = useSearch(searchQueryThrottled, mappedIcons, [
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
{ name: 'name', weight: 3 },
{ name: 'tags', weight: 2 },
{ name: 'categories', weight: 1 },
])
const { next, currentPage } = useOffsetPagination( { pageSize })
]);
const { next, currentPage } = useOffsetPagination({ pageSize });
const paginatedIcons = computed(() => {
const end = pageSize.value * currentPage.value
const end = pageSize.value * currentPage.value;
return searchResults.value.slice(0, end)
})
return searchResults.value.slice(0, end);
});
function setActiveIconName(name: string) {
activeIconName.value = name
activeIconName.value = name;
}
watch(searchQueryThrottled, (searchString) => {
currentPage.value = 1
})
watch(searchQueryDebounced, (searchString) => {
currentPage.value = 1;
});
function onFocusSearchInput() {
if (tags.value == null) {
fetchTags()
fetchTags();
}
if (categories.value == null) {
fetchCategories()
fetchCategories();
}
}
const NoResults = defineAsyncComponent(() =>
import('./NoResults.vue')
)
const IconDetailOverlay = defineAsyncComponent(() =>
import('./IconDetailOverlay.vue')
)
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
</script>
<template>
@@ -124,7 +118,7 @@ const IconDetailOverlay = defineAsyncComponent(() =>
:icons="paginatedIcons"
@setActiveIcon="setActiveIconName"
/>
<EndOfPage @end-of-page="next" class="bottom-page"/>
<EndOfPage @end-of-page="next" class="bottom-page" />
<IconDetailOverlay
v-if="activeIconName != null"
:iconName="activeIconName"
@@ -36,15 +36,22 @@ const links = computed(() => [
<template>
<footer v-if="theme.footer" class="VPFooter" :class="{ 'has-sidebar': hasSidebar }">
<div class="container">
<p v-if="theme.footer.message" class="message" v-html="theme.footer.message"></p>
<p v-if="theme.footer.copyright" class="copyright" v-html="theme.footer.copyright"></p>
<div class="sponsors">
<a href="https://vercel.com?utm_source=lucide&utm_campaign=oss" rel="noreferrer noopener">
<img src="/vercel.svg" alt="Powered by Vercel" width="200" />
</a>
<a href="https://www.digitalocean.com/?refcode=b0877a2caebd&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge" rel="noreferrer noopener">
<img src="/digitalocean.svg" alt="Digital Ocean" width="200" />
</a>
</div>
<div class="links">
<VPLink v-for="link in links" :href="link.href" :key="link.text" :rel="link.href.startsWith('http') ? 'noreferrer noopener': undefined">
{{ link.text }}
</VPLink>
<a href="https://vercel.com?utm_source=lucide&utm_campaign=oss" rel="noreferrer noopener">
<img src="/vercel.svg" alt="Powered by Vercel" width="200" />
</a>
</div>
<div>
<p v-if="theme.footer.message" class="message" v-html="theme.footer.message"></p>
<p v-if="theme.footer.copyright" class="copyright" v-html="theme.footer.copyright"></p>
</div>
</div>
</footer>
@@ -67,7 +74,7 @@ const links = computed(() => [
.container {
margin: 0 auto;
max-width: var(--vp-layout-max-width);
max-width: calc(var(--vp-layout-max-width) - 64px);
display: flex;
flex-direction: column;
align-items: center;
@@ -85,7 +92,7 @@ const links = computed(() => [
.message { order: 2; }
.copyright { order: 1; }
.links {
.links, .sponsors {
display: flex;
gap: 32px;
align-items: center;
@@ -102,7 +109,7 @@ const links = computed(() => [
flex-direction: row-reverse;
}
.links {
margin-left: auto;
margin-right: auto;
}
}
</style>
@@ -1,24 +1,30 @@
<script setup lang="ts">
import {data} from './PackageList.data'
import GridSection from '../base/GridSection.vue'
import PackageListItem from "./PackageListItem.vue";</script>
<template>
<section class="package-group">
<h1 class="name">Packages</h1>
<div class="grid package-list" ref="container">
<div v-for="packageData in data.packages" class="item">
<PackageListItem :packageData="packageData"/>
</div>
</div>
</section>
<section class="package-group">
<h2 class="name">Third-party packages</h2>
<div class="grid package-list" ref="container">
<div v-for="packageData in data.thirdPartyPackages" class="item">
<PackageListItem :packageData="packageData"/>
</div>
</div>
</section>
<GridSection
title="Packages"
:headingLevel="1"
class="package-group"
>
<PackageListItem
v-for="packageData in data.packages"
:packageData="packageData"
/>
</GridSection>
<GridSection
title="Third-party packages"
:headingLevel="2"
class="package-group"
>
<PackageListItem
v-for="packageData in data.thirdPartyPackages"
:packageData="packageData"
/>
</GridSection>
</template>
<style scoped>
@@ -26,7 +32,7 @@ import PackageListItem from "./PackageListItem.vue";</script>
font-size: 32px;
font-weight: bold;
text-align: center;
margin-bottom: 24px;
margin-bottom: 32px;
}
.package-group {
@@ -2,6 +2,7 @@
import { useRouter } from 'vitepress';
import {PackageItem} from "../../types";
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
import Card from '../base/Card.vue'
const { go } = useRouter()
const props = defineProps<{
@@ -10,48 +11,40 @@ const props = defineProps<{
</script>
<template>
<article class="package">
<header class="package-header">
<div class="package-icon-well">
<img :src="packageData.icon" alt="" class="package-icon" :class="{[packageData.iconClass]: true, light: packageData.iconDark}" />
<img v-if="packageData.iconDark" :src="packageData.iconDark" alt="" class="package-icon dark" :class="packageData.iconClass" />
<div>
<Card class="package">
<header class="package-header">
<div class="package-icon-well">
<img :src="packageData.icon" alt="" class="package-icon" :class="{[packageData.iconClass]: true, light: packageData.iconDark}" />
<img v-if="packageData.iconDark" :src="packageData.iconDark" alt="" class="package-icon dark" :class="packageData.iconClass" />
</div>
<div class="package-title">
<h2 class="title">{{ props.packageData.name }}</h2>
<a v-for="shield in props.packageData.shields" :href="shield.href" class="package-shield" rel="noreferrer noopener">
<img :src="shield.src" :alt="shield.href" />
</a>
</div>
</header>
<div class="package-details">
{{ packageData.description }}
</div>
<div class="package-title">
<h2 class="title">{{ props.packageData.name }}</h2>
<a v-for="shield in props.packageData.shields" :href="shield.href" class="package-shield" rel="noreferrer noopener">
<img :src="shield.src" :alt="shield.href" />
</a>
</div>
</header>
<div class="package-details">
{{ packageData.description }}
</div>
<footer class="package-footer">
<VPButton
:href="packageData.documentation"
text="Guide"
theme="brand"
@click="go(packageData.documentation)"
/>
<VPButton
:href="packageData.source"
text="Source"
theme="alt"
@click="go(packageData.source)"
/>
</footer>
</article>
<footer class="package-footer">
<VPButton
:href="packageData.documentation"
text="Guide"
theme="brand"
/>
<VPButton
:href="packageData.source"
text="Source"
theme="alt"
/>
</footer>
</Card>
</div>
</template>
<style scoped>
.package {
border: 1px solid var(--vp-c-bg-soft);
border-radius: 12px;
background-color: var(--vp-c-bg-soft);
display: flex;
flex-direction: column;
padding: 24px;
}
.package {
display: flex;
flex-direction: column;
@@ -0,0 +1,72 @@
<script setup lang="ts">
import companies from '../../../data/companiesData.json'
import componentLibraries from '../../../data/componentLibrariesData.json'
import GridSection from '../base/GridSection.vue'
import ShowcaseListItem from "./ShowcaseListItem.vue";
</script>
<template>
<GridSection
title="Used by"
:headingLevel="1"
class="package-group"
>
<ShowcaseListItem
v-for="company in companies"
:showcaseItem="company"
/>
</GridSection>
<GridSection
title="Used in"
:headingLevel="1"
class="package-group"
>
<ShowcaseListItem
v-for="componentLibrary in componentLibraries"
:showcaseItem="componentLibrary"
/>
</GridSection>
</template>
<style scoped>
.name {
font-size: 32px;
font-weight: bold;
text-align: center;
margin-bottom: 32px;
}
.package-group {
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>
@@ -0,0 +1,52 @@
<script setup lang="ts">
import { useRouter } from 'vitepress';
import { ShowcaseItem } from "../../types";
import Card from '../base/Card.vue'
const { go } = useRouter()
defineProps<{
showcaseItem: ShowcaseItem,
}>()
</script>
<template>
<div>
<Card
class="company"
:href="showcaseItem.url"
:aria-label="showcaseItem.name"
>
<img
:src="showcaseItem.image.light"
class="logo light"
:alt="`${showcaseItem.name} logo`"
/>
<img
:src="showcaseItem.image.dark"
class="logo dark"
:alt="`${showcaseItem.name} logo`"
/>
</Card>
</div>
</template>
<style scoped>
.company {
display: flex;
height: 100%;
padding: 48px 0;
}
.logo {
height:64px;
width: 240px;
margin: auto;
}
html.dark .logo.dark {
display: none;
}
html:not(.dark) .logo.light {
display: none;
}
</style>
@@ -1,43 +1,40 @@
import { refThrottled } from '@vueuse/core';
import { useDebounce } from '@vueuse/core';
import { nextTick, onMounted, ref, watch } from 'vue';
const useSearchInput = () => {
const searchInput = ref()
const searchInput = ref();
const searchQuery = ref(
typeof window === 'undefined'
? ''
: (
new URLSearchParams(window.location.search).get('search')
|| ''
)
)
const searchQueryThrottled = refThrottled(searchQuery, 400)
? ''
: new URLSearchParams(window.location.search).get('search') || ''
);
const searchQueryDebounced = useDebounce(searchQuery, 250);
watch(searchQueryThrottled, (searchString) => {
watch(searchQueryDebounced, (searchString) => {
const newUrl = new URL(window.location.href);
if(searchString === '') {
if (searchString === '') {
newUrl.searchParams.delete('search');
} else {
newUrl.searchParams.set('search', searchString);
}
nextTick(() => {
window.history.replaceState({}, '', newUrl)
})
})
window.history.replaceState({}, '', newUrl);
});
});
onMounted(() => {
const searchParams = new URLSearchParams(window.location.search);
if(searchParams.has('focus')) {
searchInput.value.focus()
if (searchParams.has('focus')) {
searchInput.value.focus();
}
})
});
return {
searchInput,
searchQuery,
searchQueryThrottled
searchQueryDebounced,
};
};
+34
View File
@@ -0,0 +1,34 @@
{
"colors": {
"surface1": "var(--vp-code-block-bg)",
"surface2": "var(--vp-code-block-bg)",
"surface3": "var(--vp-code-line-highlight-color)",
"clickable": "var(--vp-c-text-2)",
"base": "#323232",
"disabled": "#C5C5C5",
"hover": "var(--vp-c-brand)",
"accent": "var(--vp-c-brand)",
"error": "var(--vp-c-red)",
"errorSurface": "#ffeceb"
},
"syntax": {
"plain": "var(--vp-code-editor-plain)",
"comment": {
"color": "var(--vp-code-editor-comment)",
"fontStyle": "italic"
},
"keyword": "var(--vp-code-editor-keyword)",
"tag": "var(--vp-code-editor-tag)",
"punctuation": "var(--vp-code-editor-punctuation)",
"definition": "var(--vp-code-editor-definition)",
"property": "var(--vp-code-editor-property)",
"static": "var(--vp-code-editor-static)",
"string": "var(--vp-code-editor-string)"
},
"font": {
"body": "var(--vp-font-family-base)",
"mono": "var(--vp-font-family-mono)",
"size": "var(--vp-code-font-size)",
"lineHeight": "var(--vp-code-line-height)"
}
}
+79
View File
@@ -5,13 +5,39 @@
--vp-c-brand-dark: #DC5A5A;
--vp-c-brand-darker: #C45050;
--vp-c-brand-1: #F67373;
--vp-c-brand-2: #FF7070;
--vp-c-brand-3: #F56565;
--vp-c-brand-4: #DC5A5A;
--vp-c-brand-5: #C45050;
--vp-c-bg-alt-up: #fff;
--vp-c-bg-alt-down: #fff;
--vp-code-editor-plain: #24292E;
--vp-code-editor-comment: #6A737D;
--vp-code-editor-keyword: #D73A49;
--vp-code-editor-tag: #22863A;
--vp-code-editor-punctuation: #24292E;
--vp-code-editor-definition: #6F42C1;
--vp-code-editor-property: #005CC5;
--vp-code-editor-static: #F78C6C;
--vp-code-editor-string: #032F62;
}
.dark {
--vp-c-bg-alt-up: #1B1B1D;
--vp-c-bg-alt-down: #0F0F10;
--vp-code-editor-plain: #E1E4E8;
--vp-code-editor-comment: #6A737D;
--vp-code-editor-keyword: #F97583;
--vp-code-editor-tag: #85E89D;
--vp-code-editor-punctuation: #9ECBFF;
--vp-code-editor-definition: #B392F0;
--vp-code-editor-property: #79B8FF;
--vp-code-editor-static: #F78C6C;
--vp-code-editor-string: #9ECBFF;
}
.VPNavBarTitle .logo {
@@ -126,3 +152,56 @@
html:has(* .outline-link:target) {
scroll-behavior: smooth;
} */
.sp-wrapper + * {
margin-top: 24px;
}
.sp-wrapper .sp-layout {
border-radius: 8px;
}
.sp-wrapper .sp-tabs-scrollable-container {
border-radius: 8px 8px 0 0;
position: relative;
box-shadow: inset 0 -1px var(--vp-code-tab-divider);
margin-bottom: 0px;
margin-top: -1px;
height: 48px;
padding-bottom: 1px;
}
.sp-wrapper .sp-preview-container {
background-color: transparent;
}
.sp-wrapper .sp-tabs .sp-tab-button {
padding: 0 12px;
line-height: 48px;
height: 48px;
font-size: 14px;
font-weight: 500;
position: relative;
/* box-sizing: content-box; */
}
.sp-wrapper .sp-tabs .sp-tab-button:after {
position: absolute;
right: 8px;
left: 8px;
bottom: 0px;
z-index: 1;
height: 1px;
content: '';
background-color: transparent;
transition: background-color 0.25s;
}
.sp-wrapper .sp-tabs .sp-tab-button[data-active="true"] {
color: var(--vp-code-tab-active-text-color);
}
.sp-wrapper .sp-tabs .sp-tab-button[data-active="true"]:after {
background-color: var(--vp-code-tab-active-bar-color);
}
+11
View File
@@ -44,3 +44,14 @@ export interface Release {
version: string
date: string
}
interface ShowcaseItemImage {
light: string
dark: string
}
export interface ShowcaseItem {
name: string
url: string
image: ShowcaseItemImage
}
+11 -3
View File
@@ -1,10 +1,18 @@
declare module "*.vue" {
import Vue from "vue";
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}
declare module "*.data.ts" {
declare module '*.data.ts' {
const data: any;
export { data };
}
declare module '*.wasm' {}
declare const resvg_wasm: RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
declare module 'node:module' {
function createRequire(filename: string): NodeRequire;
}
-2
View File
@@ -3,8 +3,6 @@
The Lucide docs website is built with Vitepress: https://vitepress.dev/
This is Markdown-based documentation powered by Vue.
This is why this file is in txt format.
## Development
```sh
+4
View File
@@ -0,0 +1,4 @@
# Accessibility
<!-- Description how you should use svg icons keeping web accessible -->
<!-- See @JanTrichter comment about some information to write this: https://github.com/lucide-icons/lucide/pull/1521#discussion_r1332141390 -->
@@ -0,0 +1,23 @@
import { Star, StarHalf } from "lucide-react";
import "./icon.css";
function App() {
return (
<div className="app">
<div className="star-rating">
<div className="stars">
{ Array.from({ length: 5 }, () => (
<Star fill="#111" strokeWidth={0} />
))}
</div>
<div className="stars rating">
<Star fill="yellow" strokeWidth={0} />
<Star fill="yellow" strokeWidth={0} />
<StarHalf fill="yellow" strokeWidth={0} />
</div>
</div>
</div>
);
}
export default App;
@@ -0,0 +1,20 @@
import App from './App.js?raw'
import styles from '../../../basics/examples/styles.css?raw'
import IconCss from './icon.css?raw'
const files = {
'App.js': {
code: App,
active: true,
},
'icon.css': {
code: IconCss,
readOnly: false,
},
'styles.css': {
code: styles,
hidden: true
},
}
export default files
@@ -0,0 +1,14 @@
.star-rating {
position: relative;
}
.stars {
display: flex;
gap: 4px;
}
.rating {
position: absolute;
top: 0;
}
@@ -0,0 +1,30 @@
import {
TentTree,
Caravan,
FlameKindling,
MountainSnow,
Trees,
Axe,
Map,
CloudMoon,
Sparkles,
} from "lucide-react";
import "./icon.css";
function App() {
return (
<div className="app">
<TentTree />
<Caravan />
<FlameKindling />
<MountainSnow />
<Trees />
<Axe />
<Map />
<CloudMoon />
<Sparkles />
</div>
);
}
export default App;
@@ -0,0 +1,20 @@
import App from './App.js?raw'
import styles from '../../../basics/examples/styles.css?raw'
import IconCss from './icon.css?raw'
const files = {
'icon.css': {
code: IconCss,
readOnly: false,
active: true,
},
'App.js': {
code: App,
},
'styles.css': {
code:styles,
hidden: true
},
}
export default files
@@ -0,0 +1,16 @@
.lucide {
width: 48px;
height: 48px;
stroke-width: 1.5;
}
.lucide * {
vector-effect: non-scaling-stroke;
}
.app {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr
gap: 6px;
}
@@ -0,0 +1,30 @@
import {
CakeSlice,
Candy,
Apple,
Cookie,
Martini,
IceCream2,
Sandwich,
Wine,
Dessert,
} from "lucide-react";
import "./icon.css";
function App() {
return (
<div className="app">
<CakeSlice />
<Candy />
<Apple />
<Cookie />
<Martini />
<IceCream2 />
<Sandwich />
<Wine />
<Dessert />
</div>
);
}
export default App;
@@ -0,0 +1,20 @@
import App from './App.js?raw'
import styles from '../../../basics/examples/styles.css?raw'
import IconCss from './icon.css?raw'
const files = {
'icon.css': {
code: IconCss,
readOnly: false,
active: true,
},
'App.js': {
code: App,
},
'styles.css': {
code:styles,
hidden: true
},
}
export default files
@@ -0,0 +1,14 @@
.lucide {
/* Change this! */
color: #ffadff;
width: 56px;
height: 56px;
stroke-width: 1px;
}
.app {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr
gap: 6px;
}
+31
View File
@@ -0,0 +1,31 @@
<script setup>
import { Sandpack } from 'sandpack-vue3'
import sandpackTheme from '../../.vitepress/theme/sandpackTheme.json'
import sizeIconExample from './examples/filled-icon-example/files.ts'
</script>
# Filled Icons
Fills are officially not supported.
However, all SVG properties are available on all icons.
Fill can still be used and will work fine on certain icons.
Example with stars:
<Sandpack
template="react"
:theme="sandpackTheme"
:files="sizeIconExample"
:customSetup='{
dependencies: {
"lucide-react": "latest"
}
}'
:options="{
editorHeight: 480,
editorWidthPercentage: 60,
}"
/>
## Will Lucide have fills in the future?
This feature has been requested several times and discussion is happening at [#458](https://github.com/lucide-icons/lucide/discussions/458).
+55
View File
@@ -0,0 +1,55 @@
<script setup>
import { Sandpack } from 'sandpack-vue3'
import sandpackTheme from '../../.vitepress/theme/sandpackTheme.json'
import globalIconCssExample from './examples/global-styling-css-example/files.ts'
import globalAbsoluteStrokewidthExample from './examples/global-styling-absolute-strokewidth-example/files.ts'
</script>
# Global Styling
Adjusting icons can be done by [color](../basics/color.md), [size](../basics/sizing.md) and [stroke width](../basics/stroke-width.md).
By default, all icons have a **color** value of `currentColor`, a **size** value of `24px`, and a **stroke width** of `2`. Styling icons individually can be done by passing props to the icon component.
## Style by using CSS
Styling icons is easy to accomplish using CSS.
Every icon has a class attribute applied called `lucide`. This class name can be used in the CSS file to target all icons that are being used within the app.
- The **color** of the icons can be changed using the [`color`](https://developer.mozilla.org/en-US/docs/Web/CSS/color) CSS property.
- The **size** of the icons can be changed using [`width`](https://developer.mozilla.org/en-US/docs/Web/CSS/width) and [`height`](https://developer.mozilla.org/en-US/docs/Web/CSS/height) CSS properties.
- The **stroke width** of the icons can be changed using the [`stroke-width`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-width) CSS property.
<Sandpack
template="react"
:theme="sandpackTheme"
:files="globalIconCssExample"
:customSetup='{
dependencies: {
"lucide-react": "latest"
}
}'
:options="{
editorHeight: 300,
}"
/>
### Absolute stroke width
For global absolute stroke width styling the `vector-effect: non-scaling-stroke` CSS property can be applied to the children. This will keep the stroke-width the same size no matter the size of the icon. See [absolute-stroke-width](../basics/stroke-width.md#absolute-stroke-width) for more info.
<Sandpack
template="react"
:theme="sandpackTheme"
:files="globalAbsoluteStrokewidthExample"
:customSetup='{
dependencies: {
"lucide-react": "latest"
}
}'
:options="{
editorHeight: 300,
}"
/>
+52
View File
@@ -0,0 +1,52 @@
<script setup>
import { Sandpack } from 'sandpack-vue3'
import sandpackTheme from '../../.vitepress/theme/sandpackTheme.json'
import buttonExampleFiles from './examples/button-example/files.ts'
import iconColorExampleFiles from './examples/color-icon/files.ts'
</script>
# Color
By default, all icons have the color value: `currentColor`. This keyword uses the element's computed text `color` value to represent the icon color.
Read more about [ `currentColor` on MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#currentcolor_keyword).
## Adjust the color using the `color` prop
The color can be adjusted by passing the color prop to the element.
<Sandpack
template="react"
:theme="sandpackTheme"
:files="iconColorExampleFiles"
:customSetup='{
dependencies: {
"lucide-react": "latest"
}
}'
:options="{
editorHeight: 295,
editorWidthPercentage: 60,
}"
/>
## Using parent elements text color value
Because the color of lucide icons uses `currentColor`, the color of the icon depends on the computed `color` of the element, or it inherits it from its parent.
For example, if a parent element's color value is `#fff` and one of the children is a lucide icon, the color of the icon will be rendered as `#fff`. This is browser native behavior.
<Sandpack
template="react"
:theme="sandpackTheme"
:files="buttonExampleFiles"
:customSetup='{
dependencies: {
"lucide-react": "latest"
}
}'
:options="{
editorHeight: 320,
editorWidthPercentage: 60,
}"
/>
@@ -0,0 +1,14 @@
import { RollerCoaster } from "lucide-react";
function App() {
return (
<div className="app">
<RollerCoaster
size={96}
absoluteStrokeWidth={true}
/>
</div>
);
}
export default App;
@@ -0,0 +1,15 @@
import App from './App.js?raw'
import styles from '../styles.css?raw'
const files = {
'App.js': {
code: App,
active: true,
},
'styles.css': {
code:styles,
hidden: true
},
}
export default files
@@ -0,0 +1,5 @@
import Button from "./Button";
export default function App() {
return <Button />;
}
@@ -0,0 +1,12 @@
import { ThumbsUp } from "lucide-react";
function LikeButton() {
return (
<button style={{ color: "#fff" }}>
<ThumbsUp />
Like
</button>
);
}
export default LikeButton;
@@ -0,0 +1,21 @@
import App from './App.js?raw'
import Button from './Button.jsx?raw'
import styles from '../styles.css?raw'
const files = {
'App.js': {
code: App,
hidden: true
},
'Button.jsx': {
code: Button,
active: true,
readOnly: false,
},
'styles.css': {
code:styles,
hidden: true
},
}
export default files
@@ -0,0 +1,11 @@
import { Smile } from "lucide-react";
function App() {
return (
<div className="app">
<Smile color="#3e9392" />
</div>
);
}
export default App;
@@ -0,0 +1,15 @@
import App from './App.js?raw'
import styles from '../styles.css?raw'
const files = {
'App.js': {
code: App,
active: true,
},
'styles.css': {
code:styles,
hidden: true
},
}
export default files
@@ -0,0 +1,12 @@
import { Beer } from "lucide-react";
import "./icon.css";
function App() {
return (
<div className="app">
<Beer className="my-beer-icon" />
</div>
);
}
export default App;
@@ -0,0 +1,21 @@
import App from './App.js?raw'
import styles from '../styles.css?raw'
import IconCss from './icon.css?raw'
const files = {
'icon.css': {
code: IconCss,
readOnly: false,
active: true,
},
'App.js': {
code: App,
},
'styles.css': {
code:styles,
hidden: true
},
}
export default files
@@ -0,0 +1,5 @@
.my-beer-icon {
/* Change this! */
width: 64px;
height: 64px;
}
@@ -0,0 +1,11 @@
import { Landmark } from "lucide-react";
function App() {
return (
<div className="app">
<Landmark size={64} />
</div>
);
}
export default App;

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