diff --git a/.cspell/custom-words.txt b/.cspell/custom-words.txt index d41cd853b..e0e6b2098 100644 --- a/.cspell/custom-words.txt +++ b/.cspell/custom-words.txt @@ -8,7 +8,6 @@ squircle strikethrough touchpad ungroup -pilcrow toc # Brands diff --git a/.github/ISSUE_TEMPLATE/01_icon_request.yml b/.github/ISSUE_TEMPLATE/01_icon_request.yml index a3c4bfca1..a7f2daa73 100644 --- a/.github/ISSUE_TEMPLATE/01_icon_request.yml +++ b/.github/ISSUE_TEMPLATE/01_icon_request.yml @@ -7,8 +7,10 @@ body: 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. + > [!CAUTION] + > New brand logos are **not** allowed, see our official statement: https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md. + > + > Existing brand icons are being phased out. Consider using https://simpleicons.org, which offers purpose-built SVGs that are also on a 24×24px grid. - type: input id: name attributes: @@ -41,9 +43,9 @@ body: required: true - label: I have searched existing icons to make sure it does not already exist and I didn't find any. required: true - - label: I am not requesting a brand logo and the art is not protected by copyright. + - label: I am not requesting a brand logo and the art is not protected by copyright, see more at https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md required: true - - label: I am not requesting an icon that includes religious, political imagery or hate symbols. + - label: I am not requesting an icon that includes religious, war/violence related, political imagery or hate symbols. required: true - label: I have provided appropriate use cases for the icon(s) requested. required: true diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 2403cee42..7efa7e6d6 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,16 +1,18 @@ + + - -## What is the purpose of this pull request? - -- [ ] New Icon -- [ ] Bug fix -- [ ] New Feature -- [ ] Documentation update -- [ ] Other: - -### Description +## Description ### Icon use case @@ -23,10 +25,12 @@ ### Concept + + - [ ] I have provided valid use cases for each icon. -- [ ] I have not added any a brand or logo icon. +- [ ] I have [not added any brand or logo icon](https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md). - [ ] I have not used any hate symbols. -- [ ] I have not included any religious or political imagery. +- [ ] I have not included any religious, war/violence related or political imagery. ### Author, credits & license diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d37da8f74..638d7cc2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,10 @@ on: paths: - icons/**/*.svg +permissions: + id-token: write # Required for OIDC + contents: write + jobs: create-release: if: github.repository == 'lucide-icons/lucide' && startsWith(github.event.head_commit.message, 'feat(icons)') @@ -15,9 +19,9 @@ jobs: VERSION: ${{ steps.new-version.outputs.NEW_VERSION }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' @@ -32,25 +36,19 @@ jobs: id: latest-tag run: echo "LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)" >> $GITHUB_OUTPUT + - name: Log latest tag + run: echo '${{ steps.latest-tag.outputs.LATEST_TAG }}' + - name: Check if we can patch - run: .github/workflows/version-up.sh --minor + run: pnpm semver $LATEST_TAG -i minor + env: + LATEST_TAG: ${{ steps.latest-tag.outputs.LATEST_TAG }} - name: Create new version id: new-version - run: echo "NEW_VERSION=$(.github/workflows/version-up.sh --minor)" >> $GITHUB_OUTPUT - - - name: Create change log - id: change-log - run: | - CHANGE_LOG=$(pnpm run generate:changelog --old-tag=${{ steps.latest-tag.outputs.LATEST_TAG }}) - CHANGE_LOG=$(tail -n +5 <<< $CHANGE_LOG) - echo $CHANGE_LOG - 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 + run: echo "NEW_VERSION=$(pnpm semver $LATEST_TAG -i minor)" >> $GITHUB_OUTPUT env: - GITHUB_API_KEY: ${{ secrets.GITHUB_TOKEN }} + LATEST_TAG: ${{ steps.latest-tag.outputs.LATEST_TAG }} - name: Check output run: | @@ -64,38 +62,6 @@ jobs: name: Version ${{ steps.new-version.outputs.NEW_VERSION }} generate_release_notes: true - test-semantic-release: - if: github.repository == 'lucide-icons/lucide' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Semantic Release - id: semantic - uses: cycjimmy/semantic-release-action@v4 - with: - tag_format: ${version} - branches: | - ['new-release-workflow'] - extends: | - semantic-release-monorepo - extra_plugins: | - @semantic-release/github - @semantic-release/git - @semantic-release/release-notes-generator - conventional-changelog-conventionalcommits - - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Log output - if: steps.semantic.outputs.new_release_published == 'true' - run: | - echo ${{ steps.semantic.outputs.new_release_version }} - echo ${{ steps.semantic.outputs.new_release_major_version }} - echo ${{ steps.semantic.outputs.new_release_minor_version }} - echo ${{ steps.semantic.outputs.new_release_patch_version }} - start-release: if: github.repository == 'lucide-icons/lucide' needs: create-release diff --git a/.github/workflows/close-issue-with-banned-phrases.yml b/.github/workflows/close-issue-with-banned-phrases.yml index 2807f3607..abd2d7c7f 100644 --- a/.github/workflows/close-issue-with-banned-phrases.yml +++ b/.github/workflows/close-issue-with-banned-phrases.yml @@ -1,4 +1,4 @@ -name: Close Issue with Banned Phrases +name: Close Icon Requests with Brand Terms on: issues: @@ -11,25 +11,38 @@ jobs: issues: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - - name: Check for blocked phrases in issue title + - name: Load stopwords from JSON & check issue title & body + if: contains(github.event.issue.labels.*.name, '🙌 icon request') run: | ISSUE_TITLE=$(jq -r '.issue.title' "$GITHUB_EVENT_PATH") - BLOCKED_PHRASES=("twitter" "whatsapp" "logo" "google" "tiktok" "facebook" "slack" "discord" "bluesky" "spotify" "behance" "pix" "x.com") + ISSUE_BODY=$(jq -r '.issue.body // ""' "$GITHUB_EVENT_PATH") + ICON_NAME_SECTION=$(printf '%s\n' "$ISSUE_BODY" | awk '/### Icon name/{flag=1; next} /^### /{flag=0} flag') - # Check title and body for blocked phrases - for PHRASE in "${BLOCKED_PHRASES[@]}" - do - if echo "$ISSUE_TITLE" | grep -i "$PHRASE"; then - gh issue close ${{ github.event.issue.number }} --reason "not planned" --comment "This looks like a duplicate, use the [search](https://github.com/lucide-icons/lucide/issues?q=is%3Aissue+$PHRASE) to find similar issues. + jq -r 'to_entries[] | "\(.key) \(.value)"' brand-stopwords.json | while read -r KEY VALUE; do + SAFE_KEY=$(printf '%s\n' "$KEY" | sed 's/[][\.^$*]/\\&/g') + SAFE_VALUE=$(printf '%s\n' "$VALUE" | sed 's/[][\.^$*]/\\&/g') - Read more about brand guideline rules at [We're not accepting new Brand icons #670](https://github.com/lucide-icons/lucide/issues/670). + if echo "$ISSUE_TITLE" | grep -iqE "$SAFE_KEY|$SAFE_VALUE" || \ + { [ -n "$ICON_NAME_SECTION" ] && echo "$ICON_NAME_SECTION" | grep -iqE "$SAFE_KEY|$SAFE_VALUE"; }; then + + gh issue close ${{ github.event.issue.number }} \ + --reason "not_planned" \ + --comment "It looks like this request is about **${VALUE}**, which is a brand logo. + + Lucide **does not accept** brand logos, and we do not plan to add them in the future. This is due to a combination of **legal restrictions**, **design consistency concerns**, and **practical maintenance reasons**. + + [Click here to read our official statement about brand logos in Lucide.](./BRAND_LOGOS_STATEMENT.md) + + You can [search for similar issues.](https://github.com/lucide-icons/lucide/issues?q=is%3Aissue+${VALUE}) + + We’re always happy to help on [Discord](https://discord.gg/EH6nSts)." - Always happy to help on [Discord](https://discord.gg/EH6nSts)." gh issue lock ${{ github.event.issue.number }} --reason spam - exit 1 + exit 0 fi done - env: - GH_TOKEN: ${{ github.token }} + +env: + GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index bf474c478..7dc5c2879 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -9,4 +9,5 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: + - uses: actions/checkout@v6 - uses: actions/labeler@v5 diff --git a/.github/workflows/lint-code.yml b/.github/workflows/lint-code.yml index 51b77fb93..f2aeefc1b 100644 --- a/.github/workflows/lint-code.yml +++ b/.github/workflows/lint-code.yml @@ -2,14 +2,16 @@ name: Linting PR on: pull_request: + paths-ignore: + - icons/*.svg jobs: lint-code: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml index 67a095027..d5f7ce8a3 100644 --- a/.github/workflows/lint-pr-title.yml +++ b/.github/workflows/lint-pr-title.yml @@ -5,7 +5,6 @@ on: types: - opened - edited - - synchronize - reopened jobs: @@ -27,6 +26,7 @@ jobs: docs ci build + chore requireScope: true ignoreLabels: | bot diff --git a/.github/workflows/linting-icons.yml b/.github/workflows/linting-icons.yml index dabecbd93..fc399cd3a 100644 --- a/.github/workflows/linting-icons.yml +++ b/.github/workflows/linting-icons.yml @@ -13,66 +13,27 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 - - name: Get changed files - id: changed-files - uses: tj-actions/changed-files@v46 - with: - files: icons/* - - - name: Generate annotations - run: node ./scripts/lintFilenames.mjs - env: - CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} - - lint-contributors: - name: Lint Contributors - if: github.repository == 'lucide-icons/lucide' - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - uses: actions/checkout@v4 - - name: Get changed files - id: changed-files - uses: tj-actions/changed-files@v46 - with: - files: icons/* - - - uses: actions/setup-node@v4 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 with: node-version-file: 'package.json' - - name: Install simple-git (safer and faster than installing all deps) - run: npm install simple-git + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v46 + with: + files: icons/* - name: Generate annotations - run: node ./scripts/updateContributors.mjs + run: node ./scripts/lintFilenames.mts env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - FETCH_DEPTH: ${{ github.event.pull_request.commits }} CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} - - name: Generate annotations - env: - ANNOTATION_SEVERITY: notice - ANNOTATION_TITLE: Contributors have changed! - ANNOTATION_DESCRIPTION: Don't add people who have only performed automatic optimizations. - run: | - git diff --unified=0 -- icons/*.json | # diff icon metadata (unified=0 gives the correct chunk line number) - perl -ne '/^(\+|- |@)/ && print' | # get chunks (lines that start with "+++", "@@", "+ ", "- ") - perl -pe 's/\n/%0A/' | # url encode line breaks (\n -> %0A) - perl -pe 's/%0A(\+\+\+ b\/)/\n\1/g' | # split chunks(one chunk per line) - perl -pe "s/\+\+\+ b\/([^@]*)%0A@@ -(\d+)[^\s]* \+(\d+)[^@]*@@(.*)/::$ANNOTATION_SEVERITY file=\1,line=\2,endLine=\3,title=$ANNOTATION_TITLE::$ANNOTATION_DESCRIPTION%0A\4/" - # Example for the previous substitution - # input: +++ b/icons/accessibility.json%0A@@ -2,0 +3 @@%0A+ "contributors": ["hi"],%0A@@ -13 +14 @@%0A+}%0A - # output: ::$ANNOTATION_SEVERITY file=icons/accessibility.json,line=2,endLine=3,title=$ANNOTATION_TITLE::$ANNOTATION_DESCRIPTION%0A%0A+ "contributors": ["hi"],%0A@@ -13 +14 @@%0A+}%0A - lint-aliases: name: Check Uniqueness of Aliases runs-on: ubuntu-latest permissions: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Check Uniqueness of Aliases run: "! cat <(printf \"%s\\n\" icons/*.json | while read -r name; do basename \"$name\" .json; done) <(jq -cr 'select(.aliases) | .aliases[] | if type==\"string\" then . else .name end' icons/*.json) | sort | uniq -c | grep -ve '^\\s*1 '" diff --git a/.github/workflows/lucide-angular.yml b/.github/workflows/lucide-angular.yml index 2163314de..0812c0ea4 100644 --- a/.github/workflows/lucide-angular.yml +++ b/.github/workflows/lucide-angular.yml @@ -11,9 +11,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' @@ -27,9 +27,9 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' diff --git a/.github/workflows/lucide-astro.yml b/.github/workflows/lucide-astro.yml index 0dc46e62c..74b9043c3 100644 --- a/.github/workflows/lucide-astro.yml +++ b/.github/workflows/lucide-astro.yml @@ -12,11 +12,11 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v2 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: - node-version: 18 + node-version-file: 'package.json' cache: 'pnpm' - name: Install dependencies @@ -32,7 +32,7 @@ jobs: - uses: pnpm/action-setup@v2 - uses: actions/setup-node@v3.8.1 with: - node-version: 18 + node-version-file: 'package.json' cache: 'pnpm' - name: Install dependencies diff --git a/.github/workflows/lucide-font.yml b/.github/workflows/lucide-font.yml index 32a9e947a..19430dea3 100644 --- a/.github/workflows/lucide-font.yml +++ b/.github/workflows/lucide-font.yml @@ -3,7 +3,6 @@ name: Lucide font checks on: pull_request: paths: - - icons/** - tools/build-font/** - pnpm-lock.yaml @@ -11,9 +10,9 @@ jobs: lucide-font: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' diff --git a/.github/workflows/lucide-preact.yml b/.github/workflows/lucide-preact.yml index 644163812..1da3cf236 100644 --- a/.github/workflows/lucide-preact.yml +++ b/.github/workflows/lucide-preact.yml @@ -13,9 +13,9 @@ jobs: lucide-preact: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' diff --git a/.github/workflows/lucide-react-native.yml b/.github/workflows/lucide-react-native.yml index d645e68cf..7ed5cc039 100644 --- a/.github/workflows/lucide-react-native.yml +++ b/.github/workflows/lucide-react-native.yml @@ -13,9 +13,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' @@ -29,9 +29,9 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' diff --git a/.github/workflows/lucide-react.yml b/.github/workflows/lucide-react.yml index 0154393c0..2fe551f81 100644 --- a/.github/workflows/lucide-react.yml +++ b/.github/workflows/lucide-react.yml @@ -14,9 +14,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' @@ -30,9 +30,9 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' diff --git a/.github/workflows/lucide-shared.yml b/.github/workflows/lucide-shared.yml index 13ab83543..69e7268db 100644 --- a/.github/workflows/lucide-shared.yml +++ b/.github/workflows/lucide-shared.yml @@ -10,9 +10,9 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' diff --git a/.github/workflows/lucide-solid.yml b/.github/workflows/lucide-solid.yml index 31e0a63f0..9c5e07424 100644 --- a/.github/workflows/lucide-solid.yml +++ b/.github/workflows/lucide-solid.yml @@ -13,9 +13,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' @@ -29,9 +29,9 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' diff --git a/.github/workflows/lucide-static.yml b/.github/workflows/lucide-static.yml index 11a6d6dac..aa604eaa5 100644 --- a/.github/workflows/lucide-static.yml +++ b/.github/workflows/lucide-static.yml @@ -11,9 +11,9 @@ jobs: lucide-static: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' diff --git a/.github/workflows/lucide-svelte-5.yml b/.github/workflows/lucide-svelte-5.yml index d4ecd1333..64863d714 100644 --- a/.github/workflows/lucide-svelte-5.yml +++ b/.github/workflows/lucide-svelte-5.yml @@ -13,9 +13,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' @@ -29,9 +29,9 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' diff --git a/.github/workflows/lucide-svelte.yml b/.github/workflows/lucide-svelte.yml index 38f6a7b63..ec4d8e8e8 100644 --- a/.github/workflows/lucide-svelte.yml +++ b/.github/workflows/lucide-svelte.yml @@ -13,9 +13,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' @@ -29,9 +29,9 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' diff --git a/.github/workflows/lucide-vue-next.yml b/.github/workflows/lucide-vue-next.yml index a268536d1..eb2ff1663 100644 --- a/.github/workflows/lucide-vue-next.yml +++ b/.github/workflows/lucide-vue-next.yml @@ -13,9 +13,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' @@ -29,9 +29,9 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' diff --git a/.github/workflows/lucide.yml b/.github/workflows/lucide.yml index 6604dcc03..57fe6947d 100644 --- a/.github/workflows/lucide.yml +++ b/.github/workflows/lucide.yml @@ -12,9 +12,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' @@ -28,9 +28,9 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' diff --git a/.github/workflows/pull-request-icon-preview.yml b/.github/workflows/pull-request-icon-preview.yml index d54179f36..814673683 100644 --- a/.github/workflows/pull-request-icon-preview.yml +++ b/.github/workflows/pull-request-icon-preview.yml @@ -14,7 +14,10 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version-file: 'package.json' - name: Get changed files id: changed-files @@ -22,9 +25,9 @@ jobs: with: files: icons/*.svg - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 - name: Install svgson for code preview (safer and faster than installing all deps) - run: npm install svgson + run: npm install svgson@5.3.1 --force - name: Save PR number run: | @@ -32,7 +35,7 @@ jobs: echo ${{ github.event.number }} > ./pr/NR - name: Generate comment markup - run: node ./scripts/generateChangedIconsCommentMarkup.mjs >> ./pr/comment-markup.md + run: node ./scripts/generateChangedIconsCommentMarkup.mts >> ./pr/comment-markup.md id: comment-markup env: CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} diff --git a/.github/workflows/pull-request-tags-suggestions.yml b/.github/workflows/pull-request-tags-suggestions.yml new file mode 100644 index 000000000..141998cac --- /dev/null +++ b/.github/workflows/pull-request-tags-suggestions.yml @@ -0,0 +1,37 @@ +name: Pull request tags suggestions + +on: + pull_request_target: + paths: + - 'icons/*.json' + +jobs: + pull-request-tags-suggestions: + name: Pull Request Tags Suggestions + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + + steps: + # We checkout the main branch of main repository for security reasons. + # This is to prevent the workflow from running on a forked repository. + - uses: actions/checkout@v6 + with: + repository: lucide-icons/lucide + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v6 + with: + cache: 'pnpm' + node-version-file: 'package.json' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Generate comment markup + run: node ./scripts/suggestTags.mts + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + PULL_REQUEST_NUMBER: ${{ github.event.number }} + COMMIT_SHA: ${{ github.event.pull_request.head.sha }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f1594ce73..0b146f718 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,9 +18,13 @@ on: description: Version required: true +permissions: + id-token: write # Required for OIDC + contents: write + jobs: pre-release: - if: github.repository == 'lucide-icons/lucide' && contains('["ericfennis", "karsa-mistmere"]', github.actor) + if: github.repository == 'lucide-icons/lucide' && contains('["ericfennis", "karsa-mistmere", "jguddas"]', github.actor) runs-on: ubuntu-latest outputs: VERSION: ${{ steps.get_version.outputs.VERSION }} @@ -39,7 +43,8 @@ jobs: runs-on: ubuntu-latest needs: pre-release permissions: - id-token: write + id-token: write # Required for OIDC + contents: read strategy: fail-fast: false matrix: @@ -48,7 +53,6 @@ jobs: 'lucide', 'lucide-react', 'lucide-react-native', - 'lucide-vue', 'lucide-vue-next', 'lucide-angular', 'lucide-preact', @@ -58,9 +62,9 @@ jobs: '@lucide/svelte', ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' @@ -68,9 +72,6 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Set Auth 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 @@ -90,12 +91,14 @@ jobs: runs-on: ubuntu-latest needs: [pre-release, lucide-font] permissions: - id-token: write + id-token: write # Required for OIDC + contents: read + steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/download-artifact@v4 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' @@ -103,9 +106,6 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Set Auth Token - run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }} - - name: Set new version run: pnpm --filter lucide-static version --new-version ${{ needs.pre-release.outputs.VERSION }} --no-git-tag-version @@ -125,9 +125,9 @@ jobs: runs-on: ubuntu-latest needs: pre-release steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: 'pnpm' node-version-file: 'package.json' @@ -151,9 +151,11 @@ jobs: if: github.repository == 'lucide-icons/lucide' runs-on: ubuntu-latest needs: [pre-release, lucide-font] - + permissions: + id-token: write # Required for OIDC + contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/download-artifact@v4 - name: Zip font and icons run: | diff --git a/.github/workflows/request-review.yml b/.github/workflows/request-review.yml index f35214e38..666de1a21 100644 --- a/.github/workflows/request-review.yml +++ b/.github/workflows/request-review.yml @@ -2,7 +2,8 @@ name: 'Request Review' on: pull_request_target: types: [opened] - paths: icons/*.svg + paths: + - icons/*.svg jobs: request-review: @@ -12,7 +13,7 @@ jobs: contents: read pull-requests: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 ref: refs/pull/${{ github.event.pull_request.number }}/merge diff --git a/.github/workflows/version-up.sh b/.github/workflows/version-up.sh deleted file mode 100755 index 32892478e..000000000 --- a/.github/workflows/version-up.sh +++ /dev/null @@ -1,284 +0,0 @@ -#!/usr/bin/env bash -## Copyright (C) 2017, Oleksandr Kucherenko -## Last revisit: 2017-09-29 - -## get highest version tag for all branches -function highest_tag(){ - local TAG=$(git describe --tags `git rev-list --tags --max-count=1`) - echo "$TAG" -} - -## extract current branch name -function current_branch(){ - ## expected: heads/{branch_name} - ## expected: {branch_name} - local BRANCH=$(git rev-parse --abbrev-ref HEAD | cut -d"/" -f2) - echo "$BRANCH" -} - -## get latest/head commit hash number -function head_hash(){ - local COMMIT_HASH=$(git rev-parse --verify HEAD) - echo "$COMMIT_HASH" -} - -## extract tag commit hash code, tag name provided by argument -function tag_hash(){ - local TAG_HASH=$(git log -1 --format=format:"%H" $1 2>/dev/null | tail -n1) - echo "$TAG_HASH" -} - -## get latest revision number -function latest_revision(){ - local REV=$(git rev-list --count HEAD 2>/dev/null) - echo "$REV" -} - -## parse last found tag, extract it PARTS -function parse_last(){ - local position=$(($1-1)) - - # two parts found only - local SUBS=( ${PARTS[$position]//-/ } ) - #echo ${SUBS[@]}, size: ${#SUBS} - - # found NUMBER - PARTS[$position]=${SUBS[0]} - #echo ${PARTS[@]} - - # found SUFFIX - if [[ ${#SUBS} -ge 1 ]]; then - PARTS[4]=${SUBS[1],,} #lowercase - #echo ${PARTS[@]}, ${SUBS[@]} - fi -} - -## increment REVISION part, don't touch STAGE -function increment_revision(){ - PARTS[3]=$(( PARTS[3] + 1 )) - IS_DIRTY=1 -} - -## increment PATCH part, reset all other lower PARTS, don't touch STAGE -function increment_patch(){ - PARTS[2]=$(( PARTS[2] + 1 )) - PARTS[3]=0 - IS_DIRTY=1 -} - -## increment MINOR part, reset all other lower PARTS, don't touch STAGE -function increment_minor(){ - PARTS[1]=$(( PARTS[1] + 1 )) - PARTS[2]=0 - PARTS[3]=0 - IS_DIRTY=1 -} - -## increment MAJOR part, reset all other lower PARTS, don't touch STAGE -function incremet_major(){ - PARTS[0]="v$(( PARTS[0] + 1 ))" - PARTS[1]=0 - PARTS[2]=0 - PARTS[3]=0 - IS_DIRTY=1 -} - -## increment the number only of last found PART: REVISION --> PATCH --> MINOR. don't touch STAGE -function increment_last_found(){ - if [[ "${#PARTS[3]}" == 0 || "${PARTS[3]}" == "0" ]]; then - if [[ "${#PARTS[2]}" == 0 || "${PARTS[2]}" == "0" ]]; then - increment_minor - else - increment_patch - fi - else - increment_revision - fi - - # stage part is not EMPTY - if [[ "${#PARTS[4]}" != 0 ]]; then - IS_SHIFT=1 - fi -} - -## compose version from PARTS -function compose(){ - MAJOR="${PARTS[0]}" - MINOR=".${PARTS[1]}" - PATCH=".${PARTS[2]}" - REVISION=".${PARTS[3]}" - SUFFIX="-${PARTS[4]}" - - if [[ "${#PATCH}" == 1 ]]; then # if empty {PATCH} - PATCH="" - fi - - if [[ "${#REVISION}" == 1 ]]; then # if empty {REVISION} - REVISION="" - fi - - if [[ "${PARTS[3]}" == "0" ]]; then # if revision is ZERO - REVISION="" - fi - - # shrink patch and revision - if [[ -z "${REVISION// }" ]]; then - if [[ "${PARTS[2]}" == "0" ]]; then - PATCH=".0" - fi - else # revision is not EMPTY - if [[ "${#PATCH}" == 0 ]]; then - PATCH=".0" - fi - fi - - # remove suffix if we don't have a alpha/beta/rc - if [[ "${#SUFFIX}" == 1 ]]; then - SUFFIX="" - fi - - - echo "${MAJOR}${MINOR}${PATCH}${REVISION}${SUFFIX}" #full format -} - -# initial version used for repository without tags -INIT_VERSION=0.0.0.0-alpha - -# do GIT data extracting -TAG=$(highest_tag) -REVISION=$(latest_revision) -BRANCH=$(current_branch) -TAG_HASH=$(tag_hash $TAG) -HEAD_HASH=$(head_hash) - -# if tag and branch commit hashes are different, than print info about that -#echo $HEAD_HASH vs $TAG_HASH -if [[ "$@" == "" ]]; then - if [[ "$TAG_HASH" == "$HEAD_HASH" ]]; then - echo "Tag $TAG and HEAD are aligned. We will stay on the TAG version." - echo "" - NO_ARGS_VALUE='--stay' - else - PATTERN="^[0-9]+.[0-9]+(.[0-9]+)*(-(alpha|beta|rc))*$" - - if [[ "$BRANCH" =~ $PATTERN ]]; then - echo "Detected version branch '$BRANCH'. We will auto-increment the last version PART." - echo "" - NO_ARGS_VALUE='--default' - else - echo "Detected branch name '$BRANCH' than does not match version pattern. We will increase MINOR." - echo "" - NO_ARGS_VALUE='--minor' - fi - fi -fi - -# -# {MAJOR}.{MINOR}[.{PATCH}[.{REVISION}][-(.*)] -# -# Suffix: alpha, beta, rc -# No Suffix --> {NEW_VERSION}-alpha -# alpha --> beta -# beta --> rc -# rc --> {VERSION} -# -PARTS=( ${TAG//./ } ) -parse_last ${#PARTS[@]} # array size as argument -#echo ${PARTS[@]} - -# if no parameters than emulate --default parameter -if [[ "$@" == "" ]]; then - set -- $NO_ARGS_VALUE -fi - -# parse input parameters -for i in "$@" -do - key="$i" - - case $key in - -a|--alpha) # switched to ALPHA - PARTS[4]="alpha" - IS_SHIFT=1 - ;; - -b|--beta) # switched to BETA - PARTS[4]="beta" - IS_SHIFT=1 - ;; - -c|--release-candidate) # switched to RC - PARTS[4]="rc" - IS_SHIFT=1 - ;; - -r|--release) # switched to RELEASE - PARTS[4]="" - IS_SHIFT=1 - ;; - -p|--patch) # increment of PATCH - increment_patch - ;; - -e|--revision) # increment of REVISION - increment_revision - ;; - -g|--git-revision) # use git revision number as a revision part§ - PARTS[3]=$(( REVISION )) - IS_DIRTY=1 - ;; - -i|--minor) # increment of MINOR by default - increment_minor - ;; - --default) # stay on the same stage, but increment only last found PART of version code - increment_last_found - ;; - -m|--major) # increment of MAJOR - incremet_major - ;; - -s|--stay) # extract version info - IS_DIRTY=1 - NO_APPLY_MSG=1 - ;; - -t|--tag-only) # extract version info - TAG_ONLY=1 - ;; - --apply) - DO_APPLY=1 - ;; - -h|--help) - help - ;; - esac - shift -done - -# detected shift, but no increment -if [[ "$IS_SHIFT" == "1" ]]; then - # temporary disable stage shift - stage=${PARTS[4]} - PARTS[4]='' - - # detect first run on repository, INIT_VERSION was used - if [[ "$(compose)" == "0.0" ]]; then - increment_minor - fi - - PARTS[4]=$stage -fi - -# no increment applied yet and no shift of state, do minor increase -if [[ "$IS_DIRTY$IS_SHIFT" == "" ]]; then - increment_minor -fi - -compose - -# is proposed tag in conflict with any other TAG -PROPOSED_HASH=$(tag_hash $(compose)) -if [[ "${#PROPOSED_HASH}" -gt 0 && "$NO_APPLY_MSG" == "" ]]; then - echo -e "\033[31mERROR:\033[0m " - echo -e "\033[31mERROR:\033[0m Found conflict with existing tag \033[32m$(compose)\033[0m / $PROPOSED_HASH" - echo -e "\033[31mERROR:\033[0m Only manual resolving is possible now." - echo -e "\033[31mERROR:\033[0m " - echo -e "\033[31mERROR:\033[0m To Resolve try to add --revision or --patch modifier." - echo -e "\033[31mERROR:\033[0m " - echo "" - exit 1 -fi diff --git a/.gitignore b/.gitignore index e2098e23e..41a9f0839 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ .obsidian .now .idea +.env node_modules dist build @@ -43,7 +44,7 @@ docs/.vitepress/data/releaseMetaData docs/.vitepress/data/categoriesData.json docs/.vitepress/data/iconDetails docs/.vitepress/data/relatedIcons.json +docs/.vitepress/data/brandStopwords.json docs/.vercel docs/.nitro .gitignore - diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 3e775efb0..000000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -auto-install-peers=true diff --git a/.vscode/svg.code-snippets b/.vscode/svg.code-snippets index 52cbb23c1..dbf726e6b 100644 --- a/.vscode/svg.code-snippets +++ b/.vscode/svg.code-snippets @@ -1,4 +1,5 @@ { + "$schema": "https://raw.githubusercontent.com/Yash-Singh1/vscode-snippets-json-schema/main/schema.json", "Lucide SVG": { "scope": "xml", "description": "Base SVG with Lucide attributes.", @@ -51,6 +52,16 @@ ], "body": "" }, + "Squircle": { + "scope": "xml", + "description": "SVG `path` with Lucide defaults.", + "prefix": [ + "squircle", + "path", + "" + }, "Ellipse": { "scope": "xml", "description": "SVG `ellipse`.", diff --git a/BRAND_LOGOS_STATEMENT.md b/BRAND_LOGOS_STATEMENT.md new file mode 100644 index 000000000..9ca7c149b --- /dev/null +++ b/BRAND_LOGOS_STATEMENT.md @@ -0,0 +1,71 @@ +# Our Official Stance on Including Brand Logos in Lucide + +## TL;DR + +Lucide **does not accept** brand logos, and we do not plan to add them in the future. + +This is due to a combination of **legal restrictions**, **design consistency concerns**, and **practical maintenance reasons**. + +If you need brand logos, we recommend [Simple Icons](https://simpleicons.org/), which provides an extensive, legally safer collection of brand marks. + +--- + +## 1. Historical Context + +This is not a new debate — other icon sets have gone through the same discussion: + +- **Material Design Icons** [deprecated all brand icons](https://github.com/Templarian/MaterialDesign/issues/6602) because they didn't fit the style, didn't work well in one color, and often looked out of place in a 24×24px grid. +- **Feather Icons** [came to the same conclusion](https://github.com/feathericons/feather/issues/763): brand logos have their own style, and forcing them into another inevitably leads to aesthetic compromises. +- **Lucide** learned from these examples — we'd rather focus on making a consistent set of non-brand icons that all work together. + +## 2. Legal Considerations + +Most brand logos: +- Are **protected by trademark or copyright**. +- Have **strict rules** for how they can be used (colors, spacing, proportions, etc.). +- **Don't allow modification** — but we'd have to change them to fit Lucide's style. + +This means adding them could: +1. Break copyright or trademark law. +2. Make both you and the Lucide project legally responsible. +3. Force us to review every new request one by one for legal issues — something we simply can't do. + +> **Note:** Simple Icons avoids this by keeping logos exactly as the brand provides them — though even they sometimes face [legal challenges](https://github.com/simple-icons/simple-icons/issues/11236). + +## 3. Design & Consistency + +Lucide is all about **visual consistency**. + +Adding brand logos would: +- Break [our own design rules](https://lucide.dev/guide/design/icon-design-guide#icon-design-principles) for shapes, proportions, and stroke. +- Mix two fundamentally different categories of graphics (pictograms vs. corporate logos). +- Create a library where a subset of icons will always look "out of place". + +If the logos are not in Lucide's style, why include them in Lucide at all? Better to use them from a dedicated brand icon source. + +## 4. Maintenance Burden + +Even with our current **"no brand icon requests"** policy, people still request them regularly. + +Having any brand icons in the set: +- Makes people think we might add more in the future. +- Leads to repeated requests and the same conversations over and over. +- Wastes maintainer time redirecting people to the same explanation. + +Removing them entirely solves this problem. + +## 5. Recommended Alternatives + +If you need brand icons, try: +- [Simple Icons](https://simpleicons.org/): offers a huge range of brands, in consistent SVG format, using a 24×24 viewBox, the same as ours. +- Official brand asset pages: most major companies provide downloadable SVGs. + +You can use these alongside Lucide without bloating our core library. + +## Final Words + +Lucide is an **icon** set, not a **logo** set. + +Logos belong in dedicated logo resources. + +We're focusing on what Lucide does best: providing a clean, cohesive, and legally safe collection of open-source icons. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ffdbe3e48..898485681 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,8 +16,8 @@ Guidelines for pull requests: - __Make your commit messages as descriptive as possible.__ Include as much information as you can. Explain anything that the file diffs themselves won’t make apparent. - __Document your pull request__. Explain your fix, link to the relevant issue, add screenshots when adding new icons. -- __Make sure the target of your pull request is the relevant branch__. Most of bugfix or new feature should go to the `main` branch. -- __Include only related work__. If your pull request has unrelated commit, it won't be accepted. +- __Make sure the target of your pull request is the relevant branch__. Most of bug fixes or new feature should go to the `main` branch. +- __Include only related work__. If your pull request has unrelated commits, it won't be accepted. ### Icon Pull Requests @@ -47,10 +47,10 @@ You can also [download an Adobe Illustrator template](https://github.com/lucide- #### Submitting Multiple Icons -If you want submit multiple icons, please separate the icons and group them. That makes reviewing the icons easier and keep the thread clean and scoped. -So don't submit multiple icons in one PR that have noting to do with each other. +If you want to submit multiple icons, please separate the icons and group them. That makes reviewing the icons easier and keeps the thread clean and scoped. +So don't submit multiple icons in one PR that have nothing to do with each other. So for example don't create one PR with icons: `arrow-up`, `bicycle`, `arrow-down`. -Seperate them by two PRs; 'pr-01' `arrow`, `arrow-down` and 'pr-02' `bicycle`. +Separate them into two PRs; 'pr-01' `arrow`, `arrow-down` and 'pr-02' `bicycle`. ## Icon Requests @@ -131,7 +131,7 @@ When adding new features to for example the icon component for a framework. It i ### Local Testing -To test changes in a local project, you can use `yarn link`, `npm link`, `bun link` or `pnpm link` to link the package. Before you do this make sure you builded the package first. +To test changes in a local project, you can use `yarn link`, `npm link`, `bun link` or `pnpm link` to link the package. Before you do this make sure you've built the package first. ```sh # in packages/lucide-react @@ -161,6 +161,30 @@ lucide The lucide.dev website is using [vitepress](https://vitepress.dev/) to generate the static website. The markdown files are located in the docs directory. +#### Running the Docs Website Locally + +To test the docs website locally, follow these steps: + +1. **Navigate to the docs directory** + +```sh +cd docs +``` + +2. **Start the Local Development Server** + +```sh + pnpm run docs:dev +``` + +3. **Open the Website Locally** + +Vitepress should open with the following format: + +VitePress dev server is running at: +- **Local**: `http://localhost:3000/` +- **Network**: `http://192.168.x.x:3000/` + ### Guides Detailed documentation about: installation, guides, packages, design guides etc. @@ -173,11 +197,9 @@ All the icons of lucide in SVG format. These will be used as source for all the Includes all the (npm) packages of lucide. -> Note: One package is not managed by pnpm: **lucide-flutter**, this package is written in Dart and uses pub for publishing. - ### Scripts -Includes usefully scripts to automate certain jobs. Big part of the scripts is the template generation, for example it generates icon components for all the packages. These scripts are usually executed from the "scripts" section in the package.json. +Includes useful scripts to automate certain jobs. Big part of the scripts is the template generation, for example it generates icon components for all the packages. These scripts are usually executed from the "scripts" section in the package.json. ## Documentation diff --git a/LICENSE b/LICENSE index 325e8ff0a..46e696218 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ ISC License -Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022. +Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2025. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -13,3 +13,27 @@ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +The MIT License (MIT) (for portions derived from Feather) + +Copyright (c) 2013-2023 Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 569f512fa..1d2ea701c 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,12 @@ For more info on how to contribute please see the [contribution guidelines](http Caught a mistake or want to contribute to the documentation? [Edit this page on Github](https://github.com/lucide-icons/lucide/blob/main/README.md) +## About brand logos + +Lucide **does not accept** brand logos, and we do not plan to add them in the future. This is due to a combination of **legal restrictions**, **design consistency concerns**, and **practical maintenance reasons**. + +[Click here to read our official statement about brand logos in Lucide.](./BRAND_LOGOS_STATEMENT.md) + ## Community Join the community on our [Discord](https://discord.gg/EH6nSts) server! @@ -84,5 +90,5 @@ Thank you to all the people who contributed to Lucide! ### Awesome backers 🍺 -Scipress sponsor badge pdfme sponsor badge +Fina Money sponsor badge diff --git a/brand-stopwords.json b/brand-stopwords.json new file mode 100644 index 000000000..be5cb329a --- /dev/null +++ b/brand-stopwords.json @@ -0,0 +1,149 @@ +{ + "adobe": "Adobe", + "airplay": "AirPlay", + "amazon": "Amazon", + "angular": "Angular", + "aws": "AWS", + "azure": "Azure", + "bandcamp": "Bandcamp", + "behance": "Behance", + "bitbucket": "Bitbucket", + "blender": "Blender", + "bluesky": "BlueSky", + "bootstrap": "Bootstrap", + "brave": "Brave", + "chakra": "Chakra UI", + "chrome": "Chrome", + "codepen": "Codepen", + "codesandbox": "CodeSandbox", + "csharp": "C#", + "cypress": "Cypress", + "dart": "Dart", + "deezer": "Deezer", + "deno": "Deno", + "discord": "Discord", + "docker": "Docker", + "dribbble": "Dribbble", + "dropbox": "Dropbox", + "edge": "Edge", + "ember": "Ember", + "epic": "Epic Games", + "erlang": "Erlang", + "esbuild": "esbuild", + "eslint": "ESLint", + "facebook": "Facebook", + "figjam": "FigJam", + "figma": "Figma", + "firebase": "Firebase", + "firefox": "Firefox", + "framer": "Framer", + "gatsby": "Gatsby", + "gcp": "Google Cloud", + "github": "GitHub", + "gitlab": "GitLab", + "golang": "GoLang", + "google": "Google", + "gmail": "Gmail", + "gravatar": "Gravatar", + "haskell": "Haskell", + "instagram": "Instagram", + "java": "Java", + "javascript": "JavaScript", + "jest": "Jest", + "jira": "Jira", + "kotlin": "Kotlin", + "kubernetes": "Kubernetes", + "less": "Less", + "leetcode": "LeetCode", + "leet-code": "LeetCode", + "line": "LINE", + "linkedin": "LinkedIn", + "lua": "Lua", + "mariadb": "MariaDB", + "mcp": "MCP", + "messenger": "Messenger", + "microsoft": "Microsoft", + "mongodb": "MongoDB", + "mui": "Material UI", + "mysql": "MySQL", + "nestjs": "NestJS", + "netflix": "Netflix", + "netlify": "Netlify", + "next": "Next.js", + "nodejs": "Node.js", + "notion": "Notion", + "nostr": "Nostr", + "npm": "npm", + "nuxt": "Nuxt", + "opera": "Opera", + "oracle": "Oracle", + "patreon": "Patreon", + "paypal": "PayPal", + "perl": "Perl", + "php": "PHP", + "pinterest": "Pinterest", + "pix": "PiX", + "playstation": "PlayStation", + "playwright": "Playwright", + "pnpm": "pnpm", + "postcss": "PostCSS", + "postgresql": "PostgreSQL", + "prettier": "Prettier", + "prisma": "Prisma", + "python": "Python", + "qwik": "Qwik", + "react": "React", + "reddit": "Reddit", + "redis": "Redis", + "rollup": "Rollup", + "rust": "Rust", + "safari": "Safari", + "sass": "Sass", + "scala": "Scala", + "scss": "Sass", + "semantic": "Semantic UI", + "shopify": "Shopify", + "skype": "Skype", + "slack": "Slack", + "solid": "SolidJS", + "soundcloud": "SoundCloud", + "spotify": "Spotify", + "sqlite": "SQLite", + "squarespace": "Squarespace", + "steam": "Steam", + "stripe": "Stripe", + "substack": "Substack", + "supabase": "Supabase", + "surge": "Surge", + "svelte": "Svelte", + "swift": "Swift", + "tailwind": "Tailwind CSS", + "telegram": "Telegram", + "terraform": "Terraform", + "tesla": "Tesla", + "tidal": "Tidal", + "tiktok": "TikTok", + "trello": "Trello", + "twitch": "Twitch", + "twitter": "Twitter", + "typescript": "TypeScript", + "unity": "Unity", + "unreal": "Unreal Engine", + "vercel": "Vercel", + "vimeo": "Vimeo", + "vite": "Vite", + "vitest": "Vitest", + "vue": "Vue", + "webpack": "Webpack", + "wechat": "WeChat", + "whatsapp": "WhatsApp", + "windows": "Windows", + "wix": "Wix", + "x.com": "X.com", + "x-social": "X.com", + "xbox": "Xbox", + "yarn": "Yarn", + "youtube": "YouTube", + "zig": "Zig", + "zoom": "Zoom" +} diff --git a/categories/navigation.json b/categories/navigation.json index 0cb9e209d..86a3e5503 100644 --- a/categories/navigation.json +++ b/categories/navigation.json @@ -1,5 +1,5 @@ { "$schema": "../category.schema.json", - "title": "Navigation", + "title": "Navigation, Maps, and POIs", "icon": "compass" } diff --git a/docs/.vitepress/api/gh-icon/[...data].get.ts b/docs/.vitepress/api/gh-icon/[...data].get.ts index def248303..fc3505cce 100644 --- a/docs/.vitepress/api/gh-icon/[...data].get.ts +++ b/docs/.vitepress/api/gh-icon/[...data].get.ts @@ -13,10 +13,12 @@ export default eventHandler((event) => { const data = pathData.at(-1).slice(0, -4); const [name] = pathData; - const src = Buffer.from(data, 'base64') - .toString('utf8') - .replaceAll('\n', '') - .replace(/]*>|<\/svg>/g, ''); + const src = Buffer.from(data, 'base64').toString('utf8').replaceAll('\n', ''); + + const width = parseInt((src.includes(' { children.push( createElement(Backdrop, { backdropString, - src, + src: src.replace(/]*>|<\/svg>/g, ''), color: '#777', }), ); @@ -46,7 +48,18 @@ export default eventHandler((event) => { const svg = Buffer.from( // We can't use jsx here, is not supported here by nitro. - renderToString(createElement(SvgPreview, { src, showGrid: true }, children)), + renderToString( + createElement( + SvgPreview, + { + src: src.replace(/]*>|<\/svg>/g, ''), + height, + width, + showGrid: true, + }, + children, + ), + ), ).toString('utf8'); defaultContentType(event, 'image/svg+xml'); diff --git a/docs/.vitepress/api/gh-icon/diff/[...data].get.ts b/docs/.vitepress/api/gh-icon/diff/[...data].get.ts index b1b0f09c6..a2892269f 100644 --- a/docs/.vitepress/api/gh-icon/diff/[...data].get.ts +++ b/docs/.vitepress/api/gh-icon/diff/[...data].get.ts @@ -17,6 +17,13 @@ export default eventHandler((event) => { .replaceAll('\n', '') .replace(/]*>|<\/svg>/g, ''); + const width = parseInt( + (newSrc.includes(' { const svg = Buffer.from( // We can't use jsx here, is not supported here by nitro. - renderToString(createElement(Diff, { oldSrc, newSrc, showGrid: true }, children)), + renderToString( + createElement(Diff, { oldSrc, newSrc, showGrid: true, height, width }, children), + ), ).toString('utf8'); defaultContentType(event, 'image/svg+xml'); diff --git a/docs/.vitepress/api/gh-icon/dpi/[...data].get.ts b/docs/.vitepress/api/gh-icon/dpi/[...data].get.ts index 2e5d4ebe4..69799a27c 100644 --- a/docs/.vitepress/api/gh-icon/dpi/[...data].get.ts +++ b/docs/.vitepress/api/gh-icon/dpi/[...data].get.ts @@ -1,6 +1,10 @@ import { eventHandler, setResponseHeader, defaultContentType } from 'h3'; import { Resvg, initWasm } from '@resvg/resvg-wasm'; +import iconNodes from '../../../data/iconNodes'; import wasm from './loadWasm'; +import { createElement } from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; +import createLucideIcon from 'lucide-react/src/createLucideIcon'; var initializedResvg = initWasm(wasm); @@ -9,27 +13,37 @@ export default eventHandler(async (event) => { await initializedResvg; const imageSize = 96; - const [iconSizeString, svgData] = params.data.split('/'); + const name = params.data.split('/').at(-3); + const iconSizeString = params.data.split('/').at(-2); + const svgData = params.data.split('/').at(-1); const iconSize = parseInt(iconSizeString, 10); const data = svgData.slice(0, -4); + const prevSvg = iconNodes[name] + ? renderToStaticMarkup(createElement(createLucideIcon(name, iconNodes[name]))) + : undefined; + const src = Buffer.from(data, 'base64').toString('utf8'); const svg = (src.includes('${src}`) .replace(/(\r\n|\n|\r)/gm, '') .replace( - /]*/, + /]*>/, ` + ${prevSvg?.replaceAll('\n', '').replace(/]*>|<\/svg>/g, '')} + `, - ); + ) + .replace(/<\/svg>/, ''); const resvg = new Resvg(svg, { background: '#000' }); const pngData = resvg.render(); @@ -39,7 +53,7 @@ export default eventHandler(async (event) => { setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000'); return ` - + {showGrid && ( @@ -385,6 +442,12 @@ const SvgPreview = React.forwardRef< radius={1} strokeOpacity={0.15} /> + { return []; }; -export const getNodes = (src: string) => - extractNodes(parseSync(src.includes('${src}`)); +export const getNodes = memoize((src: string) => + extractNodes(parseSync(src.includes('${src}`)), +); export const getCommands = (src: string) => getNodes(src) .map(convertToPathNode) .flatMap(({ d, name }, idx) => - new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })), + new SVGPathData(d) + .toAbs() + // @ts-ignore + .commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })), ); -export const getPaths = (src: string) => { +const getPaths = (src: string) => { const commands = getCommands(src.includes('${src}`); const paths: Path[] = []; let prev: Point | undefined = undefined; @@ -237,6 +244,7 @@ export const getPaths = (src: string) => { break; } default: { + // @ts-ignore assertNever(c); } } @@ -244,7 +252,7 @@ export const getPaths = (src: string) => { return paths; }; -export const arcEllipseCenter = ( +const arcEllipseCenter = ( x1: number, y1: number, rx: number, @@ -296,5 +304,52 @@ export const arcEllipseCenter = ( M2[1][0] * Cp[0] + M2[1][1] * Cp[1] + V3[1], ]; - return { x: C[0], y: C[1] }; + return { + x: C[0], + y: C[1], + tangentIntersection: intersectTangents( + { x: x1, y: y1 }, + { x: x2, y: y2 }, + { x: C[0], y: C[1] }, + ), + }; }; + +function getTangentDirection(p: Point, center: Point): Point { + // Tangent is perpendicular to the radius vector (rotate radius 90°) + const dx = p.x - center.x; + const dy = p.y - center.y; + return { x: -dy, y: dx }; // 90° rotation +} + +function intersectTangents(start: Point, end: Point, center: Point): Point | null { + const t1 = getTangentDirection(start, center); + const t2 = getTangentDirection(end, center); + + // Solve: start + λ * t1 = end + μ * t2 + const A = [ + [t1.x, -t2.x], + [t1.y, -t2.y], + ]; + const b = [end.x - start.x, end.y - start.y]; + + // Compute determinant + const det = A[0][0] * A[1][1] - A[0][1] * A[1][0]; + + if (Math.abs(det) < 1e-10) { + // Lines are parallel, no intersection + return null; + } + + const invDet = 1 / det; + + const lambda = (b[0] * A[1][1] - b[1] * A[0][1]) * invDet; + + // Intersection point = start + lambda * t1 + return { + x: start.x + lambda * t1.x, + y: start.y + lambda * t1.y, + }; +} + +export default memoize(getPaths); diff --git a/docs/.vitepress/lib/codeExamples/createCodeExamples.ts b/docs/.vitepress/lib/codeExamples/createCodeExamples.ts index 7848841c2..eb6313431 100644 --- a/docs/.vitepress/lib/codeExamples/createCodeExamples.ts +++ b/docs/.vitepress/lib/codeExamples/createCodeExamples.ts @@ -1,5 +1,4 @@ -import { bundledLanguages, type ThemeRegistration } from 'shikiji'; -import { getHighlighter } from 'shikiji'; +import { bundledLanguages, createHighlighter, type ThemeRegistration } from 'shiki'; type CodeExampleType = { title: string; @@ -10,14 +9,20 @@ type CodeExampleType = { const getIconCodes = (): CodeExampleType => { return [ { - language: 'js', + language: 'html', title: 'Vanilla', code: `\ -import { createIcons, icons } from 'lucide'; + -document.body.append('');\ +\ `, }, { @@ -102,13 +107,8 @@ import { LucideAngularModule, $PascalCase } from 'lucide-angular'; }, { language: 'html', - title: 'Icon Font', - code: ` - -
-`, + title: 'Icon font', + code: `
`, }, ]; }; @@ -118,7 +118,7 @@ export type ThemeOptions = | { light: ThemeRegistration; dark: ThemeRegistration }; const highLightCode = async (code: string, lang: string, active?: boolean) => { - const highlighter = await getHighlighter({ + const highlighter = await createHighlighter({ themes: ['github-light', 'github-dark'], langs: Object.keys(bundledLanguages), }); diff --git a/docs/.vitepress/lib/codeExamples/createLabCodeExamples.ts b/docs/.vitepress/lib/codeExamples/createLabCodeExamples.ts index a996d517e..a38772523 100644 --- a/docs/.vitepress/lib/codeExamples/createLabCodeExamples.ts +++ b/docs/.vitepress/lib/codeExamples/createLabCodeExamples.ts @@ -1,5 +1,5 @@ -import { bundledLanguages, type ThemeRegistration } from 'shikiji'; -import { getHighlighter } from 'shikiji'; +import { bundledLanguages, type ThemeRegistration } from 'shiki'; +import { createHighlighter } from 'shiki'; type CodeExampleType = { title: string; @@ -10,10 +10,11 @@ type CodeExampleType = { const getIconCodes = (): CodeExampleType => { return [ { - language: 'js', + language: 'html', title: 'Vanilla', code: `\ -import { createIcons, icons } from 'lucide'; + -document.body.append('');\ +\ `, }, { @@ -119,7 +121,7 @@ export type ThemeOptions = | { light: ThemeRegistration; dark: ThemeRegistration }; const highLightCode = async (code: string, lang: string, active?: boolean) => { - const highlighter = await getHighlighter({ + const highlighter = await createHighlighter({ themes: ['github-light', 'github-dark'], langs: Object.keys(bundledLanguages), }); diff --git a/docs/.vitepress/lib/codeExamples/highLightCode.ts b/docs/.vitepress/lib/codeExamples/highLightCode.ts index ed51ce21b..2376d1432 100644 --- a/docs/.vitepress/lib/codeExamples/highLightCode.ts +++ b/docs/.vitepress/lib/codeExamples/highLightCode.ts @@ -1,12 +1,12 @@ -import { bundledLanguages, type ThemeRegistration } from 'shikiji'; -import { getHighlighter } from 'shikiji'; +import { bundledLanguages, type ThemeRegistration } from 'shiki'; +import { createHighlighter } from 'shiki'; export type ThemeOptions = | ThemeRegistration | { light: ThemeRegistration; dark: ThemeRegistration }; const highLightCode = async (code: string, lang: string, active?: boolean) => { - const highlighter = await getHighlighter({ + const highlighter = await createHighlighter({ themes: ['github-light', 'github-dark'], langs: Object.keys(bundledLanguages), }); diff --git a/docs/.vitepress/lib/getFallbackZip.tsx b/docs/.vitepress/lib/getFallbackZip.tsx index 9760697e9..6f776c47b 100644 --- a/docs/.vitepress/lib/getFallbackZip.tsx +++ b/docs/.vitepress/lib/getFallbackZip.tsx @@ -1,5 +1,5 @@ import { createLucideIcon } from 'lucide-react/src/lucide-react'; -import { type LucideProps, type IconNode } from 'lucide-react/src/createLucideIcon'; +import { type LucideProps, type IconNode } from 'lucide-react/src/types'; import { IconEntity } from '../theme/types'; import { renderToStaticMarkup } from 'react-dom/server'; import { IconContent } from './generateZip'; diff --git a/docs/.vitepress/theme/components/base/ColorPicker.vue b/docs/.vitepress/theme/components/base/ColorPicker.vue index 40ded8f48..6ba8b230e 100644 --- a/docs/.vitepress/theme/components/base/ColorPicker.vue +++ b/docs/.vitepress/theme/components/base/ColorPicker.vue @@ -1,23 +1,31 @@ @@ -45,19 +54,21 @@ const value = computed({ top: -5px; left: -5px; } + .color-input-wrapper { height: 24px; width: 24px; overflow: hidden; position: relative; - border-radius: 12px; + border-radius: 4px; flex-shrink: 0; } + .color-picker { background: var(--color-picker-bg, var(--vp-c-bg-alt)); border-radius: 8px; color: var(--vp-c-text-2); - padding: 4px 8px; + padding: 3px 8px 3px 3px; height: auto; font-size: 14px; text-align: left; @@ -66,6 +77,10 @@ const value = computed({ display: flex; align-items: center; gap: 2px; + transition: + color 0.25s, + border-color 0.25s, + background-color 0.25s; } .color-input-text { @@ -79,15 +94,18 @@ const value = computed({ text-align: left; border-radius: 8px; cursor: text; - transition: border-color 0.25s, background 0.4s ease; + transition: + border-color 0.25s, + background 0.4s ease; + letter-spacing: 1px; } -.color-picker:hover, .color-picker:focus { +.color-picker:hover, +.color-picker:focus { border-color: var(--vp-c-brand); background: var(--vp-c-bg-alt); } -.color-input[value="currentColor"] { - +.color-input[value='currentColor'] { } diff --git a/docs/.vitepress/theme/components/base/FakeInput.vue b/docs/.vitepress/theme/components/base/FakeInput.vue index 613a48aca..d86621d65 100644 --- a/docs/.vitepress/theme/components/base/FakeInput.vue +++ b/docs/.vitepress/theme/components/base/FakeInput.vue @@ -1,14 +1,27 @@ @@ -26,11 +39,34 @@ const SearchIcon = createLucideIcon('search', search) cursor: text; display: flex; gap: 12px; - transition: color 0.25s, border-color 0.25s, background-color 0.25s; + transition: + color 0.25s, + border-color 0.25s, + background-color 0.25s; } -.fake-input:hover, .fake-input:focus { +.fake-input:hover, +.fake-input:focus { border-color: var(--vp-c-brand); background: var(--vp-c-bg-alt); } + +.shortcut { + margin-left: auto; + padding: 2px 6px; + font-size: 12px; + font-family: inherit; + font-weight: 500; + line-height: 1.5; + color: var(--vp-c-text-3); + background: var(--vp-c-default-soft); + border: 1px solid var(--vp-c-divider); + border-radius: 4px; +} + +@media (hover: none) { + .shortcut { + display: none; + } +} diff --git a/docs/.vitepress/theme/components/base/IconButton.vue b/docs/.vitepress/theme/components/base/IconButton.vue index f3ac32b1f..7006803ff 100644 --- a/docs/.vitepress/theme/components/base/IconButton.vue +++ b/docs/.vitepress/theme/components/base/IconButton.vue @@ -5,7 +5,6 @@ diff --git a/docs/.vitepress/theme/components/base/ResetButton.vue b/docs/.vitepress/theme/components/base/ResetButton.vue index 5b94aa669..7709ae985 100644 --- a/docs/.vitepress/theme/components/base/ResetButton.vue +++ b/docs/.vitepress/theme/components/base/ResetButton.vue @@ -1,14 +1,15 @@ @@ -32,6 +33,7 @@ const RotateIcon = createLucideIcon('RotateIcon', rotateCw) 0% { transform: rotate(0deg); } + 100% { transform: rotate(359deg); } diff --git a/docs/.vitepress/theme/components/base/Switch.vue b/docs/.vitepress/theme/components/base/Switch.vue index c327fcf9a..7902ef396 100644 --- a/docs/.vitepress/theme/components/base/Switch.vue +++ b/docs/.vitepress/theme/components/base/Switch.vue @@ -1,15 +1,22 @@