Compare commits

..

5 Commits

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

View File

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

View File

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

View File

@@ -42,15 +42,12 @@ module.exports = {
'@html-eslint/no-duplicate-attrs': 'error', '@html-eslint/no-duplicate-attrs': 'error',
'@html-eslint/no-inline-styles': 'error', '@html-eslint/no-inline-styles': 'error',
'@html-eslint/require-attrs': [ '@html-eslint/require-attrs': [
'error', 'error',
...Object.entries(DEFAULT_ATTRS).map(([attr, value]) => ({ ...Object.entries(DEFAULT_ATTRS)
tag: 'svg', .map(([attr, value]) => ({ tag: 'svg', attr, value: String(value) }))
attr,
value: String(value),
})),
], ],
'@html-eslint/indent': ['error', 2], '@html-eslint/indent': ['error', 2],
'@html-eslint/no-multiple-empty-lines': ['error', { max: 0 }], "@html-eslint/no-multiple-empty-lines": ["error", { "max": 0 }],
'@html-eslint/no-extra-spacing-attrs': [ '@html-eslint/no-extra-spacing-attrs': [
'error', 'error',
{ {
@@ -67,7 +64,7 @@ module.exports = {
'@html-eslint/element-newline': 'error', '@html-eslint/element-newline': 'error',
'@html-eslint/no-trailing-spaces': 'error', '@html-eslint/no-trailing-spaces': 'error',
'@html-eslint/quotes': 'error', '@html-eslint/quotes': 'error',
}, }
}, },
], ],
}; };

View File

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

View File

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

View File

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

View File

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

96
.github/labeler.yml vendored
View File

@@ -1,92 +1,92 @@
# For changed dependencies # For changed dependencies
📦 dependencies: 📦 dependencies:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- pnpm-lock.yaml - pnpm-lock.yaml
# For changes in documentation # For changes in documentation
📖 documentation: 📖 documentation:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- docs/*.md - docs/*.md
- docs/**/*.md - docs/**/*.md
# For changes in the site, but not markdown files # For changes in the site, but not markdown files
🌍 site: 🌍 site:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'docs/**' - 'docs/**'
# For changes in the metadata # For changes in the metadata
🫧 metadata: 🫧 metadata:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'icons/*.json' - 'icons/*.json'
- categories/* - categories/*
# For changes or added icons # For changes or added icons
🎨 icon: 🎨 icon:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'icons/*.svg' - 'icons/*.svg'
# For changes in the lucide package # For changes in the lucide package
🧳 lucide package: 🧳 lucide package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide/*' - 'packages/lucide/*'
# For changes in the lucide React package # For changes in the lucide React package
⚛️ react package: ⚛️ react package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-react/*' - 'packages/lucide-react/*'
# For changes in the lucide React Native package # For changes in the lucide React Native package
⚛️ react native package: ⚛️ react native package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-react-native/*' - 'packages/lucide-react-native/*'
# For changes in the lucide vue packages # For changes in the lucide vue packages
💎 vue package: 💎 vue package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-vue/*' - 'packages/lucide-vue/*'
- 'packages/lucide-vue-next/*' - 'packages/lucide-vue-next/*'
# For changes in the lucide angular package # For changes in the lucide angular package
🅰️ angular package: 🅰️ angular package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-angular/*' - 'packages/lucide-angular/*'
# For changes in the lucide preact package # For changes in the lucide preact package
⚛️ preact package: ⚛️ preact package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-preact/*' - 'packages/lucide-preact/*'
# For changes in the lucide svelte package # For changes in the lucide svelte package
🧣 svelte package: 🧣 svelte package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-svelte/*' - 'packages/lucide-svelte/*'
# For changes in the lucide solid package # For changes in the lucide solid package
🪝 solid package: 🪝 solid package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-solid/*' - 'packages/lucide-solid/*'
# For changes in the lucide static package # For changes in the lucide static package
🪨 static package: 🪨 static package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-static/*' - 'packages/lucide-static/*'
# For changes in the lucide flutter package # For changes in the lucide flutter package
🏹 flutter package: 🏹 flutter package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-flutter/*' - 'packages/lucide-flutter/*'

View File

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

View File

@@ -1,13 +1,11 @@
name: Close stale issues and PR name: Close stale issues and PR
on: on:
schedule: schedule:
- cron: '45 1 * * *' - cron: "45 1 * * *"
jobs: jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
pull-requests: write
steps: steps:
- uses: actions/stale@v9 - uses: actions/stale@v9
with: with:
@@ -16,5 +14,4 @@ jobs:
close-pr-message: This PR was closed because it has been stalled for 5 days with no activity. close-pr-message: This PR was closed because it has been stalled for 5 days with no activity.
close-pr-label: 🧶 stale close-pr-label: 🧶 stale
days-before-stale: 30 days-before-stale: 30
days-before-issue-stale: -1
days-before-close: -1 days-before-close: -1

View File

@@ -1,6 +1,6 @@
name: 'Pull Request Labeler' name: "Pull Request Labeler"
on: on:
- pull_request_target - pull_request_target
jobs: jobs:
triage: triage:
@@ -9,4 +9,4 @@ jobs:
pull-requests: write pull-requests: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/labeler@v5 - uses: actions/labeler@v5

View File

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

View File

@@ -29,7 +29,7 @@ jobs:
- name: Create font in ./lucide-font - name: Create font in ./lucide-font
run: pnpm build:font run: pnpm build:font
- name: 'Upload to Artifacts' - name: "Upload to Artifacts"
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: lucide-font name: lucide-font

View File

@@ -24,5 +24,8 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter lucide-preact build
- name: Test - name: Test
run: pnpm --filter lucide-preact test run: pnpm --filter lucide-preact test

View File

@@ -24,5 +24,8 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter lucide-react-native build
- name: Test - name: Test
run: pnpm --filter lucide-react-native test run: pnpm --filter lucide-react-native test

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -41,18 +41,17 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
package: package: [
[ 'lucide',
'lucide', 'lucide-react',
'lucide-react', 'lucide-react-native',
'lucide-react-native', 'lucide-vue',
'lucide-vue', 'lucide-vue-next',
'lucide-vue-next', 'lucide-angular',
'lucide-angular', 'lucide-preact',
'lucide-preact', 'lucide-solid',
'lucide-solid', 'lucide-svelte',
'lucide-svelte', ]
]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
@@ -137,7 +136,7 @@ jobs:
- name: Create font in ./lucide-font - name: Create font in ./lucide-font
run: pnpm build:font run: pnpm build:font
- name: 'Upload to Artifacts' - name: "Upload to Artifacts"
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: lucide-font name: lucide-font
@@ -146,7 +145,10 @@ jobs:
post-release: post-release:
if: github.repository == 'lucide-icons/lucide' if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [pre-release, lucide-font] needs: [
pre-release,
lucide-font,
]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

1
.gitignore vendored
View File

@@ -34,7 +34,6 @@ docs/.vitepress/data/iconNodes
docs/.vitepress/data/iconMetaData.ts docs/.vitepress/data/iconMetaData.ts
docs/.vitepress/data/releaseMetaData.json docs/.vitepress/data/releaseMetaData.json
docs/.vitepress/data/releaseMetaData docs/.vitepress/data/releaseMetaData
docs/.vitepress/data/categoriesData.json
docs/.vitepress/data/iconDetails docs/.vitepress/data/iconDetails
docs/.vitepress/data/relatedIcons.json docs/.vitepress/data/relatedIcons.json
docs/.vercel docs/.vercel

View File

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

11
.vscode/settings.json vendored
View File

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

View File

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

View File

@@ -1,11 +1,4 @@
<p align="center"> <p align=center><img width="480" src="https://lucide.dev/lucide-logo-repo.svg" alt="Lucide Logo"></p>
<a href="https://github.com/lucide-icons/lucide#gh-light-mode-only">
<img src="https://lucide.dev/lucide-logo-repo.svg#gh-light-mode-only" alt="Lucide - Beautiful & consistent icon toolkit made by the community. Open-source project and a fork of Feather Icons." width="480">
</a>
<a href="https://github.com/lucide-icons/lucide#gh-dark-mode-only">
<img src="https://lucide.dev/lucide-logo-repo-dark.svg#gh-dark-mode-only" alt="Lucide - Beautiful & consistent icon toolkit made by the community. Open-source project and a fork of Feather Icons." width="480">
</a>
</p>
<p align="center"> <p align="center">
<a href="https://github.com/lucide-icons/lucide/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/lucide" alt="license"></a> <a href="https://github.com/lucide-icons/lucide/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/lucide" alt="license"></a>
<a href="https://www.npmjs.com/package/lucide"><img src="https://img.shields.io/npm/v/lucide" alt="npm package"></a> <a href="https://www.npmjs.com/package/lucide"><img src="https://img.shields.io/npm/v/lucide" alt="npm package"></a>
@@ -281,12 +274,9 @@ Thank you to all the people who contributed to Lucide!
## Sponsors ## Sponsors
<a href="https://vercel.com?utm_source=lucide&utm_campaign=oss"> <a href="https://vercel.com?utm_source=lucide&utm_campaign=oss">
<img src="docs/public/vercel.svg" alt="Powered by Vercel" width="200" /> <img src="docs/public/vercel.svg" alt="Powered by Vercel" width="200" />
</a> </a>
<a href="https://www.digitalocean.com/?refcode=b0877a2caebd&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge"><img src="docs/public/digitalocean.svg" width="200" alt="DigitalOcean Referral Badge" /></a> <a href="https://www.digitalocean.com/?refcode=b0877a2caebd&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge"><img src="docs/public/digitalocean.svg" width="200" alt="DigitalOcean Referral Badge" /></a>
### Awesome backer 🍺
<a href="https://www.scipress.io?utm_source=lucide"><img src="docs/public/sponsors/scipress.svg" width="180" alt="Scipress sponsor badge" /></a>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -37,13 +37,13 @@ export default eventHandler((event) => {
backdropString, backdropString,
src, src,
color: name in iconNodes ? 'red' : '#777', color: name in iconNodes ? 'red' : '#777',
}), })
); );
} }
const svg = Buffer.from( const svg = Buffer.from(
// We can't use jsx here, is not supported here by nitro. // We can't use jsx here, is not supported here by nitro.
renderToString(createElement(SvgPreview, { src, showGrid: true }, children)), renderToString(createElement(SvgPreview, { src, showGrid: true }, children))
).toString('utf8'); ).toString('utf8');
defaultContentType(event, 'image/svg+xml'); defaultContentType(event, 'image/svg+xml');

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,171 +3,87 @@
"order": 0, "order": 0,
"icon": "js", "icon": "js",
"shields": [ "shields": [
{ { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide", "href": "https://www.npmjs.com/package/lucide" },
"alt": "npm", { "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide", "href": "https://www.npmjs.com/package/lucide" }
"src": "https://img.shields.io/npm/v/lucide",
"href": "https://www.npmjs.com/package/lucide"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide",
"href": "https://www.npmjs.com/package/lucide"
}
] ]
}, },
"lucide-react": { "lucide-react": {
"order": 1, "order": 1,
"icon": "react", "icon": "react",
"shields": [ "shields": [
{ { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-react", "href": "https://www.npmjs.com/package/lucide-react" },
"alt": "npm", { "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-react", "href": "https://www.npmjs.com/package/lucide-react" }
"src": "https://img.shields.io/npm/v/lucide-react",
"href": "https://www.npmjs.com/package/lucide-react"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-react",
"href": "https://www.npmjs.com/package/lucide-react"
}
] ]
}, },
"lucide-vue": { "lucide-vue": {
"order": 2, "order": 2,
"icon": "vue", "icon": "vue",
"shields": [ "shields": [
{ { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-vue", "href": "https://www.npmjs.com/package/lucide-vue" },
"alt": "npm", { "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-vue", "href": "https://www.npmjs.com/package/lucide-vue" }
"src": "https://img.shields.io/npm/v/lucide-vue",
"href": "https://www.npmjs.com/package/lucide-vue"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-vue",
"href": "https://www.npmjs.com/package/lucide-vue"
}
] ]
}, },
"lucide-vue-next": { "lucide-vue-next": {
"order": 3, "order": 3,
"icon": "vue-next", "icon": "vue-next",
"shields": [ "shields": [
{ { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-vue-next", "href": "https://www.npmjs.com/package/lucide-vue-next" },
"alt": "npm", { "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-vue-next", "href": "https://www.npmjs.com/package/lucide-vue-next" }
"src": "https://img.shields.io/npm/v/lucide-vue-next",
"href": "https://www.npmjs.com/package/lucide-vue-next"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-vue-next",
"href": "https://www.npmjs.com/package/lucide-vue-next"
}
] ]
}, },
"lucide-svelte": { "lucide-svelte": {
"order": 4, "order": 4,
"icon": "svelte", "icon": "svelte",
"shields": [ "shields": [
{ { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-svelte", "href": "https://www.npmjs.com/package/lucide-svelte" },
"alt": "npm", { "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-svelte", "href": "https://www.npmjs.com/package/lucide-svelte" }
"src": "https://img.shields.io/npm/v/lucide-svelte",
"href": "https://www.npmjs.com/package/lucide-svelte"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-svelte",
"href": "https://www.npmjs.com/package/lucide-svelte"
}
] ]
}, },
"lucide-solid": { "lucide-solid": {
"order": 4, "order": 4,
"icon": "solid", "icon": "solid",
"shields": [ "shields": [
{ { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-solid", "href": "https://www.npmjs.com/package/lucide-solid" },
"alt": "npm", { "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-solid", "href": "https://www.npmjs.com/package/lucide-solid" }
"src": "https://img.shields.io/npm/v/lucide-solid",
"href": "https://www.npmjs.com/package/lucide-solid"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-solid",
"href": "https://www.npmjs.com/package/lucide-solid"
}
] ]
}, },
"lucide-preact": { "lucide-preact": {
"order": 5, "order": 5,
"icon": "preact", "icon": "preact",
"shields": [ "shields": [
{ { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-preact", "href": "https://www.npmjs.com/package/lucide-preact" },
"alt": "npm", { "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-preact", "href": "https://www.npmjs.com/package/lucide-preact" }
"src": "https://img.shields.io/npm/v/lucide-preact",
"href": "https://www.npmjs.com/package/lucide-preact"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-preact",
"href": "https://www.npmjs.com/package/lucide-preact"
}
] ]
}, },
"lucide-react-native": { "lucide-react-native": {
"order": 6, "order": 6,
"icon": "react-native", "icon": "react-native",
"shields": [ "shields": [
{ { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-react-native", "href": "https://www.npmjs.com/package/lucide-react-native" },
"alt": "npm", { "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-react-native", "href": "https://www.npmjs.com/package/lucide-react-native" }
"src": "https://img.shields.io/npm/v/lucide-react-native",
"href": "https://www.npmjs.com/package/lucide-react-native"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-react-native",
"href": "https://www.npmjs.com/package/lucide-react-native"
}
] ]
}, },
"lucide-angular": { "lucide-angular": {
"order": 7, "order": 7,
"icon": "angular", "icon": "angular",
"shields": [ "shields": [
{ { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-angular", "href": "https://www.npmjs.com/package/lucide-angular" },
"alt": "npm", { "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-angular", "href": "https://www.npmjs.com/package/lucide-angular" }
"src": "https://img.shields.io/npm/v/lucide-angular",
"href": "https://www.npmjs.com/package/lucide-angular"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-angular",
"href": "https://www.npmjs.com/package/lucide-angular"
}
] ]
}, },
"lucide-static": { "lucide-static": {
"order": 8, "order": 8,
"icon": "svg", "icon": "svg",
"shields": [ "shields": [
{ { "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-static", "href": "https://www.npmjs.com/package/lucide-static" },
"alt": "npm", { "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-static", "href": "https://www.npmjs.com/package/lucide-static" }
"src": "https://img.shields.io/npm/v/lucide-static",
"href": "https://www.npmjs.com/package/lucide-static"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-static",
"href": "https://www.npmjs.com/package/lucide-static"
}
] ]
}, },
"lucide-flutter": { "lucide-flutter": {
"order": 9, "order": 9,
"icon": "flutter", "icon": "flutter",
"shields": [ "shields": [
{ { "alt": "flutter", "src": "https://img.shields.io/pub/v/lucide_icons", "href": "https://img.shields.io/pub/v/lucide_icons" }
"alt": "flutter",
"src": "https://img.shields.io/pub/v/lucide_icons",
"href": "https://img.shields.io/pub/v/lucide_icons"
}
] ]
} }
} }

View File

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

View File

@@ -17,62 +17,21 @@ const Backdrop = ({ src, color = 'red', backdropString }: BackdropProps): JSX.El
patternUnits="userSpaceOnUse" patternUnits="userSpaceOnUse"
patternTransform="rotate(45 50 50)" patternTransform="rotate(45 50 50)"
> >
<line <line stroke={color} strokeWidth={0.1} y2={1} />
stroke={color} <line stroke={color} strokeWidth={0.1} y2={1} />
strokeWidth={0.1}
y2={1}
/>
<line
stroke={color}
strokeWidth={0.1}
y2={1}
/>
</pattern> </pattern>
</defs> </defs>
<mask <mask id="svg-preview-backdrop-mask-outline" maskUnits="userSpaceOnUse">
id="svg-preview-backdrop-mask-outline" <g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} />
maskUnits="userSpaceOnUse" <g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} />
>
<g
stroke="#fff"
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
<g
dangerouslySetInnerHTML={{ __html: src }}
strokeWidth={2.05}
/>
</mask> </mask>
<mask <mask id="svg-preview-backdrop-mask-fill" maskUnits="userSpaceOnUse">
id="svg-preview-backdrop-mask-fill" <g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} />
maskUnits="userSpaceOnUse" <g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} />
> <g strokeWidth={1.75} dangerouslySetInnerHTML={{ __html: backdropString }} />
<g
stroke="#fff"
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
<g
dangerouslySetInnerHTML={{ __html: src }}
strokeWidth={2.05}
/>
<g
strokeWidth={1.75}
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
</mask> </mask>
<g <g strokeWidth={2.25} stroke="url(#pattern)" mask={'url(#svg-preview-backdrop-mask-outline)'}>
strokeWidth={2.25} <rect x="0" y="0" width="24" height="24" fill="url(#pattern)" opacity={0.5} stroke="none" />
stroke="url(#pattern)"
mask={'url(#svg-preview-backdrop-mask-outline)'}
>
<rect
x="0"
y="0"
width="24"
height="24"
fill="url(#pattern)"
opacity={0.5}
stroke="none"
/>
</g> </g>
<rect <rect
x="0" x="0"

View File

@@ -10,11 +10,7 @@ const Grid = ({
strokeWidth: number; strokeWidth: number;
radius: number; radius: number;
} & PathProps<'stroke', 'strokeWidth'>) => ( } & PathProps<'stroke', 'strokeWidth'>) => (
<g <g className="svg-preview-grid-group" strokeLinecap="butt" {...props}>
className="svg-preview-grid-group"
strokeLinecap="butt"
{...props}
>
<rect <rect
className="svg-preview-grid-rect" className="svg-preview-grid-rect"
width={24 - props.strokeWidth} width={24 - props.strokeWidth}
@@ -25,29 +21,11 @@ const Grid = ({
fill={fill} fill={fill}
/> />
<path <path
strokeDasharray={'0 0.1 ' + '0.1 0.15 '.repeat(11) + '0 0.15'}
strokeWidth={0.1}
d={ d={
props.d || props.d ||
new Array(Math.floor(24 - 1)) new Array(Math.floor(24 - 1))
.fill(null) .fill(null)
.map((_, i) => i) .flatMap((_, i) => [
.filter((i) => i % 3 !== 2)
.flatMap((i) => [
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
])
.join('')
}
/>
<path
d={
props.d ||
new Array(Math.floor(24 - 1))
.fill(null)
.map((_, i) => i)
.filter((i) => i % 3 === 2)
.flatMap((i) => [
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`, `M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`, `M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
]) ])
@@ -66,21 +44,15 @@ const Shadow = ({
paths: Path[]; paths: Path[];
} & PathProps<'stroke' | 'strokeWidth' | 'strokeOpacity', 'd'>) => { } & PathProps<'stroke' | 'strokeWidth' | 'strokeOpacity', 'd'>) => {
const groupedPaths = Object.entries( const groupedPaths = Object.entries(
paths.reduce( paths.reduce((groups, val) => {
(groups, val) => { const key = val.c.id;
const key = val.c.id; groups[key] = [...(groups[key] || []), val];
groups[key] = [...(groups[key] || []), val]; return groups;
return groups; }, {} as Record<number, Path[]>)
},
{} as Record<number, Path[]>,
),
); );
return ( return (
<> <>
<g <g className="svg-preview-shadow-mask-group" {...props}>
className="svg-preview-shadow-mask-group"
{...props}
>
{groupedPaths.map(([id, paths]) => ( {groupedPaths.map(([id, paths]) => (
<mask <mask
id={`svg-preview-shadow-mask-${id}`} id={`svg-preview-shadow-mask-${id}`}
@@ -89,15 +61,7 @@ const Shadow = ({
strokeWidth={props.strokeWidth} strokeWidth={props.strokeWidth}
stroke="#000" stroke="#000"
> >
<rect <rect x={0} y={0} width={24} height={24} fill="#fff" stroke="none" rx={radius} />
x={0}
y={0}
width={24}
height={24}
fill="#fff"
stroke="none"
rx={radius}
/>
<path <path
d={paths d={paths
.flatMap(({ prev, next }) => [ .flatMap(({ prev, next }) => [
@@ -110,16 +74,9 @@ const Shadow = ({
</mask> </mask>
))} ))}
</g> </g>
<g <g className="svg-preview-shadow-group" {...props}>
className="svg-preview-shadow-group"
{...props}
>
{paths.map(({ d, c: { id } }, i) => ( {paths.map(({ d, c: { id } }, i) => (
<path <path key={i} mask={`url(#svg-preview-shadow-mask-${id})`} d={d} />
key={i}
mask={`url(#svg-preview-shadow-mask-${id})`}
d={d}
/>
))} ))}
<path <path
d={paths d={paths
@@ -137,16 +94,9 @@ const ColoredPath = ({
paths, paths,
...props ...props
}: { paths: Path[]; colors: string[] } & PathProps<never, 'd' | 'stroke'>) => ( }: { paths: Path[]; colors: string[] } & PathProps<never, 'd' | 'stroke'>) => (
<g <g className="svg-preview-colored-path-group" {...props}>
className="svg-preview-colored-path-group"
{...props}
>
{paths.map(({ d, c }, i) => ( {paths.map(({ d, c }, i) => (
<path <path key={i} d={d} stroke={colors[(c.name === 'path' ? i : c.id) % colors.length]} />
key={i}
d={d}
stroke={colors[(c.name === 'path' ? i : c.id) % colors.length]}
/>
))} ))}
</g> </g>
); );
@@ -188,15 +138,7 @@ const ControlPath = ({
key={i} key={i}
maskUnits="userSpaceOnUse" maskUnits="userSpaceOnUse"
> >
<rect <rect x="0" y="0" width="24" height="24" fill="#fff" stroke="none" rx={radius} />
x="0"
y="0"
width="24"
height="24"
fill="#fff"
stroke="none"
rx={radius}
/>
<path d={`M${prev.x} ${prev.y}h.01`} /> <path d={`M${prev.x} ${prev.y}h.01`} />
<path d={`M${next.x} ${next.y}h.01`} /> <path d={`M${next.x} ${next.y}h.01`} />
</mask> </mask>
@@ -204,10 +146,7 @@ const ControlPath = ({
); );
})} })}
</g> </g>
<g <g className="svg-preview-control-path-group" {...props}>
className="svg-preview-control-path-group"
{...props}
>
{controlPaths.map(({ d, showMarker }, i) => ( {controlPaths.map(({ d, showMarker }, i) => (
<path <path
key={i} key={i}
@@ -216,33 +155,18 @@ const ControlPath = ({
/> />
))} ))}
</g> </g>
<g <g className="svg-preview-control-path-marker-group" {...props}>
className="svg-preview-control-path-marker-group"
{...props}
>
<path <path
d={controlPaths d={controlPaths
.flatMap(({ prev, next, showMarker }) => .flatMap(({ prev, next, showMarker }) =>
showMarker ? [`M${prev.x} ${prev.y}h.01`, `M${next.x} ${next.y}h.01`] : [], showMarker ? [`M${prev.x} ${prev.y}h.01`, `M${next.x} ${next.y}h.01`] : []
) )
.join('')} .join('')}
/> />
{controlPaths.map(({ d, prev, next, startMarker, endMarker }, i) => ( {controlPaths.map(({ d, prev, next, startMarker, endMarker }, i) => (
<React.Fragment key={i}> <React.Fragment key={i}>
{startMarker && ( {startMarker && <circle cx={prev.x} cy={prev.y} r={pointSize / 2} />}
<circle {endMarker && <circle cx={next.x} cy={next.y} r={pointSize / 2} />}
cx={prev.x}
cy={prev.y}
r={pointSize / 2}
/>
)}
{endMarker && (
<circle
cx={next.x}
cy={next.y}
r={pointSize / 2}
/>
)}
</React.Fragment> </React.Fragment>
))} ))}
</g> </g>
@@ -258,16 +182,15 @@ const Radii = ({
any any
>) => { >) => {
return ( return (
<g <g className="svg-preview-radii-group" {...props}>
className="svg-preview-radii-group"
{...props}
>
{paths.map( {paths.map(
({ c, prev, next, circle }, i) => ({ c, prev, next, circle }, i) =>
circle && ( circle && (
<React.Fragment key={i}> <React.Fragment key={i}>
{c.name !== 'circle' && ( {c.name !== "circle" && (
<path d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`} /> <path
d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`}
/>
)} )}
<circle <circle
cy={circle.y} cy={circle.y}
@@ -277,7 +200,7 @@ const Radii = ({
stroke={ stroke={
(Math.round(circle.x * 100) / 100) % 1 !== 0 || (Math.round(circle.x * 100) / 100) % 1 !== 0 ||
(Math.round(circle.y * 100) / 100) % 1 !== 0 (Math.round(circle.y * 100) / 100) % 1 !== 0
? 'red' ? "red"
: undefined : undefined
} }
/> />
@@ -285,7 +208,11 @@ const Radii = ({
cy={circle.y} cy={circle.y}
cx={circle.x} cx={circle.x}
r={circle.r} r={circle.r}
stroke={(Math.round(circle.r * 1000) / 1000) % 1 !== 0 ? 'red' : undefined} stroke={
(Math.round(circle.r * 1000) / 1000) % 1 !== 0
? "red"
: undefined
}
/> />
</React.Fragment> </React.Fragment>
), ),
@@ -303,28 +230,13 @@ const Handles = ({
>) => { >) => {
console.log(paths); console.log(paths);
return ( return (
<g <g className="svg-preview-handles-group" {...props}>
className="svg-preview-handles-group"
{...props}
>
{paths.map(({ c, prev, next, cp1, cp2 }) => ( {paths.map(({ c, prev, next, cp1, cp2 }) => (
<> <>
{cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />} {cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />}
{cp1 && ( {cp1 && <circle cy={cp1.y} cx={cp1.x} r={0.25} />}
<circle
cy={cp1.y}
cx={cp1.x}
r={0.25}
/>
)}
{cp2 && <path d={`M${next.x} ${next.y} ${cp2.x} ${cp2.y}`} />} {cp2 && <path d={`M${next.x} ${next.y} ${cp2.x} ${cp2.y}`} />}
{cp2 && ( {cp2 && <circle cy={cp2.y} cx={cp2.x} r={0.25} />}
<circle
cy={cp2.y}
cx={cp2.x}
r={0.25}
/>
)}
</> </>
))} ))}
</g> </g>
@@ -368,27 +280,9 @@ const SvgPreview = React.forwardRef<
{...props} {...props}
> >
<style>{darkModeCss}</style> <style>{darkModeCss}</style>
{showGrid && ( {showGrid && <Grid strokeWidth={0.1} stroke="#777" strokeOpacity={0.3} radius={1} />}
<Grid <Shadow paths={paths} strokeWidth={4} stroke="#777" radius={1} strokeOpacity={0.15} />
strokeWidth={0.1} <Handles paths={paths} strokeWidth={0.12} stroke="#777" strokeOpacity={0.6} />
stroke="#777"
strokeOpacity={0.3}
radius={1}
/>
)}
<Shadow
paths={paths}
strokeWidth={4}
stroke="#777"
radius={1}
strokeOpacity={0.15}
/>
<Handles
paths={paths}
strokeWidth={0.12}
stroke="#777"
strokeOpacity={0.6}
/>
<ColoredPath <ColoredPath
paths={paths} paths={paths}
colors={[ colors={[
@@ -413,19 +307,8 @@ const SvgPreview = React.forwardRef<
stroke="#777" stroke="#777"
strokeOpacity={0.3} strokeOpacity={0.3}
/> />
<ControlPath <ControlPath radius={1} paths={paths} pointSize={1} stroke="#fff" strokeWidth={0.125} />
radius={1} <Handles paths={paths} strokeWidth={0.12} stroke="#FFF" strokeOpacity={0.3} />
paths={paths}
pointSize={1}
stroke="#fff"
strokeWidth={0.125}
/>
<Handles
paths={paths}
strokeWidth={0.12}
stroke="#FFF"
strokeOpacity={0.3}
/>
{children} {children}
</svg> </svg>
); );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,59 +0,0 @@
<script setup lang="ts">
import Card from '../base/Card.vue';
import HomeSectionTitle from './HomeSectionTitle.vue';
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
</script>
<template>
<HomeSectionTitle :headingLevel="3"> Sponsor the Lucide maintainers </HomeSectionTitle>
<Card class="sponsor-card">
<img
src="/company-logos/open-collective-light.svg"
alt="Open Collective logo"
class="logo light"
width="242"
height="42"
/>
<img
src="/company-logos/open-collective-dark.svg"
alt="Open Collective logo"
class="logo dark"
width="242"
height="42"
/>
<VPButton
href="https://opencollective.com/lucide-icons"
class="sponsor-button"
text="Become a sponsor"
/>
</Card>
</template>
<style scoped>
.sponsor-card {
margin: 0 auto;
max-width: 500px;
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
}
.sponsor-button {
margin: auto 0;
}
html.dark .logo.dark {
display: none;
}
html:not(.dark) .logo.light {
display: none;
}
@media (min-width: 640px) {
.sponsor-card {
flex-direction: row;
justify-content: space-between;
}
}
</style>

View File

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

View File

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

View File

@@ -1,83 +0,0 @@
<script setup lang="ts">
import { useData } from 'vitepress';
import { useSessionStorage } from '@vueuse/core';
import IconButton from '../base/IconButton.vue';
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue'
import { x } from '../../../data/iconNodes'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
const { theme } = useData()
const showAd = useSessionStorage('show-carbon-ads',true)
defineProps<{
drawerOpen: boolean
}>()
const CloseIcon = createLucideIcon('Close', x)
</script>
<template>
<div
:class="{
'drawer-open': drawerOpen,
'hide-ad': !showAd
}"
class="floating-ad"
v-if="theme.carbonAds"
>
<IconButton @click="showAd = false" class="hide-button">
<component :is="CloseIcon" :size="20" absoluteStrokeWidth />
</IconButton>
<VPDocAsideCarbonAds
:carbon-ads="theme.carbonAds"
/>
</div>
</template>
<style scoped>
.floating-ad {
display: none;
position: fixed;
bottom: 32px;
width: 224px;
right: 32px;
transition: opacity 0.5s, transform 0.25s ease;
}
.floating-ad.drawer-open {
transform: translateY(-288px);
}
.floating-ad.hide-ad {
transform: translateX(224px);
opacity: 0;
}
.floating-ad.drawer-open.hide-ad {
transform: translateY(-288px) translateX(224px);
}
.floating-ad.drawer-open, .floating-ad.hide-ad {
transition: opacity 0.25s, transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
}
@media (min-width: 1280px) {
.floating-ad {
display: block;
}
}
@media (min-width: 1440px) {
.floating-ad {
right: calc(((100% - (var(--vp-layout-max-width) - var(--vp-sidebar-width))) - 272px) / 2);
}
}
.hide-button {
padding: 4px;
position: absolute;
top: 8px;
right: 8px;
background-color: transparent;
}
</style>

View File

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

View File

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

View File

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

View File

@@ -39,12 +39,6 @@ function copyJSX() {
navigator.clipboard.writeText(code) navigator.clipboard.writeText(code)
} }
function copyComponentName() {
const code = componentName.value
navigator.clipboard.writeText(code)
}
function copyVue() { function copyVue() {
let attrs = [''] let attrs = ['']
@@ -107,7 +101,6 @@ function copyAngular() {
:popoverPosition="popoverPosition" :popoverPosition="popoverPosition"
:options="[ :options="[
{ text: 'Copy JSX' , onClick: copyJSX }, { text: 'Copy JSX' , onClick: copyJSX },
{ text: 'Copy Component Name' , onClick: copyComponentName },
{ text: 'Copy Vue' , onClick: copyVue }, { text: 'Copy Vue' , onClick: copyVue },
{ text: 'Copy Svelte' , onClick: copyJSX }, { text: 'Copy Svelte' , onClick: copyJSX },
{ text: 'Copy Angular' , onClick: copyAngular }, { text: 'Copy Angular' , onClick: copyAngular },

View File

@@ -11,31 +11,14 @@ import IconInfo from './IconInfo.vue';
import Badge from '../base/Badge.vue'; import Badge from '../base/Badge.vue';
import { computedAsync } from '@vueuse/core'; import { computedAsync } from '@vueuse/core';
import { satisfies } from 'semver'; import { satisfies } from 'semver';
import { useExternalLibs } from '../../composables/useExternalLibs';
const props = defineProps<{ const props = defineProps<{
iconName: string | null iconName: string
}>() }>()
const { externalIconNodes } = useExternalLibs()
const { go } = useRouter()
const icon = computedAsync<IconEntity | null>(async () => { const icon = computedAsync<IconEntity | null>(async () => {
if (props.iconName) { if (props.iconName) {
try { return (await import(`../../../data/iconDetails/${props.iconName}.ts`)).default as IconEntity
if (props.iconName.includes(':')) {
const [library, name] = props.iconName.split(':')
return externalIconNodes.value[library].find((icon) => icon.name === name)
} else {
return (await import(`../../../data/iconDetails/${props.iconName}.ts`)).default as IconEntity
}
} catch (err) {
if (!props.iconName.includes(':')) {
go(`/icons/${props.iconName}`)
}
}
} }
return null return null
}, null) }, null)
@@ -53,6 +36,8 @@ function onClose() {
emit('close') emit('close')
} }
const { go } = useRouter()
const CloseIcon = createLucideIcon('Close', x) const CloseIcon = createLucideIcon('Close', x)
const Expand = createLucideIcon('Expand', expand) const Expand = createLucideIcon('Expand', expand)
</script> </script>
@@ -66,8 +51,10 @@ const Expand = createLucideIcon('Expand', expand)
v-if="icon.createdRelease" v-if="icon.createdRelease"
class="version" class="version"
:href="releaseTagLink(icon.createdRelease.version)" :href="releaseTagLink(icon.createdRelease.version)"
target="_blank"
rel="noreferrer noopener"
>v{{ icon.createdRelease.version }}</Badge> >v{{ icon.createdRelease.version }}</Badge>
<IconButton @click="go(icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}` : `/icons/${icon.name}`)"> <IconButton @click="go(`/icons/${icon.name}`)">
<component :is="Expand" /> <component :is="Expand" />
</IconButton> </IconButton>
<IconButton @click="onClose"> <IconButton @click="onClose">
@@ -157,11 +144,11 @@ const Expand = createLucideIcon('Expand', expand)
} }
.drawer-enter-active { .drawer-enter-active {
transition: opacity 0.5s, transform 0.25s ease; transition: all 0.2s cubic-bezier(.21,.8,.46,.9);
} }
.drawer-leave-active { .drawer-leave-active {
transition: opacity 0.25s ease, transform 1.6s ease-out; transition: all 0.4s cubic-bezier(1, 0.5, 0.8, 1);
} }
.drawer-enter-from, .drawer-enter-from,

View File

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

View File

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

View File

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

View File

@@ -1,53 +1,56 @@
<script lang="ts">
import { IconEntity } from '../../types';
type CategoryNameRow = {
type: 'category';
title: string;
name: string;
};
type CategoryIconsRow = {
type: 'icons';
icons: IconEntity[];
};
export type CategoryRow = CategoryNameRow | CategoryIconsRow;
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue';
import { Category } from '../../types';
import IconGrid from './IconGrid.vue' import IconGrid from './IconGrid.vue'
import { vIntersectionObserver } from '@vueuse/components'
defineProps<{ defineProps<{
activeIconName: string | null activeIconName: string
categoryRow: CategoryRow category: Category
}>() }>()
const emit = defineEmits(['setActiveIcon']) const emit = defineEmits(['setActiveIcon'])
const showIcons = ref(false)
// Added intersection observer to improve performance
const onIntersectionObserver: IntersectionObserverCallback = ([{ isIntersecting }]) => {
showIcons.value = isIntersecting
}
</script> </script>
<template> <template>
<h2 <section
v-if="categoryRow.type === 'category'" class="category"
class="title" :key="category.name"
:id="category.name"
v-intersection-observer="onIntersectionObserver"
> >
<a class="header-anchor" :href="`#${categoryRow.name}`" :aria-label="`Permalink to &quot;${categoryRow.title}&quot;`">&ZeroWidthSpace;</a> <h2 class="title" >
{{ categoryRow.title }} <a class="header-anchor" :href="`#${category.name}`" :aria-label="`Permalink to &quot;${category.title}&quot;`">&ZeroWidthSpace;</a>
</h2> {{ category.title }}
<IconGrid </h2>
v-else-if="categoryRow.type === 'icons'" <IconGrid
:activeIcon="activeIconName" :activeIcon="activeIconName"
:icons="categoryRow.icons" :icons="category.icons"
@setActiveIcon="$event => $emit('setActiveIcon', $event)" @setActiveIcon="$event => $emit('setActiveIcon', $event)"
overlayMode overlayMode
/> :hideIcons="!showIcons"
/>
</section>
</template> </template>
<style scoped> <style scoped>
.title { .title {
margin-bottom: 8px; margin-bottom: 24px;
font-size: 19px; font-size: 19px;
font-weight: 500; font-weight: 500;
padding: 24px 0 8px; padding-top: 86px;
/* scroll-padding-top: 240px; */
}
.category {
margin-bottom: calc(-86px + 32px);
} }
</style> </style>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, defineAsyncComponent, onMounted, watch, watchEffect } from 'vue'; import { ref, computed, defineAsyncComponent } from 'vue';
import type { IconEntity, Category } from '../../types'; import type { IconEntity, Category } from '../../types';
import useSearch from '../../composables/useSearch'; import useSearch from '../../composables/useSearch';
import InputSearch from '../base/InputSearch.vue'; import InputSearch from '../base/InputSearch.vue';
@@ -8,14 +8,6 @@ import StickyBar from './StickyBar.vue';
import IconsCategory from './IconsCategory.vue'; import IconsCategory from './IconsCategory.vue';
import useFetchTags from '../../composables/useFetchTags'; import useFetchTags from '../../composables/useFetchTags';
import useFetchCategories from '../../composables/useFetchCategories'; import useFetchCategories from '../../composables/useFetchCategories';
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
import chunkArray from '../../utils/chunkArray';
import { CategoryRow } from './IconsCategory.vue';
import useScrollToCategory from '../../composables/useScrollToCategory';
import CarbonAdOverlay from './CarbonAdOverlay.vue';
const ICON_SIZE = 56;
const ICON_GRID_GAP = 8;
const props = defineProps<{ const props = defineProps<{
icons: IconEntity[]; icons: IconEntity[];
@@ -25,6 +17,7 @@ const props = defineProps<{
const activeIconName = ref(null); const activeIconName = ref(null);
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput(); const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
const isSearching = computed(() => !!searchQuery.value); const isSearching = computed(() => !!searchQuery.value);
function setActiveIconName(name: string) { function setActiveIconName(name: string) {
@@ -34,13 +27,6 @@ function setActiveIconName(name: string) {
const { execute: fetchTags, data: tags } = useFetchTags(); const { execute: fetchTags, data: tags } = useFetchTags();
const { execute: fetchCategories, data: categoriesMap } = useFetchCategories(); const { execute: fetchCategories, data: categoriesMap } = useFetchCategories();
const overviewEl = ref<HTMLElement | null>(null);
const { width: containerWidth } = useElementSize(overviewEl)
const columnSize = computed(() => {
return Math.floor((containerWidth.value) / ((ICON_SIZE + ICON_GRID_GAP)));
});
const mappedIcons = computed(() => { const mappedIcons = computed(() => {
if (tags.value == null) { if (tags.value == null) {
return props.icons; return props.icons;
@@ -57,10 +43,9 @@ const mappedIcons = computed(() => {
}); });
}); });
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [ const searchResults = useSearch(searchQuery, mappedIcons, [
{ name: 'name', weight: 3 }, { name: 'name', weight: 2 },
{ name: 'aliases', weight: 3 }, { name: 'tags', weight: 1 },
{ name: 'tags', weight: 2 },
]); ]);
const categories = computed(() => { const categories = computed(() => {
@@ -69,7 +54,7 @@ const categories = computed(() => {
return props.categories return props.categories
.map(({ name, title }) => { .map(({ name, title }) => {
const categoryIcons = props.icons.filter((icon) => { const categoryIcons = props.icons.filter((icon) => {
const iconCategories = icon?.externalLibrary ? icon.categories : props.iconCategories[icon.name] const iconCategories = props.iconCategories[icon.name];
return iconCategories?.includes(name); return iconCategories?.includes(name);
}); });
@@ -86,43 +71,9 @@ const categories = computed(() => {
icons: searchedCategoryIcons, icons: searchedCategoryIcons,
}; };
}) })
.filter(({ icons }) => icons.length);
}); });
const categoriesList = computed(() => {
return categories.value
.filter(({ icons }) => icons.length)
.reduce<CategoryRow[]>((acc, category) => {
acc.push({ type: 'category', title: category.title, name: category.name });
const categoryIcons = chunkArray(category.icons, columnSize.value);
categoryIcons.forEach((icons) => {
acc.push({ type: 'icons', icons });
});
return acc;
}, []);
});
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
categoriesList,
{
itemHeight: ICON_SIZE + ICON_GRID_GAP,
overscan: 10
},
)
useScrollToCategory({
categories,
categoriesList,
scrollTo,
searchQueryDebounced,
})
onMounted(() => {
containerProps.ref.value = document.documentElement;
useEventListener(window, 'scroll', containerProps.onScroll)
})
function onFocusSearchInput() { function onFocusSearchInput() {
if (tags.value == null) { if (tags.value == null) {
fetchTags(); fetchTags();
@@ -133,54 +84,33 @@ function onFocusSearchInput() {
} }
const NoResults = defineAsyncComponent(() => import('./NoResults.vue')); const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue')); const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
function handleCloseDrawer() {
setActiveIconName('');
window.history.pushState({}, '', '/icons/categories');
}
watchEffect(() => {
console.log(props.icons.find((icon) => icon.name === 'burger'));
});
</script> </script>
<template> <template>
<div ref="overviewEl" class="overview-container"> <StickyBar class="search-bar category-search">
<StickyBar class="category-search"> <InputSearch
<InputSearch :placeholder="`Search ${icons.length} icons ...`"
:placeholder="`Search ${icons.length} icons ...`" v-model="searchQuery"
v-model="searchQuery" class="input-wrapper"
class="input-wrapper" ref="searchInput"
ref="searchInput" @focus="onFocusSearchInput"
@focus="onFocusSearchInput"
/>
</StickyBar>
<NoResults
v-if="categories.length === 0"
:searchQuery="searchQuery"
@clear="searchQuery = ''"
/> />
<div v-bind="wrapperProps"> </StickyBar>
<IconsCategory <NoResults v-if="categories.length === 0" :searchQuery="searchQuery" @clear="searchQuery = ''" />
v-for="{ index, data } in list" <IconsCategory
:categoryRow="data" v-for="category in categories"
:activeIconName="activeIconName" :key="category.name"
@setActiveIcon="setActiveIconName" :category="category"
:key="index" :activeIconName="activeIconName"
/> @setActiveIcon="setActiveIconName"
</div> />
</div>
<IconDetailOverlay <IconDetailOverlay
v-if="activeIconName != null" v-if="activeIconName != null"
:iconName="activeIconName" :iconName="activeIconName"
@close="handleCloseDrawer" @close="setActiveIconName('')"
/> />
<CarbonAdOverlay :drawerOpen="!!activeIconName" />
</template> </template>
<style scoped> <style scoped>
@@ -189,21 +119,6 @@ watchEffect(() => {
} }
.search-bar.category-search { .search-bar.category-search {
margin-bottom: 10px; margin-bottom: -54px;
}
.title {
margin-bottom: 8px;
font-size: 19px;
font-weight: 500;
padding: 24px 0 8px;
}
.icons {
margin-bottom: 8px;
}
.overview-container {
padding-bottom: 288px;
} }
</style> </style>

View File

@@ -1,19 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, defineAsyncComponent, onMounted, watch } from 'vue'; import { ref, computed, watch, defineAsyncComponent } from 'vue';
import type { IconEntity } from '../../types'; import type { IconEntity } from '../../types';
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core'; import { useMediaQuery, useOffsetPagination } from '@vueuse/core';
import IconGrid from './IconGrid.vue'; import IconGrid from './IconGrid.vue';
import InputSearch from '../base/InputSearch.vue'; import InputSearch from '../base/InputSearch.vue';
import useSearch from '../../composables/useSearch'; import useSearch from '../../composables/useSearch';
import EndOfPage from '../base/EndOfPage.vue';
import useSearchInput from '../../composables/useSearchInput'; import useSearchInput from '../../composables/useSearchInput';
import StickyBar from './StickyBar.vue'; import StickyBar from './StickyBar.vue';
import useFetchTags from '../../composables/useFetchTags'; import useFetchTags from '../../composables/useFetchTags';
import useFetchCategories from '../../composables/useFetchCategories'; import useFetchCategories from '../../composables/useFetchCategories';
import chunkArray from '../../utils/chunkArray';
import CarbonAdOverlay from './CarbonAdOverlay.vue';
const ICON_SIZE = 56;
const ICON_GRID_GAP = 8;
const props = defineProps<{ const props = defineProps<{
icons: IconEntity[]; icons: IconEntity[];
@@ -21,16 +17,32 @@ const props = defineProps<{
const activeIconName = ref(null); const activeIconName = ref(null);
const isExtraLargeScreen = useMediaQuery('(min-width: 1440px)');
const isLargeScreen = useMediaQuery('(min-width: 1280px)');
const isMediumScreen = useMediaQuery('(min-width: 960px)');
const isSmallScreen = useMediaQuery('(min-width: 640px)');
const pageSize = computed(() => {
if (isExtraLargeScreen.value) {
return 16 * 20;
}
if (isLargeScreen.value) {
return 16 * 12;
}
if (isMediumScreen.value) {
return 13 * 12;
}
if (isSmallScreen.value) {
return 10 * 10;
}
return 10 * 5;
});
const { execute: fetchTags, data: tags } = useFetchTags(); const { execute: fetchTags, data: tags } = useFetchTags();
const { execute: fetchCategories, data: categories } = useFetchCategories(); const { execute: fetchCategories, data: categories } = useFetchCategories();
const overviewEl = ref<HTMLElement | null>(null);
const { width: containerWidth } = useElementSize(overviewEl)
const columnSize = computed(() => {
return Math.floor((containerWidth.value) / ((ICON_SIZE + ICON_GRID_GAP)));
});
const mappedIcons = computed(() => { const mappedIcons = computed(() => {
if (tags.value == null) { if (tags.value == null) {
return props.icons; return props.icons;
@@ -51,33 +63,26 @@ const mappedIcons = computed(() => {
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput(); const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [ const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
{ name: 'name', weight: 3 }, { name: 'name', weight: 3 },
{ name: 'aliases', weight: 3 },
{ name: 'tags', weight: 2 }, { name: 'tags', weight: 2 },
{ name: 'categories', weight: 1 }, { name: 'categories', weight: 1 },
]); ]);
const chunkedIcons = computed(() => { const { next, currentPage } = useOffsetPagination({ pageSize });
return chunkArray(searchResults.value, columnSize.value);
const paginatedIcons = computed(() => {
const end = pageSize.value * currentPage.value;
return searchResults.value.slice(0, end);
}); });
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
chunkedIcons,
{
itemHeight: ICON_SIZE + ICON_GRID_GAP,
overscan: 10
},
)
onMounted(() => {
containerProps.ref.value = document.documentElement;
useEventListener(window, 'scroll', containerProps.onScroll)
})
function setActiveIconName(name: string) { function setActiveIconName(name: string) {
activeIconName.value = name; activeIconName.value = name;
} }
watch(searchQueryDebounced, (searchString) => {
currentPage.value = 1;
});
function onFocusSearchInput() { function onFocusSearchInput() {
if (tags.value == null) { if (tags.value == null) {
fetchTags(); fetchTags();
@@ -90,57 +95,43 @@ function onFocusSearchInput() {
const NoResults = defineAsyncComponent(() => import('./NoResults.vue')); const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue')); const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
watch(searchQueryDebounced, () => {
scrollTo(0)
})
function handleCloseDrawer() {
setActiveIconName('');
window.history.pushState({}, '', '/icons/');
}
</script> </script>
<template> <template>
<div ref="overviewEl" class="overview-container"> <StickyBar>
<StickyBar> <InputSearch
<InputSearch :placeholder="`Search ${icons.length} icons ...`"
:placeholder="`Search ${icons.length} icons ...`" v-model="searchQuery"
v-model="searchQuery" ref="searchInput"
ref="searchInput" class="input-wrapper"
class="input-wrapper" @focus="onFocusSearchInput"
@focus="onFocusSearchInput"
/>
</StickyBar>
<NoResults
v-if="list.length === 0"
:searchQuery="searchQuery"
@clear="searchQuery = ''"
/> />
<div v-bind="wrapperProps" class="icon"> </StickyBar>
<IconGrid <NoResults
v-for="{ index, data: icons } in list" v-if="paginatedIcons.length === 0"
:key="index" :searchQuery="searchQuery"
overlayMode @clear="searchQuery = ''"
:icons="icons" />
:activeIcon="activeIconName" <IconGrid
@setActiveIcon="setActiveIconName" overlayMode
/> :activeIcon="activeIconName"
</div> :icons="paginatedIcons"
</div> @setActiveIcon="setActiveIconName"
/>
<IconDetailOverlay <EndOfPage @end-of-page="next" class="bottom-page" />
:iconName="activeIconName" <IconDetailOverlay
@close="handleCloseDrawer" v-if="activeIconName != null"
:iconName="activeIconName"
@close="setActiveIconName('')"
/> />
<CarbonAdOverlay :drawerOpen="!!activeIconName" />
</template> </template>
<style> <style>
.icons { .icons {
margin-bottom: 8px; display: grid;
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
gap: 8px;
width: 100%;
} }
.icon { .icon {
@@ -151,7 +142,7 @@ function handleCloseDrawer() {
width: 100%; width: 100%;
} }
.overview-container { .bottom-page {
padding-bottom: 288px; height: 288px;
} }
</style> </style>

View File

@@ -1,10 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed } from 'vue' import {ref} from 'vue'
import { bird, squirrel, rabbit } from '../../../data/iconNodes' import {bird} from '../../../data/iconNodes'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon' import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
import {useEventListener} from '@vueuse/core' import {useEventListener} from '@vueuse/core'
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue' import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
import { IconNode } from '../../types'
defineProps<{ defineProps<{
searchQuery: string searchQuery: string
@@ -12,48 +11,32 @@ defineProps<{
defineEmits(['clear']) defineEmits(['clear'])
const animalIcon = ref<HTMLElement>() const birdIcon = ref<HTMLElement>()
const randomAnimal = computed<IconNode>(() => { const Bird = createLucideIcon('bird', bird)
return Math.random() > 0.5 ? squirrel : Math.random() > 0.5 ? rabbit : bird
})
const animalComponent = computed(() => createLucideIcon('animal', randomAnimal.value))
const flip = ref(false) const flip = ref(false)
onMounted(() => { useEventListener(document, 'mousemove', (mouseEvent) => {
useEventListener(document, 'mousemove', (mouseEvent) => { const {width, height, x, y} = birdIcon.value.getBoundingClientRect()
const {width, height, x, y} = animalIcon.value.getBoundingClientRect()
const centerX = (width / 2) + x const centerX = (width / 2) + x
flip.value = mouseEvent.x < centerX flip.value = mouseEvent.x < centerX
})
}) })
</script> </script>
<template> <template>
<div class="no-results"> <div class="no-results">
<component <Bird class="bird-icon" ref="birdIcon" :class="{ flip }" :strokeWidth="1"/>
:is="animalComponent"
class="animal-icon"
ref="animalIcon"
:class="{ flip }"
:strokeWidth="1"
/>
<h2 class="no-results-text"> <h2 class="no-results-text">
No icons found for '{{ searchQuery }}' No icons found for '{{ searchQuery }}'
</h2> </h2>
<VPButton <VPButton text="Clear your search and try again" theme="alt" @click="$emit('clear')"/>
text="Clear your search and try again"
theme="alt"
@click="$emit('clear')"
/>
<span class="text-divider">or</span> <span class="text-divider">or</span>
<VPButton <VPButton text="Search on Github issues"
text="Search on Github issues" theme="alt"
theme="alt" :href="`https://github.com/lucide-icons/lucide/issues?q=is%3Aopen+${searchQuery}`"
:href="`https://github.com/lucide-icons/lucide/issues?q=is%3Aopen+${searchQuery}`" target="_blank"
target="_blank"
/> />
</div> </div>
</template> </template>
@@ -65,7 +48,7 @@ onMounted(() => {
align-items: center; align-items: center;
} }
.animal-icon { .bird-icon {
width: 160px; width: 160px;
height: 160px; height: 160px;
color: var(--vp-c-neutral); color: var(--vp-c-neutral);
@@ -73,12 +56,12 @@ onMounted(() => {
margin-top: 72px; margin-top: 72px;
} }
.animal-icon.flip { .bird-icon.flip {
transform: rotateY(180deg); transform: rotateY(180deg);
} }
@media (min-width: 960px) { @media (min-width: 960px) {
.animal-icon { .bird-icon {
width: 240px; width: 240px;
height: 240px; height: 240px;
} }

View File

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

View File

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

View File

@@ -12,7 +12,7 @@
.confetti-button:before, .confetti-button:before,
.confetti-button:after { .confetti-button:after {
position: absolute; position: absolute;
content: ''; content: "";
display: block; display: block;
width: 140%; width: 140%;
max-width: 160px; max-width: 160px;
@@ -41,16 +41,8 @@
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%), radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%), radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%); radial-gradient(circle, var(--confetti-color) 20%, transparent 20%);
background-size: background-size: 10% 10%, 20% 20%, 15% 15%, 20% 20%, 18% 18%, 10% 10%, 15% 15%,
10% 10%, 10% 10%, 18% 18%;
20% 20%,
15% 15%,
20% 20%,
18% 18%,
10% 10%,
15% 15%,
10% 10%,
18% 18%;
} }
.confetti-button:after { .confetti-button:after {
@@ -63,14 +55,7 @@
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%), radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%), radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%); radial-gradient(circle, var(--confetti-color) 20%, transparent 20%);
background-size: background-size: 15% 15%, 20% 20%, 18% 18%, 20% 20%, 15% 15%, 10% 10%, 20% 20%;
15% 15%,
20% 20%,
18% 18%,
20% 20%,
15% 15%,
10% 10%,
20% 20%;
} }
.confetti-button.animate:before { .confetti-button.animate:before {
@@ -85,89 +70,35 @@
@keyframes topBubbles { @keyframes topBubbles {
0% { 0% {
color: rgb(var(--text-color) / 0); color: rgb(var(--text-color) / 0);
background-position: background-position: 5% 90%, 10% 90%, 10% 90%, 15% 90%, 25% 90%, 25% 90%,
5% 90%, 40% 90%, 55% 90%, 70% 90%;
10% 90%,
10% 90%,
15% 90%,
25% 90%,
25% 90%,
40% 90%,
55% 90%,
70% 90%;
} }
30% { 30% {
color: rgb(var(--text-color) / 1); color: rgb(var(--text-color) / 1);
} }
50% { 50% {
background-position: background-position: 0% 80%, 0% 20%, 10% 40%, 20% 0%, 30% 30%, 22% 50%,
0% 80%, 50% 50%, 65% 20%, 90% 30%;
0% 20%,
10% 40%,
20% 0%,
30% 30%,
22% 50%,
50% 50%,
65% 20%,
90% 30%;
} }
100% { 100% {
background-position: background-position: 0% 70%, 0% 10%, 10% 30%, 20% -10%, 30% 20%, 22% 40%,
0% 70%, 50% 40%, 65% 10%, 90% 20%;
0% 10%, background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%;
10% 30%,
20% -10%,
30% 20%,
22% 40%,
50% 40%,
65% 10%,
90% 20%;
background-size:
0% 0%,
0% 0%,
0% 0%,
0% 0%,
0% 0%,
0% 0%;
color: rgb(var(--text-color) / 0); color: rgb(var(--text-color) / 0);
} }
} }
@keyframes bottomBubbles { @keyframes bottomBubbles {
0% { 0% {
background-position: background-position: 10% -10%, 30% 10%, 55% -10%, 70% -10%, 85% -10%,
10% -10%, 70% -10%, 70% 0%;
30% 10%,
55% -10%,
70% -10%,
85% -10%,
70% -10%,
70% 0%;
} }
50% { 50% {
background-position: background-position: 0% 80%, 20% 80%, 45% 60%, 60% 100%, 75% 70%, 95% 60%,
0% 80%,
20% 80%,
45% 60%,
60% 100%,
75% 70%,
95% 60%,
105% 0%; 105% 0%;
} }
100% { 100% {
background-position: background-position: 0% 90%, 20% 90%, 45% 70%, 60% 110%, 75% 80%, 95% 70%,
0% 90%,
20% 90%,
45% 70%,
60% 110%,
75% 80%,
95% 70%,
110% 10%; 110% 10%;
background-size: background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%;
0% 0%,
0% 0%,
0% 0%,
0% 0%,
0% 0%,
0% 0%;
} }
} }

View File

@@ -1,22 +1,21 @@
import packageData from '../../../data/packageData.json'; import packageData from '../../../data/packageData.json';
import thirdPartyPackages from '../../../data/packageData.thirdParty.json'; import thirdPartyPackages from '../../../data/packageData.thirdParty.json';
import fetchPackages from '../../../lib/fetchPackages'; import fetchPackages from "../../../lib/fetchPackages";
export default { export default {
async load() { async load() {
const packages = await fetchPackages(); const packages = await fetchPackages();
return { return {
packages: packages packages: packages
.filter((p) => p.name in packageData) .filter(p => p.name in packageData)
.map((pData) => ({ .map((pData) => ({
...pData, ...pData,
...packageData[pData.name], ...packageData[pData.name],
documentation: `/guide/packages/${pData.name}`, documentation: `/guide/packages/${pData.name}`,
source: `https://github.com/lucide-icons/lucide/tree/main/packages/${pData.name}`, source: `https://github.com/lucide-icons/lucide/tree/main/packages/${pData.name}`,
icon: `/framework-logos/${packageData[pData.name].icon}.svg`, icon: `/framework-logos/${packageData[pData.name].icon}.svg`,
})) })).sort((a, b) => a.order - b.order),
.sort((a, b) => a.order - b.order),
thirdPartyPackages, thirdPartyPackages,
}; };
}, }
}; }

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { data } from './PackageList.data' import {data} from './PackageList.data'
import GridSection from '../base/GridSection.vue' import GridSection from '../base/GridSection.vue'
import PackageListItem from "./PackageListItem.vue";</script> import PackageListItem from "./PackageListItem.vue";</script>
@@ -38,4 +38,33 @@ import PackageListItem from "./PackageListItem.vue";</script>
.package-group { .package-group {
margin-bottom: 96px; margin-bottom: 96px;
} }
.grid {
display: flex;
flex-wrap: wrap;
align-items: stretch;
justify-content: center;
align-content: space-evenly;
box-sizing: border-box;
margin: -8px;
}
.grid > * {
flex-basis: 100%;
box-sizing: border-box;
padding: 8px;
}
@media (min-width: 960px) {
.grid > * {
flex-basis: 50%;
}
}
@media (min-width: 1280px) {
.grid > * {
flex-basis: 33.33%;
}
}
</style> </style>

View File

@@ -40,4 +40,33 @@ import ShowcaseListItem from "./ShowcaseListItem.vue";
.package-group { .package-group {
margin-bottom: 96px; margin-bottom: 96px;
} }
.grid {
display: flex;
flex-wrap: wrap;
align-items: stretch;
justify-content: center;
align-content: space-evenly;
box-sizing: border-box;
margin: -8px;
}
.grid > * {
flex-basis: 100%;
box-sizing: border-box;
padding: 8px;
}
@media (min-width: 960px) {
.grid > * {
flex-basis: 50%;
}
}
@media (min-width: 1280px) {
.grid > * {
flex-basis: 33.33%;
}
}
</style> </style>

View File

@@ -1,76 +1,73 @@
import { onMounted, onUpdated, onUnmounted } from 'vue'; import { onMounted, onUpdated, onUnmounted } from 'vue';
import { throttleAndDebounce } from 'vitepress/dist/client/theme-default/support/utils'; import { throttleAndDebounce } from 'vitepress/dist/client/theme-default/support/utils'
/* /*
* This file is compied and adjusted from vitepress/dist/client/theme-default/composables/useActiveAnchor.ts * This file is compied and adjusted from vitepress/dist/client/theme-default/composables/useActiveAnchor.ts
*/ */
export function useActiveAnchor(container, marker) { export function useActiveAnchor(container, marker) {
const setActiveLinkDebounced = throttleAndDebounce(setActiveLink, 100); const onScroll = throttleAndDebounce(setActiveLink, 100);
let prevActiveLink = null; let prevActiveLink = null;
onMounted(() => { onMounted(() => {
requestAnimationFrame(setActiveLink); requestAnimationFrame(setActiveLink);
window.addEventListener('scroll', setActiveLinkDebounced); window.addEventListener('scroll', onScroll);
}); });
onUpdated(() => { onUpdated(() => {
// sidebar update means a route change // sidebar update means a route change
activateLink(location.hash); activateLink(location.hash);
}); });
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('scroll', setActiveLinkDebounced); window.removeEventListener('scroll', onScroll);
}); });
function setActiveLink() { function setActiveLink() {
const links = [].slice.call(container.value.querySelectorAll('.outline-link')); const links = [].slice.call(container.value.querySelectorAll('.outline-link'));
const anchors = [].slice const anchors = [].slice
.call(document.querySelectorAll('.content .header-anchor')) .call(document.querySelectorAll('.content .header-anchor'))
.filter((anchor) => { .filter((anchor) => {
return links.some((link) => { return links.some((link) => {
return link.hash === anchor.hash && anchor.offsetParent !== null; return link.hash === anchor.hash && anchor.offsetParent !== null;
}); });
}); });
const scrollY = window.scrollY; const scrollY = window.scrollY;
const innerHeight = window.innerHeight; const innerHeight = window.innerHeight;
const offsetHeight = document.body.offsetHeight; const offsetHeight = document.body.offsetHeight;
const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1; const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1;
// page bottom - highlight last one // page bottom - highlight last one
if (anchors.length && isBottom) { if (anchors.length && isBottom) {
activateLink(anchors[anchors.length - 1].hash); activateLink(anchors[anchors.length - 1].hash);
return; return;
} }
for (let i = 0; i < anchors.length; i++) { for (let i = 0; i < anchors.length; i++) {
const anchor = anchors[i]; const anchor = anchors[i];
const nextAnchor = anchors[i + 1]; const nextAnchor = anchors[i + 1];
const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor); const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor);
if (isActive) { if (isActive) {
activateLink(hash); activateLink(hash);
return; return;
}
} }
}
} }
function activateLink(hash) { function activateLink(hash) {
if (prevActiveLink) { if (prevActiveLink) {
prevActiveLink.classList.remove('active'); prevActiveLink.classList.remove('active');
} }
if (hash !== null) { if (hash !== null) {
prevActiveLink = container.value.querySelector(`a[href="${decodeURIComponent(hash)}"]`); prevActiveLink = container.value.querySelector(`a[href="${decodeURIComponent(hash)}"]`);
} }
const activeLink = prevActiveLink; const activeLink = prevActiveLink;
if (activeLink) { if (activeLink) {
activeLink.classList.add('active'); activeLink.classList.add('active');
marker.value.style.top = activeLink.offsetTop + 5 + 'px'; marker.value.style.top = activeLink.offsetTop + 5 + 'px';
marker.value.style.opacity = '1'; marker.value.style.opacity = '1';
} else { }
marker.value.style.top = '33px'; else {
marker.value.style.opacity = '0'; marker.value.style.top = '33px';
} marker.value.style.opacity = '0';
}
} }
return {
setActiveLinkDebounced,
};
} }
const PAGE_OFFSET = 128; const PAGE_OFFSET = 64;
function getAnchorTop(anchor) { function getAnchorTop(anchor) {
return anchor.parentElement.offsetTop - PAGE_OFFSET; return anchor.parentElement.offsetTop - PAGE_OFFSET;
@@ -78,13 +75,13 @@ function getAnchorTop(anchor) {
function isAnchorActive(index, anchor, nextAnchor) { function isAnchorActive(index, anchor, nextAnchor) {
const scrollTop = window.scrollY; const scrollTop = window.scrollY;
if (index === 0 && scrollTop === 0) { if (index === 0 && scrollTop === 0) {
return [true, anchor.hash]; return [true, null];
} }
if (scrollTop < getAnchorTop(anchor)) { if (scrollTop < getAnchorTop(anchor)) {
return [false, null]; return [false, null];
} }
if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor)) { if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor)) {
return [true, anchor.hash]; return [true, anchor.hash];
} }
return [false, null]; return [false, null];
} }

View File

@@ -1,38 +1,25 @@
import { useRoute } from 'vitepress'; import {
import { ref, inject, Ref, onMounted, watch } from 'vue'; ref, inject, Ref
} from 'vue';
export const CATEGORY_VIEW_CONTEXT = Symbol('categoryView'); export const CATEGORY_VIEW_CONTEXT = Symbol('categoryView');
interface CategoryViewContext { interface CategoryViewContext {
selectedCategory: Ref<string>; selectedCategory: Ref<string>
categoryCounts: Ref<Record<string, number>>; categoryCounts: Ref<Record<string, number>>
} }
export const categoryViewContext = { export const categoryViewContext = {
selectedCategory: ref(), selectedCategory: ref(''),
categoryCounts: ref({}), categoryCounts: ref({}),
}; };
export function useCategoryView(): CategoryViewContext { export function useCategoryView(): CategoryViewContext {
const context = inject<CategoryViewContext>(CATEGORY_VIEW_CONTEXT); const context = inject<CategoryViewContext>(CATEGORY_VIEW_CONTEXT);
const route = useRoute();
if (!context) { if (!context) {
throw new Error('useCategoryView must be used with categoryView context'); throw new Error('useCategoryView must be used with categoryView context');
} }
onMounted(() => {
if (window.location.hash) {
context.selectedCategory.value = decodeURIComponent(window.location.hash.slice(1));
}
});
watch(route, (currentRoute) => {
if (currentRoute.path !== '/icons/categories') {
context.selectedCategory.value = '';
context.categoryCounts.value = {};
}
});
return context; return context;
} }

View File

@@ -1,8 +1,8 @@
import { ref } from 'vue'; import { ref } from "vue";
export default function useConfetti() { export default function useConfetti() {
const animate = ref(false); const animate = ref(false)
const confettiText = ref('confetti!'); const confettiText = ref('confetti!')
function confetti() { function confetti() {
animate.value = true; animate.value = true;
@@ -15,6 +15,6 @@ export default function useConfetti() {
return { return {
animate, animate,
confetti, confetti,
confettiText, confettiText
}; }
} }

View File

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

View File

@@ -1,12 +1,12 @@
import { useFetch } from '@vueuse/core'; import { useFetch } from "@vueuse/core"
const useFetchCategories = () => const useFetchCategories = () => useFetch<Record<string, string[]>>(
useFetch<Record<string, string[]>>( `${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/categories`,
`${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/categories`, {
{ immediate:
immediate: typeof window !== 'undefined'
typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('search'), && new URLSearchParams(window.location.search).has('search'),
}, }
).json(); ).json()
export default useFetchCategories; export default useFetchCategories

View File

@@ -1,12 +1,12 @@
import { useFetch } from '@vueuse/core'; import { useFetch } from "@vueuse/core"
const useFetchTags = () => const useFetchTags = () => useFetch<Record<string, string[]>>(
useFetch<Record<string, string[]>>( `${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/tags`,
`${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/tags`, {
{ immediate:
immediate: typeof window !== 'undefined'
typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('search'), && new URLSearchParams(window.location.search).has('search'),
}, }
).json(); ).json()
export default useFetchTags; export default useFetchTags

View File

@@ -1,14 +1,16 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import { ref, inject, Ref } from 'vue'; import {
ref, inject, Ref
} from 'vue';
export const ICON_STYLE_CONTEXT = Symbol('size'); export const ICON_STYLE_CONTEXT = Symbol('size');
interface IconSizeContext { interface IconSizeContext {
size: Ref<number>; size: Ref<number>
strokeWidth: Ref<number>; strokeWidth: Ref<number>
color: Ref<string>; color: Ref<string>
absoluteStrokeWidth: Ref<boolean>; absoluteStrokeWidth: Ref<boolean>
} }
export const STYLE_DEFAULTS = { export const STYLE_DEFAULTS = {
@@ -25,7 +27,7 @@ export const iconStyleContext = {
absoluteStrokeWidth: ref(false), absoluteStrokeWidth: ref(false),
}; };
export function useIconStyleContext(): IconSizeContext { export function useIconStyleContext(): IconSizeContext{
const context = inject<IconSizeContext>(ICON_STYLE_CONTEXT); const context = inject<IconSizeContext>(ICON_STYLE_CONTEXT);
if (!context) { if (!context) {

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