mirror of
https://github.com/lucide-icons/lucide.git
synced 2026-11-11 13:28:19 +00:00
Compare commits
57
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15ae176502 | ||
|
|
a880511857 | ||
|
|
d9d1cbfe08 | ||
|
|
fc20eb6c7a | ||
|
|
d850228c3c | ||
|
|
f6cef23cd7 | ||
|
|
766c98af22 | ||
|
|
1f0f070e84 | ||
|
|
a90271b282 | ||
|
|
c81680e066 | ||
|
|
0c57f7265a | ||
|
|
f917f247e3 | ||
|
|
aef0f98a68 | ||
|
|
6e5c250af0 | ||
|
|
f37f235d78 | ||
|
|
b973e42174 | ||
|
|
ce6fb3dd6c | ||
|
|
c3262987be | ||
|
|
52ee7b3692 | ||
|
|
04ee825d6b | ||
|
|
0241b57c35 | ||
|
|
24dd855b87 | ||
|
|
4e5fce4adc | ||
|
|
ae94b5118b | ||
|
|
2dd4be802a | ||
|
|
f4fa0512d3 | ||
|
|
d1150cea7a | ||
|
|
4afccc1842 | ||
|
|
4e161a15ef | ||
|
|
76175250cd | ||
|
|
2e58e319e7 | ||
|
|
283e4b2805 | ||
|
|
db63e66333 | ||
|
|
4b3521a3e3 | ||
|
|
f664af1933 | ||
|
|
03ad294db0 | ||
|
|
a2ec77fc7f | ||
|
|
602bca62a2 | ||
|
|
423afc6d03 | ||
|
|
374398b58d | ||
|
|
6c2b8e6fa0 | ||
|
|
ecc612044e | ||
|
|
896aba0d19 | ||
|
|
45740bef8b | ||
|
|
84ab8524b5 | ||
|
|
071e4d2738 | ||
|
|
3c9d1a56a4 | ||
|
|
c20dd8464d | ||
|
|
29af709846 | ||
|
|
410dfc3577 | ||
|
|
574e156701 | ||
|
|
22cfe96931 | ||
|
|
1475fc02e0 | ||
|
|
7f14e74e4d | ||
|
|
5b40f2c5a7 | ||
|
|
bac39e6c53 | ||
|
|
523026ac18 |
@@ -13,21 +13,19 @@ body:
|
||||
description: Which Lucide packages are affected? You may select more than one.
|
||||
options:
|
||||
- label: lucide
|
||||
- label: lucide-angular (old version)
|
||||
- label: '@lucide/angular (new version)'
|
||||
- label: '@lucide/astro'
|
||||
- label: lucide-flutter
|
||||
- label: lucide-preact
|
||||
- label: lucide-react
|
||||
- label: '@lucide/vue'
|
||||
- label: '@lucide/svelte'
|
||||
- label: '@lucide/angular'
|
||||
- label: '@lucide/astro'
|
||||
- label: lucide-react-native
|
||||
- label: lucide-preact
|
||||
- label: lucide-solid
|
||||
- label: lucide-static
|
||||
- label: lucide-svelte (old version)
|
||||
- label: '@lucide/svelte (new version)'
|
||||
- label: lucide-vue
|
||||
- label: lucide-vue-next
|
||||
- label: lucide-astro
|
||||
- label: '@lucide/icons'
|
||||
- label: lucide-vue-next (deprecated)
|
||||
- label: lucide-svelte (deprecated)
|
||||
- label: lucide-angular (deprecated)
|
||||
- label: Figma plugin
|
||||
- label: source/main
|
||||
- label: other/not relevant
|
||||
@@ -38,7 +36,7 @@ body:
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version of Lucide are you running?
|
||||
placeholder: e.g. 0.289.1
|
||||
placeholder: e.g. 1.1.0
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
|
||||
@@ -13,21 +13,19 @@ body:
|
||||
description: Which Lucide project do you wish this feature were added to? You may select more than one.
|
||||
options:
|
||||
- label: lucide
|
||||
- label: lucide-angular (old version)
|
||||
- label: '@lucide/angular (new version)'
|
||||
- label: '@lucide/astro'
|
||||
- label: lucide-flutter
|
||||
- label: lucide-preact
|
||||
- label: lucide-react
|
||||
- label: '@lucide/vue'
|
||||
- label: '@lucide/svelte'
|
||||
- label: '@lucide/angular'
|
||||
- label: '@lucide/astro'
|
||||
- label: lucide-react-native
|
||||
- label: lucide-preact
|
||||
- label: lucide-solid
|
||||
- label: lucide-static
|
||||
- label: lucide-svelte (old version)
|
||||
- label: '@lucide/svelte (new version)'
|
||||
- label: lucide-vue
|
||||
- label: lucide-vue-next
|
||||
- label: lucide-astro
|
||||
- label: '@lucide/icons'
|
||||
- label: lucide-vue-next (deprecated)
|
||||
- label: lucide-svelte (deprecated)
|
||||
- label: lucide-angular (deprecated)
|
||||
- label: Figma plugin
|
||||
- label: all JS packages
|
||||
- label: site
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
## Reviewing PRs
|
||||
|
||||
When reviewing PRs, please respond in a friendly and constructive manner.
|
||||
|
||||
- First, thank the contributor for their contribution in an enthusiastic way.
|
||||
- If you have any suggestions for improvement, provide them clearly and concisely.
|
||||
- If you have any questions about the PR, ask them respectfully.
|
||||
- Let the contributor know that we have many PRs to review and that we appreciate their patience while we review their contribution.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
applyTo: "icons/*.json"
|
||||
---
|
||||
# JSON Metadata Descriptor
|
||||
|
||||
The schema for the JSON metadata descriptor can be found in `icon.schema.json`. It defines the required and optional properties for the JSON files that accompany each icon. The JSON metadata descriptor should be placed in the same directory as the SVG file of the icon and should have the same name as the SVG file, but with a `.json` extension. For example, if the SVG file is named `home.svg`, the JSON metadata descriptor should be named `home.json`.
|
||||
|
||||
## Contributors
|
||||
|
||||
The `contributors` property is a required array of GitHub usernames for the people who created or contributed to the icon. It is used to give credit to contributors and to track the icon's history. Add the PR author to the `contributors` array if they are not already listed and they have made a significant contribution to the icon's SVG.
|
||||
|
||||
## Tags
|
||||
|
||||
The `tags` property is an array of strings that describe the icon and can be used for searching.
|
||||
Validate the tags against the `icon.schema.json` to ensure they are correctly formatted and adhere to the defined structure.
|
||||
Provide tag suggestions based on the name of the icon and the use cases provided in the PR description. Use the existing tags in the repository as a reference for consistency and to avoid duplicates. Don't suggest words like: 'icon' and preferably use single words. Tags should always be in lowercase and should not contain spaces. The name of icon should not be included in the tags, as it is already specified.
|
||||
|
||||
# Categories
|
||||
|
||||
The `categories` property is an array of strings that specify the categories to which the icon belongs, such as "devices", "interface", "media", etc. See the `categories` property in the `icon.schema.json` for more details on the allowed values. The categories should be chosen based on the use cases provided in the PR description and the existing categories in the repository. The categories should be relevant to the icon and should help users find the icon when searching for specific types of icons. The name of icon should not be included in the categories, as it is already specified. Suggest categories based on the name of the icon and the use cases provided in the PR description. Use the existing categories in the repository as a reference for consistency and to avoid duplicates. Categories should always be in lowercase and should not contain spaces.
|
||||
+2
-10
@@ -52,15 +52,13 @@
|
||||
💎 vue package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-vue/*'
|
||||
- 'packages/lucide-vue-next/*'
|
||||
- 'packages/vue/*'
|
||||
|
||||
# For changes in the Lucide angular package
|
||||
🅰️ angular package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/angular/*'
|
||||
- 'packages/lucide-angular/*'
|
||||
|
||||
# For changes in the Lucide preact package
|
||||
⚛️ preact package:
|
||||
@@ -72,7 +70,7 @@
|
||||
🧣 svelte package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-svelte/*'
|
||||
- 'packages/svelte/*'
|
||||
|
||||
# For changes in the Lucide solid package
|
||||
🪝 solid package:
|
||||
@@ -97,9 +95,3 @@
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-static/*'
|
||||
|
||||
# For changes in the Lucide flutter package
|
||||
🏹 flutter package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-flutter/*'
|
||||
|
||||
@@ -32,8 +32,37 @@ concurrency:
|
||||
group: continuous-integration
|
||||
|
||||
jobs:
|
||||
check-dispatch-gate:
|
||||
if: github.repository == 'lucide-icons/lucide' &&
|
||||
startsWith(github.event.head_commit.message, 'feat(icons)') &&
|
||||
github.event_name != 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
RELEASE_ENVIRONMENT: ${{ steps.environment.outputs.RELEASE_ENVIRONMENT }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Fetch tags
|
||||
run: git fetch --all --tags
|
||||
|
||||
- name: Check if latest tag was created today
|
||||
id: environment
|
||||
run: |
|
||||
LATEST_TAG=$(git tag --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)
|
||||
TAG_DATE=$(git log -1 --format=%cs "$LATEST_TAG")
|
||||
TODAY=$(date +%Y-%m-%d)
|
||||
|
||||
if [ "$TAG_DATE" == "$TODAY" ]; then
|
||||
echo "::warning::A release tag already exists for today ($TODAY): $LATEST_TAG. Manual approval will be required to proceed."
|
||||
echo "RELEASE_ENVIRONMENT=Icon Release Gated" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "RELEASE_ENVIRONMENT=Icon Release" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
create-release:
|
||||
if: github.repository == 'lucide-icons/lucide' && startsWith(github.event.head_commit.message, 'feat(icons)') && github.event_name != 'workflow_dispatch'
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
needs: check-dispatch-gate
|
||||
environment: ${{ needs.check-dispatch-gate.outputs.RELEASE_ENVIRONMENT }}
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
VERSION: ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
name: Lucide Angular checks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- next
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/lucide-angular/**
|
||||
- tools/build-icons/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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: Build
|
||||
run: pnpm --filter lucide-angular build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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: Test
|
||||
run: pnpm --filter lucide-angular test
|
||||
@@ -1,46 +0,0 @@
|
||||
name: Lucide Svelte checks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- next
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/lucide-svelte/**
|
||||
- packages/shared/**
|
||||
- tools/build-icons/**
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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: Build
|
||||
run: pnpm --filter lucide-svelte build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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: Test
|
||||
run: pnpm --filter lucide-svelte test
|
||||
@@ -1,46 +0,0 @@
|
||||
name: Lucide Vue Next checks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- next
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/lucide-vue-next/**
|
||||
- packages/shared/**
|
||||
- tools/build-icons/**
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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: Build
|
||||
run: pnpm --filter lucide-vue-next build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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: Test
|
||||
run: pnpm --filter lucide-vue-next test
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
name: Pull request tags suggestions
|
||||
name: Pull request metadata suggestions
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
@@ -6,8 +6,8 @@ on:
|
||||
- 'icons/*.json'
|
||||
|
||||
jobs:
|
||||
pull-request-tags-suggestions:
|
||||
name: Pull Request Tags Suggestions
|
||||
pull-request-metadata-suggestions:
|
||||
name: Pull Request Metadata Suggestions
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Generate comment markup
|
||||
run: node ./scripts/suggestTags.mts
|
||||
run: node ./scripts/suggestMetaData.mts
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
PULL_REQUEST_NUMBER: ${{ github.event.number }}
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Set new version
|
||||
run: pnpm --fail-if-no-match --filter ${{ matrix.package }} version --new-version ${{ needs.prepare.outputs.VERSION }} --no-git-tag-version
|
||||
run: pnpm version ${{ needs.prepare.outputs.VERSION }} --filter ${{ matrix.package }} --fail-if-no-match --no-git-tag-version
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter ${{ matrix.package }} build
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Set new version
|
||||
run: pnpm --filter lucide-static version --new-version ${{ needs.prepare.outputs.VERSION }} --no-git-tag-version
|
||||
run: pnpm version ${{ needs.prepare.outputs.VERSION }} --filter lucide-static --no-git-tag-version
|
||||
|
||||
- name: Move Font
|
||||
run: cp -r lucide-font packages/lucide-static/font
|
||||
|
||||
@@ -39,6 +39,7 @@ packages/**/LICENSE
|
||||
categories.json
|
||||
tags.json
|
||||
.vercel
|
||||
package-lock.json
|
||||
|
||||
# docs
|
||||
docs/.vitepress/cache
|
||||
@@ -52,6 +53,7 @@ docs/.vitepress/data/categoriesData.json
|
||||
docs/.vitepress/data/iconDetails
|
||||
docs/.vitepress/data/relatedIcons.json
|
||||
docs/.vitepress/data/brandStopwords.json
|
||||
docs/.vitepress/data/iconPopularity
|
||||
docs/.vercel
|
||||
docs/.nitro
|
||||
docs/public/og/**
|
||||
|
||||
+4
-4
@@ -7,8 +7,8 @@ docs/.vitepress/cache
|
||||
docs/.vitepress/data
|
||||
docs/.nitro
|
||||
|
||||
# lucide-angular
|
||||
packages/lucide-angular/.angular/cache
|
||||
# @lucide/angular
|
||||
packages/angular/.angular
|
||||
|
||||
# lucide-static
|
||||
packages/lucide-static/icons
|
||||
@@ -20,5 +20,5 @@ packages/lucide-static/font
|
||||
packages/svelte/.svelte-kit
|
||||
|
||||
# lucide-svelte
|
||||
packages/lucide-svelte/src/icons/*.svelte
|
||||
packages/lucide-svelte/.svelte-kit
|
||||
packages/svelte/src/icons/*.svelte
|
||||
packages/svelte/.svelte-kit
|
||||
|
||||
@@ -35,7 +35,7 @@ Lucide is an open-source icon library that provides 1600+ vector (svg) files for
|
||||
| <img src="https://lucide.dev/framework-logos/js.svg" alt="JS logo" width="48"> | **`lucide`** | [](https://www.npmjs.com/package/lucide) |  | [Docs](https://lucide.dev/guide/lucide) · [Source](./packages/lucide) |
|
||||
| <img src="https://lucide.dev/framework-logos/react.svg" alt="React logo" width="48"> | **`lucide-react`** | [](https://www.npmjs.com/package/lucide-react) |  | [Docs](https://lucide.dev/guide/react) · [Source](./packages/lucide-react) |
|
||||
| <img src="https://lucide.dev/framework-logos/vue.svg" alt="Vue logo" width="48"> | **`@lucide/vue`** | [](https://www.npmjs.com/package/@lucide/vue) |  | [Docs](https://lucide.dev/guide/vue) · [Source](./packages/vue) |
|
||||
| <img src="https://lucide.dev/framework-logos/svelte.svg" alt="Svelte logo" width="48"> | **`@lucide/svelte`** | [](https://www.npmjs.com/package/@lucide/svelte) |  | [Docs](https://lucide.dev/guide/svelte) · [Source](./packages/@lucide/svelte) |
|
||||
| <img src="https://lucide.dev/framework-logos/svelte.svg" alt="Svelte logo" width="48"> | **`@lucide/svelte`** | [](https://www.npmjs.com/package/@lucide/svelte) |  | [Docs](https://lucide.dev/guide/svelte) · [Source](./packages/svelte) |
|
||||
| <img src="https://lucide.dev/framework-logos/solid.svg" alt="Solid logo" width="48"> | **`lucide-solid`** | [](https://www.npmjs.com/package/lucide-solid) |  | [Docs](https://lucide.dev/guide/solid) · [Source](./packages/lucide-solid) |
|
||||
| <img src="https://lucide.dev/framework-logos/preact.svg" alt="Preact logo" width="48"> | **`lucide-preact`** | [](https://www.npmjs.com/package/lucide-preact) |  | [Docs](https://lucide.dev/guide/preact) · [Source](./packages/lucide-preact) |
|
||||
| <img src="https://lucide.dev/framework-logos/react-native.svg" alt="React Native logo" width="48"> | **`lucide-react-native`** | [](https://www.npmjs.com/package/lucide-react-native) |  | [Docs](https://lucide.dev/guide/react-native) · [Source](./packages/lucide-react-native) |
|
||||
|
||||
@@ -11,7 +11,7 @@ export default eventHandler(async (event) => {
|
||||
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 iconSize = Math.min(parseInt(iconSizeString, 10) || imageSize, 256);
|
||||
const data = svgData.slice(0, -4);
|
||||
|
||||
const prevSvg = iconNodes[name]
|
||||
|
||||
@@ -109,7 +109,7 @@ export default defineConfig({
|
||||
],
|
||||
footer: {
|
||||
message: 'Released under the ISC License.',
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`,
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Lucide Icons`,
|
||||
},
|
||||
editLink: {
|
||||
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path',
|
||||
|
||||
@@ -24,6 +24,13 @@ const getHeadConfig = ({
|
||||
defer: '',
|
||||
},
|
||||
],
|
||||
[
|
||||
'script',
|
||||
{
|
||||
src: 'https://media.bitterbrains.com/main.js?from=LUCIDE&type=top',
|
||||
async: 'true',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
<script setup lang="ts">
|
||||
import { useLocalStorage } from '@vueuse/core';
|
||||
import IconButton from './base/IconButton.vue';
|
||||
import { x } from '../../data/iconNodes';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||
|
||||
const showAskSurvey = useLocalStorage('show-ask-overlay', true, {
|
||||
initOnMounted: true,
|
||||
});
|
||||
|
||||
const delayedShow = ref(false)
|
||||
|
||||
const showPopup = computed(() => showAskSurvey.value && delayedShow.value );
|
||||
let isInitialized = false
|
||||
|
||||
function startTally() {
|
||||
window.Tally.openPopup('EkvOpB', {
|
||||
layout: 'modal',
|
||||
width: 700,
|
||||
autoClose: 5000,
|
||||
onClose() {
|
||||
showAskSurvey.value = false;
|
||||
},
|
||||
onSubmit() {
|
||||
showAskSurvey.value = false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
delayedShow.value = true
|
||||
}, 10000)
|
||||
})
|
||||
|
||||
function initTally() {
|
||||
try {
|
||||
if (!isInitialized) {
|
||||
isInitialized = true
|
||||
const s = document.createElement('script')
|
||||
s.src = `//tally.so/widgets/embed.js`
|
||||
s.async = true
|
||||
s.onload = startTally
|
||||
document.body.appendChild(s)
|
||||
} else {
|
||||
startTally();
|
||||
}
|
||||
} catch (error) {
|
||||
window.open('https://tally.so/r/EkvOpB', '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
function hidePopup() {
|
||||
showAskSurvey.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div
|
||||
:class="{
|
||||
'show-popup': showPopup,
|
||||
}"
|
||||
class="ask-overlay"
|
||||
>
|
||||
<IconButton
|
||||
@click="hidePopup"
|
||||
class="hide-button"
|
||||
>
|
||||
<Icon
|
||||
:iconNode="x"
|
||||
:size="20"
|
||||
absoluteStrokeWidth
|
||||
/>
|
||||
</IconButton>
|
||||
|
||||
<h2 class="title">
|
||||
We value your feedback!
|
||||
</h2>
|
||||
|
||||
<p class="description">
|
||||
Can you spare a moment to take our survey? <br>Your feedback helps us improve Lucide and make it better for everyone.
|
||||
</p>
|
||||
|
||||
<div class="buttons">
|
||||
<VPButton
|
||||
text="Take the Survey"
|
||||
theme="brand"
|
||||
@click="initTally"
|
||||
/>
|
||||
|
||||
<VPButton
|
||||
text="Close"
|
||||
theme="alt"
|
||||
@click="hidePopup"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ask-overlay {
|
||||
position: fixed;
|
||||
z-index: 99;;
|
||||
bottom: 24px;
|
||||
width: calc(100% - 48px);
|
||||
max-width: 420px;
|
||||
left: 24px;
|
||||
transition:
|
||||
opacity 0.25s,
|
||||
transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--vp-shadow-4);
|
||||
background-color: var(--vp-carbon-ads-bg-color);
|
||||
opacity: 0;
|
||||
transform: translateY(200px);
|
||||
}
|
||||
|
||||
.ask-overlay .title {
|
||||
line-height: 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ask-overlay .description {
|
||||
padding-top: 8px;
|
||||
line-height: 24px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.ask-overlay.show-popup {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
transition:
|
||||
opacity 0.5s,
|
||||
transform 0.25s ease;
|
||||
}
|
||||
|
||||
.ask-overlay .buttons {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.hide-button {
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 3;
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
@@ -6,10 +6,10 @@ import SelectIcon from './SelectIcon.vue';
|
||||
|
||||
defineProps<{
|
||||
id?: string;
|
||||
items?: { name: string; route: string; icon: string; iconDark?: string }[];
|
||||
items?: { name: string; value: string; icon?: string; iconDark?: string }[];
|
||||
}>();
|
||||
|
||||
const selected = defineModel<{ name: string; icon: string; iconDark?: string }>();
|
||||
const selected = defineModel<{ name: string; value: string; icon?: string; iconDark?: string }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -19,14 +19,18 @@ const selected = defineModel<{ name: string; icon: string; iconDark?: string }>(
|
||||
class="select-button"
|
||||
:id="id"
|
||||
>
|
||||
<SelectIcon :item="selected" />
|
||||
<slot name="start-icon">
|
||||
<SelectIcon v-if="selected.icon" :item="selected" />
|
||||
</slot>
|
||||
<span class="select-text">{{ selected.name }}</span>
|
||||
<span class="select-icon">
|
||||
<Icon
|
||||
:iconNode="chevronsUpDown"
|
||||
class="chevron-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<slot name="end-icon">
|
||||
<Icon
|
||||
:iconNode="chevronsUpDown"
|
||||
class="chevron-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</slot>
|
||||
</span>
|
||||
</ListboxButton>
|
||||
|
||||
@@ -43,8 +47,9 @@ const selected = defineModel<{ name: string; icon: string; iconDark?: string }>(
|
||||
:value="item"
|
||||
as="template"
|
||||
>
|
||||
|
||||
<li :class="['select-option', { active, selected }]">
|
||||
<SelectIcon :item="item" />
|
||||
<SelectIcon v-if="item.icon" :item="item" />
|
||||
<span :class="['option-text', { selected }]">{{ item.name }}</span>
|
||||
<span
|
||||
v-if="selected"
|
||||
|
||||
@@ -2,25 +2,28 @@
|
||||
const { item } = defineProps<{
|
||||
item: {
|
||||
name: string;
|
||||
icon: string;
|
||||
icon?: string;
|
||||
iconDark?: string;
|
||||
};
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img
|
||||
:src="item.icon"
|
||||
:class="{ 'select-item-icon': true, light: item.iconDark }"
|
||||
:alt="`${item.name} logo`"
|
||||
loading="lazy"
|
||||
/>
|
||||
<img
|
||||
v-if="item.iconDark"
|
||||
:src="item.iconDark"
|
||||
:alt="`${item.name} logo`"
|
||||
class="select-item-icon dark"
|
||||
/>
|
||||
<div v-if="item.icon || item.iconDark">
|
||||
<img
|
||||
v-if="item.icon"
|
||||
:src="item.icon"
|
||||
:class="{ 'select-item-icon': true, light: item.iconDark }"
|
||||
:alt="`${item.name} logo`"
|
||||
loading="lazy"
|
||||
/>
|
||||
<img
|
||||
v-if="item.iconDark"
|
||||
:src="item.iconDark"
|
||||
:alt="`${item.name} logo`"
|
||||
class="select-item-icon dark"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vitepress';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const frameworks = [
|
||||
{
|
||||
ariaLabel: 'Get JavaScript certificates from certificates.dev',
|
||||
image: '/certificates-dev/js-certificates-dev.jpg',
|
||||
href: 'https://certificates.dev/javascript?friend=LUCIDE',
|
||||
routes: [
|
||||
'/guide/lucide/',
|
||||
'/guide/svelte/',
|
||||
'/guide/solid/',
|
||||
'/guide/astro/',
|
||||
'/guide/static/'
|
||||
],
|
||||
},
|
||||
{
|
||||
ariaLabel: 'Get React certificates from certificates.dev',
|
||||
image: '/certificates-dev/react-certificates-dev.jpg',
|
||||
href: 'https://certificates.dev/react?friend=LUCIDE',
|
||||
routes: [
|
||||
'/guide/react/',
|
||||
'/guide/react-native/',
|
||||
'/guide/preact/',
|
||||
],
|
||||
},
|
||||
{
|
||||
ariaLabel: 'Get Vue certificates from certificates.dev',
|
||||
href: 'https://certificates.dev/vuejs?friend=LUCIDE',
|
||||
image: '/certificates-dev/vue-certificates-dev.jpg',
|
||||
routes: [
|
||||
'/guide/vue/',
|
||||
],
|
||||
},
|
||||
{
|
||||
ariaLabel: 'Get Angular certificates from certificates.dev',
|
||||
href: 'https://certificates.dev/angular?friend=LUCIDE',
|
||||
image: '/certificates-dev/angular-certificates-dev.jpg',
|
||||
routes: [
|
||||
'/guide/angular/',
|
||||
],
|
||||
}
|
||||
];
|
||||
|
||||
const selected = computed(() => {
|
||||
const current = frameworks.find(({ routes }) => {
|
||||
return routes.some(route => router.route.path.startsWith(route));
|
||||
});
|
||||
|
||||
return current || frameworks[0];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a class="certificates-link" :href="selected.href" :aria-label="selected.ariaLabel" target="_blank" rel="noopener noreferrer">
|
||||
<img :src="selected.image" :alt="selected.ariaLabel" />
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.certificates-link {
|
||||
/* padding: 20px; */
|
||||
border-radius: 12px;
|
||||
min-height: 100px;
|
||||
text-align: center;
|
||||
line-height: 18px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
background-color: var(--vp-carbon-ads-bg-color);
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.certificates-link img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
border-radius: 6px;
|
||||
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.certificates-link:hover img {
|
||||
transform: scale(1.05);
|
||||
opacity: 0.9;
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -11,49 +11,49 @@ const { page } = useData();
|
||||
const router = useRouter();
|
||||
|
||||
const frameworks = [
|
||||
{ name: 'Vanilla', icon: '/framework-logos/js.svg', route: '/guide/lucide/' },
|
||||
{ name: 'React', icon: '/framework-logos/react.svg', route: '/guide/react/' },
|
||||
{ name: 'Vue', icon: '/framework-logos/vue.svg', route: '/guide/vue/' },
|
||||
{ name: 'Svelte', icon: '/framework-logos/svelte.svg', route: '/guide/svelte/' },
|
||||
{ name: 'Solid', icon: '/framework-logos/solid.svg', route: '/guide/solid/' },
|
||||
{ name: 'Angular', icon: '/framework-logos/angular.svg', route: '/guide/angular/' },
|
||||
{ name: 'Preact', icon: '/framework-logos/preact.svg', route: '/guide/preact/' },
|
||||
{ name: 'Vanilla', icon: '/framework-logos/js.svg', value: '/guide/lucide/' },
|
||||
{ name: 'React', icon: '/framework-logos/react.svg', value: '/guide/react/' },
|
||||
{ name: 'Vue', icon: '/framework-logos/vue.svg', value: '/guide/vue/' },
|
||||
{ name: 'Svelte', icon: '/framework-logos/svelte.svg', value: '/guide/svelte/' },
|
||||
{ name: 'Solid', icon: '/framework-logos/solid.svg', value: '/guide/solid/' },
|
||||
{ name: 'Angular', icon: '/framework-logos/angular.svg', value: '/guide/angular/' },
|
||||
{ name: 'Preact', icon: '/framework-logos/preact.svg', value: '/guide/preact/' },
|
||||
{
|
||||
name: 'React Native',
|
||||
icon: '/framework-logos/react-native.svg',
|
||||
route: '/guide/react-native/',
|
||||
value: '/guide/react-native/',
|
||||
},
|
||||
{
|
||||
name: 'Astro',
|
||||
icon: '/framework-logos/astro.svg',
|
||||
iconDark: '/framework-logos/astro-dark.svg',
|
||||
route: '/guide/astro/',
|
||||
value: '/guide/astro/',
|
||||
},
|
||||
{ name: 'Static', icon: '/framework-logos/svg.svg', route: '/guide/static/' },
|
||||
{ name: 'Static', icon: '/framework-logos/svg.svg', value: '/guide/static/' },
|
||||
];
|
||||
|
||||
const fallbackFramework = useLocalStorage('lucide-docs-fallback-framework', frameworks[1]);
|
||||
|
||||
const selected = computed(() => {
|
||||
const current = frameworks.find(({ route }) => {
|
||||
const current = frameworks.find(({ value }) => {
|
||||
if (router.route.path?.startsWith?.('/guide')) {
|
||||
const [, , framework] = router.route.path.split('/');
|
||||
const [, frameWorkRoute] = route.split('/').filter(Boolean);
|
||||
const [, frameWorkRoute] = value.split('/').filter(Boolean);
|
||||
|
||||
return framework === frameWorkRoute;
|
||||
}
|
||||
return router.route.path.split('/').slice(0, 3).join('/') === route;
|
||||
return router.route.path.split('/').slice(0, 3).join('/') === value;
|
||||
});
|
||||
|
||||
return current || fallbackFramework.value;
|
||||
return current || (fallbackFramework.value?.value ? fallbackFramework.value : frameworks[0]);
|
||||
});
|
||||
|
||||
function onSelectFramework(item: { name: string; icon: string; iconDark?: string; route: string }) {
|
||||
function onSelectFramework(item: { name: string; icon: string; iconDark?: string; value: string }) {
|
||||
fallbackFramework.value = item;
|
||||
if (item.route !== router.route.path) {
|
||||
const likeRoute = router.route.path.replace(selected.value.route, item.route);
|
||||
if (item.value !== router.route.path) {
|
||||
const likeRoute = router.route.path.replace(selected.value.value, item.value);
|
||||
|
||||
const hasRoute = sidebar[item.route]?.some((section) =>
|
||||
const hasRoute = sidebar[item.value]?.some((section) =>
|
||||
section?.items?.some(({ link }) => link === likeRoute),
|
||||
);
|
||||
|
||||
@@ -62,7 +62,7 @@ function onSelectFramework(item: { name: string; icon: string; iconDark?: string
|
||||
return;
|
||||
}
|
||||
|
||||
router.go(item.route);
|
||||
router.go(item.value);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -85,12 +85,12 @@ function onSelectFramework(item: { name: string; icon: string; iconDark?: string
|
||||
/>
|
||||
</div>
|
||||
<VPSidebarGroup
|
||||
:key="selected.route"
|
||||
:key="selected.value"
|
||||
v-if="
|
||||
page?.relativePath?.startsWith?.('guide') &&
|
||||
!page?.relativePath?.startsWith?.(selected.route.substring(1))
|
||||
!page?.relativePath?.startsWith?.(selected.value.substring(1))
|
||||
"
|
||||
:items="sidebar[selected.route]"
|
||||
:items="sidebar[selected.value]"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ const deprecatedTitle = computed(() => {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
scrollbar-width: thin; /* can also be normal, or none, to not render scrollbar */
|
||||
scrollbar-color: currentColor transparent; /* foreground background */
|
||||
scrollbar-color: var(--vp-c-text-4) transparent; /* foreground background */
|
||||
}
|
||||
.horizontal-scroller::-webkit-scrollbar {
|
||||
width: 0;
|
||||
|
||||
@@ -156,11 +156,11 @@ function handleCloseDrawer() {
|
||||
if (searchQueryDebounced.value) {
|
||||
url.searchParams.set('search', searchQueryDebounced.value);
|
||||
}
|
||||
|
||||
|
||||
if (selectedCategory.value) {
|
||||
url.hash = selectedCategory.value;
|
||||
}
|
||||
|
||||
|
||||
window.history.pushState({}, '', url);
|
||||
}
|
||||
</script>
|
||||
@@ -169,6 +169,7 @@ function handleCloseDrawer() {
|
||||
<div
|
||||
ref="overviewEl"
|
||||
class="overview-container"
|
||||
:class="{ 'icon-drawer-open': activeIconName }"
|
||||
>
|
||||
<StickyBar class="category-search">
|
||||
<InputSearch
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { IconEntity } from '../../types';
|
||||
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
||||
import { useRoute } from 'vitepress';
|
||||
import IconGrid from './IconGrid.vue';
|
||||
import Select from '../base/Select.vue';
|
||||
import InputSearch from '../base/InputSearch.vue';
|
||||
import useSearch from '../../composables/useSearch';
|
||||
import useSearchInput from '../../composables/useSearchInput';
|
||||
@@ -14,9 +15,26 @@ import useFetchCategories from '../../composables/useFetchCategories';
|
||||
import chunkArray from '../../utils/chunkArray';
|
||||
import CarbonAdOverlay from './CarbonAdOverlay.vue';
|
||||
import useSearchPlaceholder from '../../utils/useSearchPlaceholder.ts';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import { listSortDescending } from '~/.vitepress/data/iconNodes';
|
||||
|
||||
const ICON_SIZE = 56;
|
||||
const ICON_GRID_GAP = 8;
|
||||
const SORTING = [
|
||||
{
|
||||
name: 'Popularity',
|
||||
value: 'popularity',
|
||||
},
|
||||
{
|
||||
name: 'Release date',
|
||||
value: 'release-date',
|
||||
},
|
||||
{
|
||||
name: 'Name',
|
||||
value: 'name',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
const initialGridItems = computed(() => {
|
||||
if (containerWidth.value === 0) return 120;
|
||||
@@ -32,6 +50,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const activeIconName = ref(null);
|
||||
const selectedSort = ref(SORTING[0])
|
||||
|
||||
const { execute: fetchTags, data: tags } = useFetchTags();
|
||||
const { execute: fetchCategories, data: categories } = useFetchCategories();
|
||||
@@ -43,12 +62,29 @@ const columnSize = computed(() => {
|
||||
return Math.floor(containerWidth.value / (ICON_SIZE + ICON_GRID_GAP));
|
||||
});
|
||||
|
||||
const sortedIcons = computed(() => {
|
||||
switch (selectedSort.value.value) {
|
||||
case 'popularity':
|
||||
return [...props.icons].sort((a, b) => (b.popularity || 0) - (a.popularity || 0));
|
||||
case 'release-date':
|
||||
return [...props.icons].sort((a, b) => {
|
||||
const aDate = a.createdRelease?.date ? new Date(a.createdRelease.date).getTime() : 0;
|
||||
const bDate = b.createdRelease?.date ? new Date(b.createdRelease.date).getTime() : 0;
|
||||
return bDate - aDate;
|
||||
});
|
||||
case 'name':
|
||||
return [...props.icons].sort((a, b) => a.name.localeCompare(b.name));
|
||||
default:
|
||||
return props.icons;
|
||||
}
|
||||
});
|
||||
|
||||
const mappedIcons = computed(() => {
|
||||
if (tags.value == null) {
|
||||
return props.icons;
|
||||
return sortedIcons.value;
|
||||
}
|
||||
|
||||
return props.icons.map((icon) => {
|
||||
return sortedIcons.value.map((icon) => {
|
||||
const iconTags = tags.value[icon.name];
|
||||
const iconCategories = categories.value?.[icon.name] ?? [];
|
||||
|
||||
@@ -68,10 +104,11 @@ const { shortcutText: kbdSearchShortcut } = useSearchShortcut(() => {
|
||||
|
||||
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
|
||||
{ name: 'name', weight: 3 },
|
||||
{ name: 'aliases', weight: 3 },
|
||||
{ name: 'aliases', weight: 8 },
|
||||
{ name: 'tags', weight: 2 },
|
||||
{ name: 'categories', weight: 1 },
|
||||
]);
|
||||
|
||||
const searchPlaceholder = useSearchPlaceholder(searchQuery, searchResults);
|
||||
|
||||
const chunkedIcons = computed(() => {
|
||||
@@ -124,7 +161,7 @@ function handleCloseDrawer() {
|
||||
if (searchQueryDebounced.value) {
|
||||
url.searchParams.set('search', searchQueryDebounced.value);
|
||||
}
|
||||
|
||||
|
||||
window.history.pushState({}, '', url);
|
||||
}
|
||||
</script>
|
||||
@@ -133,6 +170,7 @@ function handleCloseDrawer() {
|
||||
<div
|
||||
ref="overviewEl"
|
||||
class="overview-container"
|
||||
:class="{ 'icon-drawer-open': activeIconName }"
|
||||
>
|
||||
<StickyBar>
|
||||
<InputSearch
|
||||
@@ -143,6 +181,20 @@ function handleCloseDrawer() {
|
||||
class="input-wrapper"
|
||||
@focus="onFocusSearchInput"
|
||||
/>
|
||||
|
||||
<Select
|
||||
id="sort-select"
|
||||
:items="SORTING"
|
||||
v-model="selectedSort"
|
||||
>
|
||||
<template #start-icon>
|
||||
<Icon
|
||||
:iconNode="listSortDescending"
|
||||
class="chevron-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</template>
|
||||
</Select>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="searchPlaceholder.isNoResults"
|
||||
|
||||
@@ -15,6 +15,50 @@
|
||||
margin-bottom: 32px;
|
||||
background: var(--vp-c-bg);
|
||||
box-shadow: 0 16px 24px var(--vp-c-bg);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-button) {
|
||||
padding: 11px 14px;
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
color: var(--vp-c-text-2);
|
||||
flex-shrink: 0;
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-button):hover,
|
||||
.search-bar :deep(.select-button):focus {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-options) {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-option) {
|
||||
padding-right: 36px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-icon) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-button) .select-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.search-bar :deep(.select-button) {
|
||||
padding: 11px 16px 11px 16px;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-button) .select-text {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useData } from 'vitepress';
|
||||
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar';
|
||||
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue';
|
||||
import { computed } from 'vue';
|
||||
import SurveyAskOverlay from '../SurveyAskOverlay.vue';
|
||||
|
||||
const { theme } = useData();
|
||||
const { hasSidebar } = useSidebar();
|
||||
|
||||
@@ -20,16 +20,6 @@ const useSearch = <T>(
|
||||
return index.value.search(query.value).map((result) => result.item);
|
||||
}
|
||||
|
||||
if (keys.length !== 0) {
|
||||
const mainKey = keys[0].name;
|
||||
|
||||
return collection.value.sort((a, b) => {
|
||||
const aString = a[mainKey as keyof T] as string;
|
||||
const bString = b[mainKey as keyof T] as string;
|
||||
return aString.localeCompare(bString);
|
||||
});
|
||||
}
|
||||
|
||||
return collection.value;
|
||||
});
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ import IconsSidebarNavAfter from './layouts/IconsSidebarNavAfter.vue';
|
||||
import HomeHeroIconsCard from './components/home/HomeHeroIconsCard.vue';
|
||||
import HomeHeroAfter from './components/home/HomeHeroAfter.vue';
|
||||
import HomeHeroInfoBefore from './components/home/HomeHeroInfoBefore.vue';
|
||||
import LayoutTop from './components/base/LayoutTop.vue';
|
||||
import { ICON_STYLE_CONTEXT, iconStyleContext } from './composables/useIconStyle';
|
||||
import { CATEGORY_VIEW_CONTEXT, categoryViewContext } from './composables/useCategoryView';
|
||||
import { EXTERNAL_LIBS_CONTEXT, externalLibContext } from './composables/useExternalLibs';
|
||||
import FrameworkSelect from './components/guide/FrameworkSelect.vue';
|
||||
import SnackPlayer from './components/editors/SnackPlayer.vue';
|
||||
import CertificatesLink from './components/guide/CertificatesLink.vue';
|
||||
|
||||
const theme: Partial<Theme> = {
|
||||
extends: DefaultTheme,
|
||||
@@ -23,6 +23,7 @@ const theme: Partial<Theme> = {
|
||||
'sidebar-nav-after': () => h(IconsSidebarNavAfter),
|
||||
'home-hero-image': () => h(HomeHeroIconsCard),
|
||||
'home-hero-actions-after': () => h(HomeHeroAfter),
|
||||
'aside-ads-before': () => h(CertificatesLink),
|
||||
// Keep this here for now, we might want to add more things to the top bar in the future
|
||||
// 'layout-top': () => h(LayoutTop),
|
||||
});
|
||||
|
||||
@@ -117,6 +117,10 @@
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.overview-container.icon-drawer-open {
|
||||
padding-bottom: 288px;
|
||||
}
|
||||
|
||||
.VPHomeHero .container {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
@@ -162,3 +166,30 @@
|
||||
margin: 0 auto 48px;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
html:not(.has-bb-banner) #bb-banner {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
.VPSidebar {
|
||||
/* Hide Scrollbar */
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
scrollbar-width: thin; /* can also be normal, or none, to not render scrollbar */
|
||||
scrollbar-color: var(--vp-c-text-4) transparent; /* foreground background */
|
||||
}
|
||||
.VPSidebar::-webkit-scrollbar {
|
||||
width: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.VPSidebar::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.VPSidebar::-webkit-scrollbar-thumb {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface IconEntity extends IconMetaData {
|
||||
externalLibrary?: ExternalLibs;
|
||||
createdRelease?: Release;
|
||||
changedRelease?: Release;
|
||||
popularity?: number;
|
||||
}
|
||||
|
||||
export interface Category {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type PageData } from 'vitepress';
|
||||
import { PageData } from 'vitepress';
|
||||
import getStructuredData from './getStructuredData';
|
||||
import { createGeneralOGImage, createIconOGImage } from './createOGImage';
|
||||
|
||||
|
||||
Vendored
+38
@@ -46,5 +46,43 @@ declare global {
|
||||
*/
|
||||
append(container: Element): void;
|
||||
};
|
||||
Tally: {
|
||||
// From https://tally.so/help/developer-resources#2f676e40530a460ea6a634b8441f6001
|
||||
openPopup: (
|
||||
formId: string,
|
||||
options?: {
|
||||
key?: string; // This is used as a unique identifier used for the "Show only once" and "Don't show after submit" functionality
|
||||
layout?: 'default' | 'modal';
|
||||
width?: number;
|
||||
alignLeft?: boolean;
|
||||
hideTitle?: boolean;
|
||||
overlay?: boolean;
|
||||
emoji?: {
|
||||
text: string;
|
||||
animation:
|
||||
| 'none'
|
||||
| 'wave'
|
||||
| 'tada'
|
||||
| 'heart-beat'
|
||||
| 'spin'
|
||||
| 'flash'
|
||||
| 'bounce'
|
||||
| 'rubber-band'
|
||||
| 'head-shake';
|
||||
};
|
||||
autoClose?: number; // in milliseconds
|
||||
showOnce?: boolean;
|
||||
doNotShowAfterSubmit?: boolean;
|
||||
customFormUrl?: string; // when you want to load the form via it's custom domain URL
|
||||
hiddenFields?: {
|
||||
[key: string]: any;
|
||||
};
|
||||
onOpen?: () => void;
|
||||
onClose?: () => void;
|
||||
onPageView?: (page: number) => void;
|
||||
onSubmit?: (payload: SubmissionPayload) => void;
|
||||
},
|
||||
) => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ However, not everyone can understand them easily. Read more about [how to use Lu
|
||||
|
||||
## Official Packages
|
||||
|
||||
Lucide's official packages are designed to work on different platforms, making it easier for users to integrate icons into their projects. The packages are available for various technologies, including [Web (Vanilla)](https://lucide.dev/guide/packages/lucide), [React](https://lucide.dev/guide/packages/lucide-react), [React Native](https://lucide.dev/guide/packages/lucide-react-native), [Vue](https://lucide.dev/guide/packages/lucide-vue), [Vue 3](https://lucide.dev/guide/packages/lucide-vue-next), [Svelte](https://lucide.dev/guide/packages/lucide-svelte), [Preact](https://lucide.dev/guide/packages/lucide-preact), [Solid](https://lucide.dev/guide/packages/lucide-solid), [Angular](https://lucide.dev/guide/packages/angular), [Astro](https://lucide.dev/guide/packages/lucide-astro), and [NodeJS](https://lucide.dev/guide/packages/lucide-static#nodejs).
|
||||
Lucide's official packages are designed to work on different platforms, making it easier for users to integrate icons into their projects. The packages are available for various technologies, including [Web (Vanilla)](./lucide/index.md), [React](./react/index.md), [React Native](./react-native/index.md), [Vue](./vue/index.md), [Svelte](./svelte/index.md), [Preact](./preact/index.md), [Solid](./solid/index.md), [Angular](./angular/index.md), [Astro](./astro/index.md), and [Static](./static/index.md).
|
||||
|
||||
## Community
|
||||
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
::: warning
|
||||
This documentation is for `@lucide/angular`.
|
||||
|
||||
To learn about our legacy package for Angular, please refer to [`lucide-angular`](./lucide-angular).
|
||||
To learn about our legacy package for Angular, please refer to [`lucide-angular`](https://v0.lucide.dev/guide/packages/lucide-angular).
|
||||
:::
|
||||
|
||||
A standalone, signal-based, zoneless implementation of Lucide icons for Angular.
|
||||
|
||||
**What you can accomplish:**
|
||||
|
||||
- Use icons as standalone Angular components with full dependency injection support
|
||||
- Configure icons globally through modern Angular providers
|
||||
- Integrate with Angular's reactive forms and data binding
|
||||
@@ -118,6 +119,7 @@ Alternatively, the component also accepts string inputs.
|
||||
To use icons this way, first, you have to provide icons via `provideLucideIcons`:
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts{7-10} [app.config.ts]
|
||||
import { ApplicationConfig } from '@angular/core';
|
||||
import { provideLucideIcons, LucideCircleCheck, LucideCircleX } from '@lucide/angular';
|
||||
@@ -148,6 +150,7 @@ import { LucideIcon } from '@lucide/angular';
|
||||
})
|
||||
export class Foobar { }
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: tip
|
||||
@@ -259,19 +262,25 @@ export class Foobar {
|
||||
## Troubleshooting
|
||||
|
||||
### The icon is not being displayed
|
||||
|
||||
If using per-icon-components:
|
||||
|
||||
1. Ensure that the icon component is being imported, if using per-icon-components
|
||||
2. Check that the icon name matches exactly (case-sensitive)
|
||||
|
||||
If using the dynamic component:
|
||||
|
||||
1. Ensure the icon is provided via `provideLucideIcons()` if using string names
|
||||
2. Verify the icon is imported from `@lucide/angular` and not the legacy package
|
||||
|
||||
### TypeScript errors?
|
||||
|
||||
Make sure you're importing from `@lucide/angular` and not `lucide-angular`.
|
||||
|
||||
### Icons render with wrong defaults
|
||||
|
||||
Ensure `provideLucideConfig()` is used at the right level.
|
||||
|
||||
## Migration guide
|
||||
|
||||
Migrating from `lucide-angular`? Read our [comprehensive migration guide](https://github.com/lucide-icons/lucide/blob/main/packages/angular/MIGRATION.md).
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
# Lucide Angular
|
||||
|
||||
::: warning
|
||||
This documentation is for our legacy package for Angular.
|
||||
|
||||
For our modern, standalone-first implementation, please refer to [`@lucide/angular`](./angular).
|
||||
:::
|
||||
|
||||
Angular components and services for Lucide icons that integrate with Angular's dependency injection and component system. Provides both traditional module-based and modern standalone component approaches for maximum flexibility in Angular applications.
|
||||
|
||||
**What you can accomplish:**
|
||||
- Use icons as Angular components with full dependency injection support
|
||||
- Configure icons globally through Angular services and providers
|
||||
- Choose from multiple component selectors (lucide-angular, lucide-icon, i-lucide, span-lucide)
|
||||
- Integrate with Angular's reactive forms and data binding
|
||||
- Build scalable applications with tree-shaken icon bundles and lazy loading support
|
||||
|
||||
## Installation
|
||||
|
||||
::: code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add lucide-angular
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add lucide-angular
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm install lucide-angular
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add lucide-angular
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## How to use
|
||||
|
||||
### Step 1: Import `LucideAngularModule`
|
||||
|
||||
In any Angular module you wish to use Lucide icons in, you have to import `LucideAngularModule`, and pick any icons you wish to use:
|
||||
|
||||
```js
|
||||
import { LucideAngularModule, File, House, Menu, UserCheck } from 'lucide-angular';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
LucideAngularModule.pick({File, House, Menu, UserCheck})
|
||||
]
|
||||
})
|
||||
export class AppModule { }
|
||||
```
|
||||
|
||||
or using standalone version:
|
||||
|
||||
```js
|
||||
import { Component } from '@angular/core';
|
||||
import { LucideAngularModule, FileIcon } from 'lucide-angular';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.scss',
|
||||
imports: [LucideAngularModule]
|
||||
})
|
||||
export class AppComponent {
|
||||
readonly FileIcon = FileIcon;
|
||||
}
|
||||
```
|
||||
### Step 2: Use the icons in templates
|
||||
|
||||
Within your templates you may now use one of the following component tags to insert an icon:
|
||||
|
||||
```html
|
||||
<lucide-angular name="file" class="my-icon"></lucide-angular>
|
||||
<lucide-icon name="house" class="my-icon"></lucide-icon>
|
||||
<i-lucide name="menu" class="my-icon"></i-lucide>
|
||||
<span-lucide name="user-check" class="my-icon"></span-lucide>
|
||||
```
|
||||
for standalone
|
||||
```html
|
||||
<lucide-angular [img]="FileIcon" class="my-icon"></lucide-angular>
|
||||
<lucide-icon [img]="FileIcon" class="my-icon"></lucide-icon>
|
||||
<i-lucide [img]="FileIcon" class="my-icon"></i-lucide>
|
||||
<span-lucide [img]="FileIcon" class="my-icon"></span-lucide>
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
You can pass additional props to adjust the icon appearance.
|
||||
|
||||
| name | type | default |
|
||||
| --------------------- | --------- | ------------ |
|
||||
| `size` | *number* | 24 |
|
||||
| `color` | *string* | currentColor |
|
||||
| `strokeWidth` | *number* | 2 |
|
||||
| `absoluteStrokeWidth` | *boolean* | false |
|
||||
|
||||
```html
|
||||
<i-lucide name="house" [size]="48" color="red" [strokeWidth]="1"></i-lucide>
|
||||
```
|
||||
|
||||
### Global configuration
|
||||
|
||||
You can inject the `LucideIconConfig` service in your root component to globally configure the default property values as defined above.
|
||||
|
||||
### Styling using a custom CSS class
|
||||
|
||||
Any extra HTML attribute is ignored, but the `class` attribute
|
||||
is passed onto the internal SVG image element and it can be used to style it:
|
||||
|
||||
```css
|
||||
svg.my-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
stroke-width: 3;
|
||||
}
|
||||
```
|
||||
|
||||
## Injecting multiple icon providers
|
||||
|
||||
You may provide additional icons using the `LUCIDE_ICONS` injection token,
|
||||
which accepts multiple providers of the interface `LucideIconsProviderInterface`
|
||||
with the utility class `LucideIconsProvider` available for easier usage:
|
||||
|
||||
```js
|
||||
import { LUCIDE_ICONS, LucideIconProvider } from 'lucide-angular';
|
||||
import { MyIcon } from './icons/my-icon';
|
||||
|
||||
const myIcons = {MyIcon};
|
||||
|
||||
@NgModule({
|
||||
providers: [
|
||||
{provide: LUCIDE_ICONS, multi: true, useValue: new LucideIconProvider(myIcons)},
|
||||
]
|
||||
})
|
||||
export class AppModule { }
|
||||
```
|
||||
|
||||
To add custom icons, you will first need to convert them to an [svgson format](https://github.com/elrumordelaluz/svgson).
|
||||
|
||||
## Loading all icons
|
||||
|
||||
::: danger
|
||||
You may also opt to import all icons if necessary using the following format but be aware that this will significantly increase your application build size.
|
||||
:::
|
||||
|
||||
```js
|
||||
import { icons } from 'lucide-angular';
|
||||
|
||||
...
|
||||
|
||||
LucideAngularModule.pick(icons)
|
||||
```
|
||||
|
||||
## With Lucide Lab or custom icons
|
||||
|
||||
[Lucide Lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
|
||||
They can be used in the same way as the official icons.
|
||||
|
||||
```js
|
||||
import { LucideAngularModule } from 'lucide-angular';
|
||||
import { coconut } from '@lucide/lab';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
LucideAngularModule.pick({ coconut })
|
||||
]
|
||||
})
|
||||
export class AppModule { }
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
By default, we hide icons from screen readers using `aria-hidden="true"`.
|
||||
|
||||
You can add accessibility attributes using aria-labels.
|
||||
|
||||
```html
|
||||
<lucide-icon name="check" aria-label="Task completed"></lucide-icon>
|
||||
```
|
||||
|
||||
For best practices on accessibility, please see our [accessibility guide](../accessibility.md).
|
||||
@@ -3,6 +3,7 @@
|
||||
Vue 2 components for Lucide icons that integrate with Vue's Options API and template system. Each icon is a Vue component that renders as an inline SVG, providing familiar Vue development patterns for legacy applications still using Vue 2.
|
||||
|
||||
**What you can accomplish:**
|
||||
|
||||
- Use icons as Vue 2 components with Options API integration
|
||||
- Maintain legacy Vue 2 applications with modern icon components
|
||||
- Integrate with Vue 2's template system and component lifecycle
|
||||
@@ -155,7 +156,7 @@ You can add accessibility attributes using aria-labels.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { Check } from 'lucide-vue-next';
|
||||
import { Check } from '@lucide/vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -15,24 +15,22 @@ Make sure you have a Svelte environment set up. If you don't have one yet, you c
|
||||
|
||||
## Installation
|
||||
|
||||
<!-- TODO: v1 Remove After full release -->
|
||||
|
||||
::: code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm install @lucide/svelte@next
|
||||
pnpm install @lucide/svelte
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add @lucide/svelte@next
|
||||
yarn add @lucide/svelte
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm install @lucide/svelte@next
|
||||
npm install @lucide/svelte
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add @lucide/svelte@next
|
||||
bun add @lucide/svelte
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
import iconNodes from '../.vitepress/data/iconNodes';
|
||||
import * as iconDetails from '../.vitepress/data/iconDetails';
|
||||
|
||||
export default {
|
||||
async load() {
|
||||
return {
|
||||
icons: Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode })),
|
||||
icons: Object.entries(iconDetails).map(
|
||||
([, { name, iconNode, popularity, createdRelease, aliases = [] }]) => ({
|
||||
name,
|
||||
iconNode,
|
||||
popularity: popularity?.count ?? 0,
|
||||
aliases: aliases
|
||||
.map((alias) => (typeof alias === 'string' ? alias : alias?.name))
|
||||
.filter((alias): alias is string => Boolean(alias)),
|
||||
createdRelease,
|
||||
}),
|
||||
),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
+10
-9
@@ -9,14 +9,15 @@
|
||||
"docs:build": "pnpm run /^prebuild:.*/ && vitepress build",
|
||||
"docs:preview": "vitepress preview",
|
||||
"build:docs": "vitepress build",
|
||||
"prebuild:iconNodes": "node ./scripts/writeIconNodes.mjs",
|
||||
"prebuild:metaJson": "node ./scripts/writeIconMetaIndex.mjs",
|
||||
"prebuild:releaseJson": "node ./scripts/writeReleaseMetadata.mjs",
|
||||
"prebuild:categoriesJson": "node ./scripts/writeCategoriesMetadata.mjs",
|
||||
"prebuild:relatedIcons": "node ./scripts/writeIconRelatedIcons.mjs",
|
||||
"prebuild:iconDetails": "node ./scripts/writeIconDetails.mjs",
|
||||
"prebuild:brandStopwords": "node ./scripts/writeBrandStopwords.mjs",
|
||||
"postbuild:vercelJson": "node ./scripts/writeVercelOutput.mjs",
|
||||
"prebuild:iconNodes": "node ./scripts/writeIconNodes.mts",
|
||||
"prebuild:metaJson": "node ./scripts/writeIconMetaIndex.mts",
|
||||
"prebuild:releaseJson": "node ./scripts/writeReleaseMetadata.mts",
|
||||
"prebuild:categoriesJson": "node ./scripts/writeCategoriesMetadata.mts",
|
||||
"prebuild:relatedIcons": "node ./scripts/writeIconRelatedIcons.mts",
|
||||
"prebuild:iconDetails": "node ./scripts/writeIconDetails.mts",
|
||||
"prebuild:iconPopularity": "node ./scripts/writeiconPopularity.mts",
|
||||
"prebuild:brandStopwords": "node ./scripts/writeBrandStopwords.mts",
|
||||
"postbuild:vercelJson": "node ./scripts/writeVercelOutput.mts",
|
||||
"dev": "npx nitropack dev",
|
||||
"prebuild:api": "npx nitropack prepare",
|
||||
"build:api": "npx nitropack build",
|
||||
@@ -38,7 +39,7 @@
|
||||
"vitepress": "^1.6.4",
|
||||
"vitepress-plugin-group-icons": "^1.6.5",
|
||||
"vitepress-plugin-llms": "^1.11.1",
|
||||
"markdown-it": "^14.1.0",
|
||||
"markdown-it": "^14.2.0",
|
||||
"markdown-it-container": "^4.0.0",
|
||||
"semver": "^7.7.3",
|
||||
"sharp": "^0.34.5",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
@@ -26,6 +26,7 @@ const writeIconFiles = icons.map(async (iconFileName) => {
|
||||
import iconNode from '../iconNodes/${iconName}.node.json'
|
||||
import metaData from '../../../../icons/${iconName}.json'
|
||||
import releaseData from '../releaseMetadata/${iconName}.json'
|
||||
import popularity from '../iconPopularity/${iconName}.json'
|
||||
|
||||
const { tags, categories, contributors, aliases, deprecated, deprecationReason, toBeRemovedInVersion } = metaData
|
||||
|
||||
@@ -37,6 +38,7 @@ const iconDetails = {
|
||||
categories,
|
||||
aliases,
|
||||
deprecated,
|
||||
popularity,
|
||||
deprecationReason,
|
||||
toBeRemovedInVersion,
|
||||
...releaseData,
|
||||
@@ -42,7 +42,7 @@ const getRelatedIcons = (currentIcon, icons) => {
|
||||
};
|
||||
|
||||
const iconsMetaDataPromises = svgFiles.map(async (iconName) => {
|
||||
const metaDataFileContent = await fs.promises.readFile(`../icons/${iconName}`);
|
||||
const metaDataFileContent = await fs.promises.readFile(`../icons/${iconName}`, 'utf-8');
|
||||
const metaData = JSON.parse(metaDataFileContent);
|
||||
|
||||
const name = iconName.replace('.json', '');
|
||||
@@ -70,10 +70,10 @@ const comparisonsPromises = tags.map(async (tag, index) => {
|
||||
};
|
||||
});
|
||||
|
||||
const comparisons = await Promise.all(comparisonsPromises);
|
||||
const comparisons = (await Promise.all(comparisonsPromises)).filter((comparison) => comparison != null && Object.keys(comparison.iconFiles).length > 0);
|
||||
const newReleaseMetaData = {};
|
||||
|
||||
comparisons.forEach(({ tag, iconFiles, date } = {}) => {
|
||||
comparisons.forEach(({ tag, iconFiles, date }) => {
|
||||
if (tag == null) return;
|
||||
|
||||
iconFiles.forEach(({ status, file, renamedFile }) => {
|
||||
@@ -11,7 +11,7 @@ const iconMetaData = await getIconMetaData(path.resolve(scriptDir, '../../icons'
|
||||
const iconAliasesRedirectRoutes = Object.entries(iconMetaData)
|
||||
.filter(([, { aliases }]) => aliases?.length)
|
||||
.map(([iconName, { aliases }]) => {
|
||||
const aliasRouteMatches = aliases.map((alias) => alias.name).join('|');
|
||||
const aliasRouteMatches = aliases.map((alias) => typeof alias === 'string' ? alias : alias?.name).join('|');
|
||||
|
||||
return {
|
||||
src: `^/icons/(${aliasRouteMatches})$`,
|
||||
@@ -0,0 +1,99 @@
|
||||
/* eslint-disable no-restricted-syntax, no-await-in-loop */
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { loadEnvFile } from 'node:process';
|
||||
import { readSvgDirectory } from '@lucide/helpers';
|
||||
|
||||
const currentDir = process.cwd();
|
||||
const ICONS_DIR = path.resolve(currentDir, '../icons');
|
||||
const iconPopularityDirectory = path.resolve(currentDir, '.vitepress/data', 'iconPopularity');
|
||||
|
||||
try {
|
||||
// Load environment variables from .env file, if it exists.
|
||||
loadEnvFile(`${currentDir}/.env`);
|
||||
} catch (error) {}
|
||||
|
||||
if (fs.existsSync(iconPopularityDirectory)) {
|
||||
fs.rmSync(iconPopularityDirectory, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
if (!fs.existsSync(iconPopularityDirectory)) {
|
||||
fs.mkdirSync(iconPopularityDirectory);
|
||||
}
|
||||
|
||||
const iconJsonFiles = await readSvgDirectory(ICONS_DIR, '.json');
|
||||
const iconNames = iconJsonFiles.map((file) => path.basename(file, '.json'));
|
||||
|
||||
const defaultIconNamePopularity: Record<string, number> = Object.fromEntries(
|
||||
iconNames.map((iconName) => [iconName, 0])
|
||||
);
|
||||
|
||||
interface AnalyticsResponse {
|
||||
results: {
|
||||
dimensions: [route: string];
|
||||
metrics: [count: number];
|
||||
}[];
|
||||
}
|
||||
|
||||
if (process.env.LUCIDE_ANALYTICS_TOKEN === undefined) {
|
||||
console.error('LUCIDE_ANALYTICS_TOKEN environment variable is not set. Please set it to fetch analytics data.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('https://analytics.lucide.dev/api/v2/query', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${process.env.LUCIDE_ANALYTICS_TOKEN}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"site_id": "lucide.dev",
|
||||
"metrics": [
|
||||
"visitors"
|
||||
],
|
||||
"dimensions": [
|
||||
"visit:entry_page"
|
||||
],
|
||||
"date_range": "12mo"
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Network response was not ok: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const analyticsData: AnalyticsResponse = await response.json();
|
||||
|
||||
const iconVisitCounts = analyticsData.results.reduce((acc, { dimensions, metrics }) => {
|
||||
const [route] = dimensions;
|
||||
const [count] = metrics;
|
||||
|
||||
if (!route.startsWith('/icons')) {
|
||||
return acc
|
||||
}
|
||||
// Only match routes that start with /icons/ and end with an icon name, e.g. /icons/activity, /icons/activity/ or /icons/activity?ref=homepage
|
||||
const match = route.match(/^\/icons\/([^\/\?]+)(?:[\/\?]|$)/);
|
||||
if (match) {
|
||||
const iconName = match[1];
|
||||
|
||||
if (iconNames.includes(iconName)) {
|
||||
acc[iconName] = (acc[iconName] || 0) + count;
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, defaultIconNamePopularity);
|
||||
|
||||
const writePopularityPromises = Object.entries(iconVisitCounts).map(([iconName, count]) => {
|
||||
const location = path.resolve(iconPopularityDirectory, `${iconName}.json`);
|
||||
return fs.promises.writeFile(location, JSON.stringify({ count }, null, 2), 'utf-8');
|
||||
});
|
||||
|
||||
await Promise.all(writePopularityPromises);
|
||||
console.log('Successfully written icon popularity data for', writePopularityPromises.length, 'icons.');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching analytics data:', error);
|
||||
}
|
||||
|
||||
+9
-1
@@ -13,7 +13,7 @@
|
||||
"title": "Lucide Icons icon schema",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["$schema", "categories", "tags"],
|
||||
"required": ["$schema", "contributors", "categories", "tags", "use-cases"],
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string"
|
||||
@@ -114,6 +114,14 @@
|
||||
"minItems": 1,
|
||||
"uniqueItems": true
|
||||
},
|
||||
"use-cases": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 0,
|
||||
"uniqueItems": true
|
||||
},
|
||||
"deprecated": {
|
||||
"const": true
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"danielbayley",
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"letter",
|
||||
"font size",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"danielbayley",
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"letter",
|
||||
"font size",
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"use-cases": [
|
||||
"indicating a large and small text size",
|
||||
"formatting text size",
|
||||
"indicating a heading and body text"
|
||||
],
|
||||
"contributors": [
|
||||
"it-is-not",
|
||||
"jguddas",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"karsa-mistmere",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"disability",
|
||||
"disabled",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"colebemis",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"pulse",
|
||||
"action",
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"ahtohbi4",
|
||||
"jamiemlaw",
|
||||
"karsa-mistmere",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [
|
||||
"marking content as sponsored",
|
||||
"linking to an advertise with us page"
|
||||
],
|
||||
"tags": [
|
||||
"advert",
|
||||
"affiliate",
|
||||
"brand",
|
||||
"campaign",
|
||||
"commercial",
|
||||
"marketing",
|
||||
"monetize",
|
||||
"paid",
|
||||
"partner",
|
||||
"promo",
|
||||
"sponsor"
|
||||
],
|
||||
"categories": [
|
||||
"multimedia",
|
||||
"notifications"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M10 13H6" />
|
||||
<path d="M10 15v-4a2 2 0 0 0-4 0v4" />
|
||||
<path d="M14 14.5a.5.5 0 0 0 .5.5h1a2.5 2.5 0 0 0 2.5-2.5v-1A2.5 2.5 0 0 0 15.5 9h-1a.5.5 0 0 0-.5.5z" />
|
||||
<rect x="2" y="5" width="20" height="14" rx="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 434 B |
@@ -4,6 +4,7 @@
|
||||
"karsa-mistmere",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"air conditioner",
|
||||
"ac",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"csandman",
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"stream",
|
||||
"cast",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"jguddas",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"done",
|
||||
"todo",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"jguddas",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"remove"
|
||||
],
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"karsa-mistmere",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"morning",
|
||||
"turn-off"
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"jguddas",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"add"
|
||||
],
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"jguddas",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"morning"
|
||||
],
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"karsa-mistmere",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"fire",
|
||||
"alert",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"csandman",
|
||||
"mittalyashu"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"photo",
|
||||
"book"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"items",
|
||||
"flex",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"items",
|
||||
"flex",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"items",
|
||||
"bottom",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"items",
|
||||
"right",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"r4lv",
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"items",
|
||||
"flex",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"r4lv",
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"right",
|
||||
"items",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"r4lv",
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"left",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"center",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"right",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"left",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"center",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"around",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"top",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"left",
|
||||
"items",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"ericfennis",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"items",
|
||||
"flex",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"bottom",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"top",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"center",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"bottom",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"top",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"center",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"center",
|
||||
"items",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"karsa-mistmere",
|
||||
"danielbayley"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"ambulance",
|
||||
"emergency",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"karsa-mistmere",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"and",
|
||||
"typography",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"danielbayley",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"and",
|
||||
"operator",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"pottery",
|
||||
"artifact",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"ericfennis",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"ship"
|
||||
],
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"karsa-mistmere",
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"emoji",
|
||||
"anger",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"emoji",
|
||||
"nuisance",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"contributors": [
|
||||
"danielbayley"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
"signal",
|
||||
"connection",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user