mirror of
https://github.com/lucide-icons/lucide.git
synced 2025-12-17 13:17:41 +01:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8cf24e75a | ||
|
|
4e2773bd2d | ||
|
|
7c8f898893 | ||
|
|
c3951d36c7 | ||
|
|
f958310d91 | ||
|
|
d00cbebcc4 | ||
|
|
3619dfa7f2 | ||
|
|
c844bc668f | ||
|
|
32dec05e0b | ||
|
|
5394d7fceb | ||
|
|
8dea4c7b7f | ||
|
|
b8a595275d | ||
|
|
a7b3ecde63 | ||
|
|
8bcfc225ce | ||
|
|
1d608db223 | ||
|
|
c1cb5d9bc2 | ||
|
|
66f51474cd | ||
|
|
af0b16bc10 | ||
|
|
46d6c3c119 | ||
|
|
d3b8510602 | ||
|
|
97e0f30627 | ||
|
|
24676ddc8e | ||
|
|
92689a3328 | ||
|
|
b578269ecb | ||
|
|
d041a2b02a | ||
|
|
0253326b17 | ||
|
|
275c2cbc69 | ||
|
|
4bd6a273db | ||
|
|
34155d48e7 | ||
|
|
82db590192 | ||
|
|
70be55b78f | ||
|
|
7163aeaa6b | ||
|
|
99cd76bb35 | ||
|
|
f5fb1ec263 | ||
|
|
6916aebee4 | ||
|
|
65d213264f | ||
|
|
ee77147aff | ||
|
|
3b7b74fe86 | ||
|
|
3a2f052ce9 | ||
|
|
cf34d61971 | ||
|
|
2814a63b8f | ||
|
|
4bcab462dc | ||
|
|
6c93bb97c7 | ||
|
|
3c1993c463 | ||
|
|
7a57c306c3 | ||
|
|
32637199f5 | ||
|
|
e490bc35b8 | ||
|
|
496058cc15 | ||
|
|
4ee46673af | ||
|
|
5a46f4b87c | ||
|
|
875e8a2d06 | ||
|
|
e006a171c1 | ||
|
|
606706e8e0 | ||
|
|
ffc03ea1f6 | ||
|
|
b2e685262b | ||
|
|
5bfc736b61 | ||
|
|
2ebf99f591 | ||
|
|
7a17a2f343 | ||
|
|
4b5d343791 | ||
|
|
b19b01d323 |
8
.github/actions/build-and-test.yml
vendored
8
.github/actions/build-and-test.yml
vendored
@@ -24,15 +24,15 @@ runs:
|
|||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-lucide-preact-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-lucide-preact-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --filter lucide-preact
|
run: pnpm install --filter lucide-preact
|
||||||
|
|||||||
8
.github/actions/check-icons.yml
vendored
8
.github/actions/check-icons.yml
vendored
@@ -24,15 +24,15 @@ runs:
|
|||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-lucide-preact-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-lucide-preact-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --filter .
|
run: pnpm install --filter .
|
||||||
|
|||||||
62
.github/labeler.yml
vendored
Normal file
62
.github/labeler.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# For changed dependencies
|
||||||
|
📦 dependencies:
|
||||||
|
- pnpm-lock.yaml
|
||||||
|
|
||||||
|
# For changes in documentation
|
||||||
|
📖 documentation:
|
||||||
|
- docs/*.md
|
||||||
|
- docs/**/*.md
|
||||||
|
|
||||||
|
# For changes in the site, but not markdown files
|
||||||
|
🌍 site:
|
||||||
|
- 'docs/**'
|
||||||
|
|
||||||
|
# For changes in the metadata
|
||||||
|
🫧 metadata:
|
||||||
|
- 'icons/*.json'
|
||||||
|
- categories/*
|
||||||
|
|
||||||
|
# For changes or added icons
|
||||||
|
🎨 icon:
|
||||||
|
- 'icons/*.svg'
|
||||||
|
|
||||||
|
# For changes in the lucide package
|
||||||
|
🧳 lucide package:
|
||||||
|
- 'packages/lucide/*'
|
||||||
|
|
||||||
|
# For changes in the lucide React package
|
||||||
|
⚛️ react package:
|
||||||
|
- 'packages/lucide-react/*'
|
||||||
|
|
||||||
|
# For changes in the lucide React Native package
|
||||||
|
⚛️ react native package:
|
||||||
|
- 'packages/lucide-react-native/*'
|
||||||
|
|
||||||
|
# For changes in the lucide vue packages
|
||||||
|
💎 vue package:
|
||||||
|
- 'packages/lucide-vue/*'
|
||||||
|
- 'packages/lucide-vue-next/*'
|
||||||
|
|
||||||
|
# For changes in the lucide angular package
|
||||||
|
🅰️ angular package:
|
||||||
|
- 'packages/lucide-angular/*'
|
||||||
|
|
||||||
|
# For changes in the lucide preact package
|
||||||
|
⚛️ preact package:
|
||||||
|
- 'packages/lucide-preact/*'
|
||||||
|
|
||||||
|
# For changes in the lucide svelte package
|
||||||
|
🧣 svelte package:
|
||||||
|
- 'packages/lucide-svelte/*'
|
||||||
|
|
||||||
|
# For changes in the lucide solid package
|
||||||
|
🪝 solid package:
|
||||||
|
- 'packages/lucide-solid/*'
|
||||||
|
|
||||||
|
# For changes in the lucide static package
|
||||||
|
🪨 static package:
|
||||||
|
- 'packages/lucide-static/*'
|
||||||
|
|
||||||
|
# For changes in the lucide flutter package
|
||||||
|
🏹 flutter package:
|
||||||
|
- 'packages/lucide-flutter/*'
|
||||||
19
.github/workflows/ci.yml
vendored
19
.github/workflows/ci.yml
vendored
@@ -29,12 +29,12 @@ jobs:
|
|||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Get latest tag
|
- name: Get latest tag
|
||||||
id: latest-tag
|
id: latest-tag
|
||||||
run: echo "::set-output name=LATEST_TAG::$(git describe --tags `git rev-list --tags --max-count=1`)"
|
run: echo "LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
@@ -57,7 +57,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create new version
|
- name: Create new version
|
||||||
id: new-version
|
id: new-version
|
||||||
run: echo "::set-output name=NEW_VERSION::$(.github/workflows/version-up.sh --minor)"
|
run: echo "NEW_VERSION=$(.github/workflows/version-up.sh --minor)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Create change log
|
- name: Create change log
|
||||||
id: change-log
|
id: change-log
|
||||||
@@ -67,7 +67,7 @@ jobs:
|
|||||||
CHANGE_LOG="${CHANGE_LOG//$'\n'/'%0A'}"
|
CHANGE_LOG="${CHANGE_LOG//$'\n'/'%0A'}"
|
||||||
CHANGE_LOG="${CHANGE_LOG//$'\r'/'%0D'}"
|
CHANGE_LOG="${CHANGE_LOG//$'\r'/'%0D'}"
|
||||||
echo $CHANGE_LOG
|
echo $CHANGE_LOG
|
||||||
echo "::set-output name=CHANGE_LOG::$CHANGE_LOG"
|
echo "CHANGE_LOG=$CHANGE_LOG" >> $GITHUB_OUTPUT
|
||||||
env:
|
env:
|
||||||
GITHUB_API_KEY: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_API_KEY: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@@ -76,11 +76,14 @@ jobs:
|
|||||||
echo '${{ steps.new-version.outputs.NEW_VERSION }}'
|
echo '${{ steps.new-version.outputs.NEW_VERSION }}'
|
||||||
echo '${{ steps.change-log.outputs.CHANGE_LOG }}'
|
echo '${{ steps.change-log.outputs.CHANGE_LOG }}'
|
||||||
|
|
||||||
- name: Release
|
- name: Create Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.CREATE_RELEASE_TOKEN }}
|
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ steps.new-version.outputs.NEW_VERSION }}
|
tag_name: ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||||
name: New icons ${{ steps.new-version.outputs.NEW_VERSION }}
|
name: New icons ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||||
body: ${{ steps.change-log.outputs.CHANGE_LOG }}
|
body: ${{ steps.change-log.outputs.CHANGE_LOG }}
|
||||||
|
|
||||||
|
- name: Release packages
|
||||||
|
uses: './release.yml'
|
||||||
|
with:
|
||||||
|
version: ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||||
|
|||||||
12
.github/workflows/labeler.yml
vendored
Normal file
12
.github/workflows/labeler.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
name: "Pull Request Labeler"
|
||||||
|
on:
|
||||||
|
- pull_request_target
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
triage:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/labeler@v4
|
||||||
4
.github/workflows/lucide-angular.yml
vendored
4
.github/workflows/lucide-angular.yml
vendored
@@ -25,12 +25,12 @@ jobs:
|
|||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|||||||
8
.github/workflows/lucide-font.yml
vendored
8
.github/workflows/lucide-font.yml
vendored
@@ -26,15 +26,15 @@ jobs:
|
|||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-lucide-font-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-lucide-font-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --filter outline-svg
|
run: pnpm install --filter outline-svg
|
||||||
|
|||||||
4
.github/workflows/lucide-preact.yml
vendored
4
.github/workflows/lucide-preact.yml
vendored
@@ -25,12 +25,12 @@ jobs:
|
|||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|||||||
4
.github/workflows/lucide-react-native.yml
vendored
4
.github/workflows/lucide-react-native.yml
vendored
@@ -25,12 +25,12 @@ jobs:
|
|||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|||||||
4
.github/workflows/lucide-react.yml
vendored
4
.github/workflows/lucide-react.yml
vendored
@@ -25,12 +25,12 @@ jobs:
|
|||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|||||||
4
.github/workflows/lucide-solid.yml
vendored
4
.github/workflows/lucide-solid.yml
vendored
@@ -25,12 +25,12 @@ jobs:
|
|||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|||||||
4
.github/workflows/lucide-static.yml
vendored
4
.github/workflows/lucide-static.yml
vendored
@@ -25,12 +25,12 @@ jobs:
|
|||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|||||||
4
.github/workflows/lucide-svelte.yml
vendored
4
.github/workflows/lucide-svelte.yml
vendored
@@ -25,12 +25,12 @@ jobs:
|
|||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|||||||
4
.github/workflows/lucide-vue-next.yml
vendored
4
.github/workflows/lucide-vue-next.yml
vendored
@@ -25,12 +25,12 @@ jobs:
|
|||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|||||||
4
.github/workflows/lucide-vue.yml
vendored
4
.github/workflows/lucide-vue.yml
vendored
@@ -25,12 +25,12 @@ jobs:
|
|||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|||||||
4
.github/workflows/lucide.yml
vendored
4
.github/workflows/lucide.yml
vendored
@@ -25,12 +25,12 @@ jobs:
|
|||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|||||||
563
.github/workflows/release.yml
vendored
563
.github/workflows/release.yml
vendored
@@ -12,8 +12,8 @@ on:
|
|||||||
required: true
|
required: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre-build:
|
pre-release:
|
||||||
if: github.repository == 'lucide-icons/lucide' && contains('["locness3","ericfennis", "johnletey", "karsa-mistmere"]', github.actor)
|
if: github.repository == 'lucide-icons/lucide' && contains('["ericfennis", "karsa-mistmere"]', github.actor)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
VERSION: ${{ steps.get_version.outputs.VERSION }}
|
VERSION: ${{ steps.get_version.outputs.VERSION }}
|
||||||
@@ -23,19 +23,32 @@ jobs:
|
|||||||
id: get_version
|
id: get_version
|
||||||
run: |
|
run: |
|
||||||
echo $VERSION_REF
|
echo $VERSION_REF
|
||||||
echo ::set-output name=VERSION::${VERSION_REF/refs\/tags\/\v}
|
echo "VERSION=${VERSION_REF/refs\/tags\/\v}" >> $GITHUB_OUTPUT
|
||||||
env:
|
env:
|
||||||
VERSION_REF: ${{ github.event.inputs.version || github.ref }}
|
VERSION_REF: ${{ github.event.inputs.version || github.ref }}
|
||||||
|
|
||||||
lucide:
|
release:
|
||||||
if: github.repository == 'lucide-icons/lucide'
|
if: github.repository == 'lucide-icons/lucide'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: pre-build
|
needs: pre-release
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
package: [
|
||||||
|
'lucide',
|
||||||
|
'lucide-react',
|
||||||
|
'lucide-react-native',
|
||||||
|
'lucide-vue',
|
||||||
|
'lucide-vue-next',
|
||||||
|
'lucide-angular',
|
||||||
|
'lucide-preact',
|
||||||
|
'lucide-solid',
|
||||||
|
'lucide-svelte',
|
||||||
|
]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 18
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2.0.1
|
- uses: pnpm/action-setup@v2.0.1
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
@@ -47,484 +60,38 @@ jobs:
|
|||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Set Auth Token
|
- name: Set Auth Token
|
||||||
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
|
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
- name: Set new version
|
- name: Set new version
|
||||||
run: pnpm --filter lucide version --new-version ${{ needs.pre-build.outputs.VERSION }} --no-git-tag-version
|
run: pnpm --filter ${{ matrix.package }} version --new-version ${{ needs.pre-build.outputs.VERSION }} --no-git-tag-version
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm --filter lucide build
|
run: pnpm --filter ${{ matrix.package }} build
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm --filter lucide test
|
run: pnpm --filter ${{ matrix.package }} test
|
||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: pnpm --filter lucide publish --no-git-checks
|
run: pnpm --filter ${{ matrix.package }} publish --no-git-checks --ignore-scripts
|
||||||
|
|
||||||
- name: Upload package.json
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: lucide-package-json
|
|
||||||
path: packages/lucide/package.json
|
|
||||||
|
|
||||||
lucide-react:
|
|
||||||
if: github.repository == 'lucide-icons/lucide'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre-build
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2.0.1
|
|
||||||
name: Install pnpm
|
|
||||||
id: pnpm-install
|
|
||||||
with:
|
|
||||||
version: 7
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
id: pnpm-cache
|
|
||||||
run: |
|
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Set Auth Token
|
|
||||||
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set package.json version lucide
|
|
||||||
run: pnpm --filter lucide-react version --new-version ${{ needs.pre-build.outputs.VERSION }} --no-git-tag-version
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm --filter lucide-react build
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: pnpm --filter lucide-react test
|
|
||||||
|
|
||||||
- name: Publish
|
|
||||||
run: pnpm --filter lucide-react publish --no-git-checks
|
|
||||||
|
|
||||||
- name: Upload package.json
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: lucide-react-package-json
|
|
||||||
path: packages/lucide-react/package.json
|
|
||||||
|
|
||||||
lucide-react-native:
|
|
||||||
if: github.repository == 'lucide-icons/lucide'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre-build
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2.0.1
|
|
||||||
name: Install pnpm
|
|
||||||
id: pnpm-install
|
|
||||||
with:
|
|
||||||
version: 7
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
id: pnpm-cache
|
|
||||||
run: |
|
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Set Auth Token
|
|
||||||
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set package.json version lucide
|
|
||||||
run: pnpm --filter lucide-react-native version --new-version ${{ needs.pre-build.outputs.VERSION }} --no-git-tag-version
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm --filter lucide-react-native build
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: pnpm --filter lucide-react-native test
|
|
||||||
|
|
||||||
- name: Publish
|
|
||||||
run: pnpm --filter lucide-react-native publish --no-git-checks
|
|
||||||
|
|
||||||
- name: Upload package.json
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: lucide-react-native-package-json
|
|
||||||
path: packages/lucide-react-native/package.json
|
|
||||||
|
|
||||||
lucide-vue:
|
|
||||||
if: github.repository == 'lucide-icons/lucide'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre-build
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2.0.1
|
|
||||||
name: Install pnpm
|
|
||||||
id: pnpm-install
|
|
||||||
with:
|
|
||||||
version: 7
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
id: pnpm-cache
|
|
||||||
run: |
|
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Set Auth Token
|
|
||||||
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set new version
|
|
||||||
run: pnpm --filter lucide-vue version --new-version ${{ needs.pre-build.outputs.VERSION }} --no-git-tag-version
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm --filter lucide-vue build
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: pnpm --filter lucide-vue test
|
|
||||||
|
|
||||||
- name: Publish
|
|
||||||
run: pnpm --filter lucide-vue publish --no-git-checks
|
|
||||||
|
|
||||||
- name: Upload package.json
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: lucide-vue-package-json
|
|
||||||
path: packages/lucide-vue/package.json
|
|
||||||
|
|
||||||
lucide-vue-next:
|
|
||||||
if: github.repository == 'lucide-icons/lucide'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre-build
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2.0.1
|
|
||||||
name: Install pnpm
|
|
||||||
id: pnpm-install
|
|
||||||
with:
|
|
||||||
version: 7
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
id: pnpm-cache
|
|
||||||
run: |
|
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Set Auth Token
|
|
||||||
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set new version
|
|
||||||
run: pnpm --filter lucide-vue-next version --new-version ${{ needs.pre-build.outputs.VERSION }} --no-git-tag-version
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm --filter lucide-vue-next build
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: pnpm --filter lucide-vue-next test
|
|
||||||
|
|
||||||
- name: Publish
|
|
||||||
run: pnpm --filter lucide-vue-next publish --no-git-checks
|
|
||||||
|
|
||||||
- name: Upload package.json
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: lucide-vue-next-package-json
|
|
||||||
path: packages/lucide-vue-next/package.json
|
|
||||||
|
|
||||||
lucide-angular:
|
|
||||||
if: github.repository == 'lucide-icons/lucide'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre-build
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2.0.1
|
|
||||||
name: Install pnpm
|
|
||||||
id: pnpm-install
|
|
||||||
with:
|
|
||||||
version: 7
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
id: pnpm-cache
|
|
||||||
run: |
|
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Set Auth Token
|
|
||||||
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set package.json version lucide
|
|
||||||
run: pnpm --filter lucide-angular version --new-version ${{ needs.pre-build.outputs.VERSION }} --no-git-tag-version
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm --filter lucide-angular build
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: pnpm --filter lucide-angular test
|
|
||||||
|
|
||||||
- name: Publish
|
|
||||||
run: pnpm --filter lucide-angular publish --no-git-checks --ignore-scripts
|
|
||||||
|
|
||||||
- name: Upload package.json
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: lucide-angular-package-json
|
|
||||||
path: packages/lucide-angular/package.json
|
|
||||||
|
|
||||||
lucide-preact:
|
|
||||||
if: github.repository == 'lucide-icons/lucide'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre-build
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2.0.1
|
|
||||||
name: Install pnpm
|
|
||||||
id: pnpm-install
|
|
||||||
with:
|
|
||||||
version: 7
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
id: pnpm-cache
|
|
||||||
run: |
|
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Set Auth Token
|
|
||||||
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set package.json version lucide
|
|
||||||
run: pnpm --filter lucide-preact version --new-version ${{ needs.pre-build.outputs.VERSION }} --no-git-tag-version
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm --filter lucide-preact build
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: pnpm --filter lucide-preact test
|
|
||||||
|
|
||||||
- name: Publish
|
|
||||||
run: pnpm --filter lucide-preact publish --no-git-checks
|
|
||||||
|
|
||||||
- name: Upload package.json
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: lucide-preact-package-json
|
|
||||||
path: packages/lucide-preact/package.json
|
|
||||||
|
|
||||||
lucide-solid:
|
|
||||||
if: github.repository == 'lucide-icons/lucide'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre-build
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2.0.1
|
|
||||||
name: Install pnpm
|
|
||||||
id: pnpm-install
|
|
||||||
with:
|
|
||||||
version: 7
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
id: pnpm-cache
|
|
||||||
run: |
|
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Set Auth Token
|
|
||||||
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set package.json version lucide
|
|
||||||
run: pnpm --filter lucide-solid version --new-version ${{ needs.pre-build.outputs.VERSION }} --no-git-tag-version
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm --filter lucide-solid build
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: pnpm --filter lucide-solid test
|
|
||||||
|
|
||||||
- name: Publish
|
|
||||||
run: pnpm --filter lucide-solid publish --no-git-checks
|
|
||||||
|
|
||||||
- name: Upload package.json
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: lucide-solid-package-json
|
|
||||||
path: packages/lucide-solid/package.json
|
|
||||||
|
|
||||||
lucide-svelte:
|
|
||||||
if: github.repository == 'lucide-icons/lucide'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre-build
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2.0.1
|
|
||||||
name: Install pnpm
|
|
||||||
id: pnpm-install
|
|
||||||
with:
|
|
||||||
version: 7
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
id: pnpm-cache
|
|
||||||
run: |
|
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Set Auth Token
|
|
||||||
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set package.json version lucide
|
|
||||||
run: pnpm --filter lucide-svelte version --new-version ${{ needs.pre-build.outputs.VERSION }} --no-git-tag-version
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm --filter lucide-svelte build
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: pnpm --filter lucide-svelte test
|
|
||||||
|
|
||||||
- name: Publish
|
|
||||||
run: pnpm --filter lucide-svelte publish --no-git-checks
|
|
||||||
|
|
||||||
- name: Upload package.json
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: lucide-svelte-package-json
|
|
||||||
path: packages/lucide-svelte/package.json
|
|
||||||
|
|
||||||
lucide-static:
|
lucide-static:
|
||||||
if: github.repository == 'lucide-icons/lucide'
|
if: github.repository == 'lucide-icons/lucide'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [pre-build, lucide-font]
|
needs: [pre-release, lucide-font]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/download-artifact@v2
|
- uses: actions/download-artifact@v2
|
||||||
@@ -543,18 +110,18 @@ jobs:
|
|||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Set Auth Token
|
- name: Set Auth Token
|
||||||
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
|
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
|
||||||
@@ -571,16 +138,10 @@ jobs:
|
|||||||
- name: Publish
|
- name: Publish
|
||||||
run: pnpm --filter lucide-static publish --no-git-checks
|
run: pnpm --filter lucide-static publish --no-git-checks
|
||||||
|
|
||||||
- name: Upload package.json
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: lucide-static-package-json
|
|
||||||
path: packages/lucide-static/package.json
|
|
||||||
|
|
||||||
lucide-font:
|
lucide-font:
|
||||||
if: github.repository == 'lucide-icons/lucide'
|
if: github.repository == 'lucide-icons/lucide'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: pre-build
|
needs: pre-release
|
||||||
container: ericfennis/lucide-font:latest
|
container: ericfennis/lucide-font:latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -598,12 +159,12 @@ jobs:
|
|||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
@@ -629,7 +190,7 @@ jobs:
|
|||||||
lucide-flutter:
|
lucide-flutter:
|
||||||
if: github.repository == 'lucide-icons/lucide'
|
if: github.repository == 'lucide-icons/lucide'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [pre-build, lucide-font]
|
needs: [pre-release, lucide-font]
|
||||||
container:
|
container:
|
||||||
image: cirrusci/flutter:latest
|
image: cirrusci/flutter:latest
|
||||||
steps:
|
steps:
|
||||||
@@ -691,65 +252,25 @@ jobs:
|
|||||||
run: flutter pub publish -f
|
run: flutter pub publish -f
|
||||||
working-directory: packages/lucide-flutter
|
working-directory: packages/lucide-flutter
|
||||||
|
|
||||||
- name: Upload pubspec.yaml
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: lucide-flutter-pubspec-yaml
|
|
||||||
path: packages/lucide-flutter/pubspec.yaml
|
|
||||||
|
|
||||||
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:
|
needs: [
|
||||||
[
|
lucide-font,
|
||||||
pre-build,
|
]
|
||||||
lucide,
|
|
||||||
lucide-react,
|
|
||||||
lucide-react-native,
|
|
||||||
lucide-vue,
|
|
||||||
lucide-vue-next,
|
|
||||||
lucide-angular,
|
|
||||||
lucide-svelte,
|
|
||||||
lucide-preact,
|
|
||||||
lucide-flutter,
|
|
||||||
lucide-font,
|
|
||||||
]
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/download-artifact@v2
|
- uses: actions/download-artifact@v2
|
||||||
|
|
||||||
- name: Commit package files
|
|
||||||
run: |
|
|
||||||
mv lucide-package-json/package.json packages/lucide/package.json
|
|
||||||
mv lucide-react-package-json/package.json packages/lucide-react/package.json
|
|
||||||
mv lucide-react-native-package-json/package.json packages/lucide-react-native/package.json
|
|
||||||
mv lucide-vue-package-json/package.json packages/lucide-vue/package.json
|
|
||||||
mv lucide-preact-package-json/package.json packages/lucide-preact/package.json
|
|
||||||
mv lucide-svelte-package-json/package.json packages/lucide-svelte/package.json
|
|
||||||
mv lucide-vue-next-package-json/package.json packages/lucide-vue-next/package.json
|
|
||||||
mv lucide-angular-package-json/package.json packages/lucide-angular/package.json
|
|
||||||
mv lucide-flutter-pubspec-yaml/pubspec.yaml packages/lucide-flutter/pubspec.yaml
|
|
||||||
|
|
||||||
- name: Commit package.jsons
|
|
||||||
run: |
|
|
||||||
git add packages/*/package.json packages/lucide-flutter/pubspec.yaml
|
|
||||||
git -c user.name="Lucide Bot" -c user.email="lucide-bot@users.noreply.github.com" \
|
|
||||||
commit -m ":package: Bump lucide package versions to ${{ needs.pre-build.outputs.VERSION }}" --no-verify --quiet
|
|
||||||
git remote set-url --push origin https://lucide-bot:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY.git
|
|
||||||
git push origin HEAD:main
|
|
||||||
|
|
||||||
- name: Zip font and icons
|
- name: Zip font and icons
|
||||||
run: |
|
run: |
|
||||||
zip -r lucide-font-${{ needs.pre-build.outputs.VERSION }}.zip lucide-font
|
zip -r lucide-font-${{ needs.pre-release.outputs.VERSION }}.zip lucide-font
|
||||||
zip -r lucide-icons-${{ needs.pre-build.outputs.VERSION }}.zip icons
|
zip -r lucide-icons-${{ needs.pre-release.outputs.VERSION }}.zip icons
|
||||||
|
|
||||||
- name: Release zip and fonts
|
- name: Release zip and fonts
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.CREATE_RELEASE_TOKEN }}
|
|
||||||
with:
|
with:
|
||||||
tag_name: v${{ needs.pre-build.outputs.VERSION }}
|
tag_name: v${{ needs.pre-release.outputs.VERSION }}
|
||||||
files: |
|
files: |
|
||||||
lucide-font-${{ needs.pre-build.outputs.VERSION }}.zip
|
lucide-font-${{ needs.pre-release.outputs.VERSION }}.zip
|
||||||
lucide-icons-${{ needs.pre-build.outputs.VERSION }}.zip
|
lucide-icons-${{ needs.pre-release.outputs.VERSION }}.zip
|
||||||
|
|||||||
14
.gitignore
vendored
14
.gitignore
vendored
@@ -20,3 +20,17 @@ packages/**/src/aliases.ts
|
|||||||
packages/**/LICENSE
|
packages/**/LICENSE
|
||||||
categories.json
|
categories.json
|
||||||
tags.json
|
tags.json
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# docs
|
||||||
|
docs/.vitepress/cache
|
||||||
|
docs/.vitepress/dist
|
||||||
|
docs/.vitepress/.temp
|
||||||
|
docs/.vitepress/data/iconNodes
|
||||||
|
docs/.vitepress/data/iconMetaData.ts
|
||||||
|
docs/.vitepress/data/releaseMetaData.json
|
||||||
|
docs/.vitepress/data/releaseMetaData
|
||||||
|
docs/.vitepress/data/iconDetails
|
||||||
|
docs/.vitepress/data/relatedIcons.json
|
||||||
|
docs/.vercel
|
||||||
|
docs/.nitro
|
||||||
|
|||||||
95
.vscode/svg.code-snippets
vendored
Normal file
95
.vscode/svg.code-snippets
vendored
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
{
|
||||||
|
"Lucide SVG": {
|
||||||
|
"scope": "xml",
|
||||||
|
"description": "Base SVG with Lucide attributes.",
|
||||||
|
"prefix": [
|
||||||
|
"svg",
|
||||||
|
"lucide"
|
||||||
|
],
|
||||||
|
"body": [
|
||||||
|
"<svg",
|
||||||
|
" xmlns=\"http://www.w3.org/2000/svg\"",
|
||||||
|
" width=\"24\"",
|
||||||
|
" height=\"24\"",
|
||||||
|
" viewBox=\"0 0 24 24\"",
|
||||||
|
" fill=\"none\"",
|
||||||
|
" stroke=\"currentColor\"",
|
||||||
|
" stroke-width=\"2\"",
|
||||||
|
" stroke-linecap=\"round\"",
|
||||||
|
" stroke-linejoin=\"round\"",
|
||||||
|
">",
|
||||||
|
" $0",
|
||||||
|
"</svg>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Rectangle": {
|
||||||
|
"scope": "xml",
|
||||||
|
"description": "SVG `rect`angle, with Lucide defaults.",
|
||||||
|
"prefix": [
|
||||||
|
"rect",
|
||||||
|
"<rect"
|
||||||
|
],
|
||||||
|
"body": "<rect width=\"${1:20}\" height=\"${2:12}\" x=\"${3:2}\" y=\"${4:6}\" rx=\"${5|2,1|}\"/>"
|
||||||
|
},
|
||||||
|
"Square": {
|
||||||
|
"scope": "xml",
|
||||||
|
"description": "SVG square `rect`angle, with Lucide defaults.",
|
||||||
|
"prefix": [
|
||||||
|
"square",
|
||||||
|
"rect",
|
||||||
|
"<rect",
|
||||||
|
"tile"
|
||||||
|
],
|
||||||
|
"body": "<rect width=\"${1:18}\" height=\"$1\" x=\"${2:3}\" y=\"${3:$2}\" rx=\"${4|2,1|}\" />"
|
||||||
|
},
|
||||||
|
"Circle": {
|
||||||
|
"scope": "xml",
|
||||||
|
"description": "SVG `circle`, with Lucide defaults.",
|
||||||
|
"prefix": [
|
||||||
|
"circle",
|
||||||
|
"<circle"
|
||||||
|
],
|
||||||
|
"body": "<circle cx=\"${2:12}\" cy=\"${3:$2}\" r=\"${1|10,2,.5|}\" />"
|
||||||
|
},
|
||||||
|
"Ellipse": {
|
||||||
|
"scope": "xml",
|
||||||
|
"description": "SVG `ellipse`.",
|
||||||
|
"prefix": [
|
||||||
|
"ellipse",
|
||||||
|
"<ellipse"
|
||||||
|
],
|
||||||
|
"body": "<ellipse cx=\"${3:12}\" cy=\"${4:$3}\" rx=\"${1:10}\" ry=\"${2:$1}\" />"
|
||||||
|
},
|
||||||
|
"Path": {
|
||||||
|
"scope": "xml",
|
||||||
|
"description": "SVG custom `path`.",
|
||||||
|
"prefix": [
|
||||||
|
"path",
|
||||||
|
"<path",
|
||||||
|
"polyline",
|
||||||
|
"<polyline",
|
||||||
|
"polygon",
|
||||||
|
"<polygon"
|
||||||
|
],
|
||||||
|
"body": "<path d=\"${1|M,m|}$0\" />"
|
||||||
|
},
|
||||||
|
"Line": {
|
||||||
|
"scope": "xml",
|
||||||
|
"description": "SVG `path`, preffered to `line` in Lucide.",
|
||||||
|
"prefix": [
|
||||||
|
"line",
|
||||||
|
"<line",
|
||||||
|
"minus"
|
||||||
|
],
|
||||||
|
"body": "<path d=\"M${3:5} ${4:12}${1|h,v|}${2:14}\" />"
|
||||||
|
},
|
||||||
|
"Dot": {
|
||||||
|
"scope": "xml",
|
||||||
|
"description": "SVG small dot, within the Lucide guidelines.",
|
||||||
|
"prefix": [
|
||||||
|
"dot",
|
||||||
|
"."
|
||||||
|
],
|
||||||
|
"body": "<path d=\"M ${1:12} ${2:$1}h.01\" />"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,19 +25,19 @@ Guidelines for pull requests:
|
|||||||
|
|
||||||
Please make sure you follow the icon guidelines, that should be followed to keep quality and consistency when making icons for Lucide.
|
Please make sure you follow the icon guidelines, that should be followed to keep quality and consistency when making icons for Lucide.
|
||||||
|
|
||||||
Read it here: [ICON_GUIDELINES](/docs/icon-design-guide.md).
|
Read it here: [ICON_GUIDELINES](https://lucide.dev/docs/icon-design-guide).
|
||||||
|
|
||||||
### Editor guides
|
### Editor guides
|
||||||
|
|
||||||
Here you can find instructions on how to implement the guidelines with different vector graphics editors:
|
Here you can find instructions on how to implement the guidelines with different vector graphics editors:
|
||||||
|
|
||||||
#### [Adobe Illustrator Guide](/docs/illustrator-guide.md)
|
#### [Adobe Illustrator Guide](https://lucide.dev/docs/illustrator-guide)
|
||||||
|
|
||||||
You can also [download an Adobe Illustrator template](/docs/templates/illustrator-template.ai).
|
You can also [download an Adobe Illustrator template](https://lucide.dev/templates/illustrator-template.ai).
|
||||||
|
|
||||||
#### [Inkscape Guide](/docs/inkscape-guide.md)
|
#### [Inkscape Guide](https://lucide.dev/docs/inkscape-guide)
|
||||||
|
|
||||||
#### [Figma Guide](/docs/figma-guide.md)
|
#### [Figma Guide](https://lucide.dev/docs/figma-guide)
|
||||||
|
|
||||||
### Submitting Multiple Icons
|
### Submitting Multiple Icons
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ pnpm install # Install dependencies, including the workspace packages
|
|||||||
|
|
||||||
### Packages -> PNPM Workspaces
|
### Packages -> PNPM Workspaces
|
||||||
|
|
||||||
To distribute different packages we use PNPM workspaces. Before you start make sure you are familiar with this concept. The concept of working in workspaces is created by Yarn, they have a well written introduction: [yarn workspaces](https://classic.yarnpkg.com/lang/en/docs/workspaces).
|
To distribute different packages we use PNPM workspaces. Before you start make sure you are familiar with this concept. The concept of working in workspaces is created by Yarn, they have a well written introduction: [yarn workspaces](https://classic.yarnpkg.com/lang/enhttps://lucide.dev/docs/workspaces).
|
||||||
|
|
||||||
The configured directory for workspaces is the [packages](./packages) directory, located in the root directory. There you will find all the current packages from lucide.
|
The configured directory for workspaces is the [packages](./packages) directory, located in the root directory. There you will find all the current packages from lucide.
|
||||||
There are more workspaces defined, see [`pnpm-workspace.yaml`](./pnpm-workspace.yaml).
|
There are more workspaces defined, see [`pnpm-workspace.yaml`](./pnpm-workspace.yaml).
|
||||||
@@ -172,11 +172,11 @@ Includes usefully scripts to automate certain jobs. Big part of the scripts is t
|
|||||||
|
|
||||||
### site
|
### site
|
||||||
|
|
||||||
The lucide.dev website using [Nextjs](https://nextjs.org).
|
The lucide.dev website is using [vitepress](https://vitepress.dev/) to generate the static website. The markdown files are located in the docs directory.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
The documentation files are located in the [docs](./docs) directory. All these markdown files will be loaded in the build of the lucide.dev website.
|
The documentation files are located in the [docs](https://github.com/lucide-icons/lucide/tree/main/docs) directory. All these markdown files will be loaded in the build of the lucide.dev website.
|
||||||
|
|
||||||
Feel free to write, adjust or add new markdown files to improve our documentation.
|
Feel free to write, adjust or add new markdown files to improve our documentation.
|
||||||
|
|
||||||
|
|||||||
@@ -166,8 +166,8 @@ For more details, see the [documentation](https://github.com/lucide-icons/lucide
|
|||||||
### Static (svg sprite, font, icons ..)
|
### Static (svg sprite, font, icons ..)
|
||||||
|
|
||||||
Assets:
|
Assets:
|
||||||
[Font Files](https://github.com/lucide-icons/lucide/releases/tag/latest)
|
[Font Files](https://github.com/lucide-icons/lucide/releases/latest)
|
||||||
[SVG Files](https://github.com/lucide-icons/lucide/releases/tag/latest)
|
[SVG Files](https://github.com/lucide-icons/lucide/releases/latest)
|
||||||
[SVG Sprite](https://cdn.jsdelivr.net/npm/lucide-static@latest/sprite.svg)
|
[SVG Sprite](https://cdn.jsdelivr.net/npm/lucide-static@latest/sprite.svg)
|
||||||
|
|
||||||
NPM package
|
NPM package
|
||||||
|
|||||||
11
docs/.vitepress/api/categories/index.get.ts
Normal file
11
docs/.vitepress/api/categories/index.get.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { eventHandler, setResponseHeader } from 'h3'
|
||||||
|
import iconMetaData from '../../data/iconMetaData'
|
||||||
|
|
||||||
|
export default eventHandler((event) => {
|
||||||
|
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
|
||||||
|
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||||
|
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(iconMetaData).map(([name, { categories }]) => [ name, categories ])
|
||||||
|
)
|
||||||
|
})
|
||||||
41
docs/.vitepress/api/gh-icon/[...data].get.ts
Normal file
41
docs/.vitepress/api/gh-icon/[...data].get.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { eventHandler, setResponseHeader, defaultContentType } from 'h3'
|
||||||
|
import { renderToString, renderToStaticMarkup } from 'react-dom/server'
|
||||||
|
import { createElement } from 'react'
|
||||||
|
import SvgPreview from '../../lib/SvgPreview/index.tsx';
|
||||||
|
import iconNodes from '../../data/iconNodes'
|
||||||
|
import createLucideIcon from 'lucide-react/src/createLucideIcon'
|
||||||
|
import Backdrop from '../../lib/SvgPreview/Backdrop.tsx';
|
||||||
|
|
||||||
|
export default eventHandler((event) => {
|
||||||
|
const { params } = event.context
|
||||||
|
|
||||||
|
const [name, svgData] = params.data.split('/');
|
||||||
|
const data = svgData.slice(0, -4);
|
||||||
|
|
||||||
|
const src = Buffer.from(data, 'base64').toString('utf8');
|
||||||
|
|
||||||
|
const children = []
|
||||||
|
|
||||||
|
if (name in iconNodes) {
|
||||||
|
const iconNode = iconNodes[name]
|
||||||
|
|
||||||
|
const LucideIcon = createLucideIcon(name, iconNode)
|
||||||
|
const svg = renderToStaticMarkup(createElement(LucideIcon))
|
||||||
|
const backdropString = svg.replace(/<svg[^>]*>|<\/svg>/g, '');
|
||||||
|
|
||||||
|
children.push(createElement(Backdrop, { backdropString, src }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const svg = Buffer.from(
|
||||||
|
// We can't use jsx here, is not supported here by nitro.
|
||||||
|
renderToString(createElement(SvgPreview, {src, showGrid: true}, children)).replace(
|
||||||
|
/>/,
|
||||||
|
'><style>@media screen and (prefers-color-scheme: dark) { svg { stroke: #fff } }</style>'
|
||||||
|
)
|
||||||
|
).toString('utf8');
|
||||||
|
|
||||||
|
defaultContentType(event, 'image/svg+xml')
|
||||||
|
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000')
|
||||||
|
|
||||||
|
return svg
|
||||||
|
})
|
||||||
35
docs/.vitepress/api/gh-icon/stroke-width/[...data].get.ts
Normal file
35
docs/.vitepress/api/gh-icon/stroke-width/[...data].get.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { eventHandler, setResponseHeader, defaultContentType } from 'h3'
|
||||||
|
import { renderToString } from 'react-dom/server'
|
||||||
|
import { createElement } from 'react'
|
||||||
|
import SvgPreview from '../../../lib/SvgPreview/index.tsx';
|
||||||
|
import createLucideIcon, { IconNode } from 'lucide-react/src/createLucideIcon'
|
||||||
|
import { parseSync } from 'svgson';
|
||||||
|
|
||||||
|
export default eventHandler((event) => {
|
||||||
|
const { params } = event.context
|
||||||
|
|
||||||
|
const [strokeWidth, svgData] = params.data.split('/');
|
||||||
|
const data = svgData.slice(0, -4);
|
||||||
|
|
||||||
|
const src = Buffer.from(data, 'base64').toString('utf8');
|
||||||
|
|
||||||
|
const Icon = createLucideIcon(
|
||||||
|
'icon',
|
||||||
|
parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`).children.map(
|
||||||
|
({ name, attributes }) => [name, attributes]
|
||||||
|
) as IconNode
|
||||||
|
);
|
||||||
|
|
||||||
|
const svg = Buffer.from(
|
||||||
|
// We can't use jsx here, is not supported here by nitro.
|
||||||
|
renderToString(createElement(Icon, { strokeWidth })).replace(
|
||||||
|
/>/,
|
||||||
|
'><style>@media screen and (prefers-color-scheme: dark) { svg { stroke: #fff } }</style>'
|
||||||
|
)
|
||||||
|
).toString('utf8');
|
||||||
|
|
||||||
|
defaultContentType(event, 'image/svg+xml')
|
||||||
|
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000')
|
||||||
|
|
||||||
|
return svg
|
||||||
|
})
|
||||||
30
docs/.vitepress/api/icon-nodes/index.get.ts
Normal file
30
docs/.vitepress/api/icon-nodes/index.get.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { eventHandler, getQuery, setResponseHeader } from 'h3'
|
||||||
|
import iconNodes from '../../data/iconNodes'
|
||||||
|
import { IconNodeWithKeys } from '../../theme/types'
|
||||||
|
|
||||||
|
export default eventHandler((event) => {
|
||||||
|
const query = getQuery(event)
|
||||||
|
|
||||||
|
const withUniqueKeys = query.withUniqueKeys === 'true'
|
||||||
|
|
||||||
|
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
|
||||||
|
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||||
|
|
||||||
|
if (withUniqueKeys) {
|
||||||
|
return iconNodes
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
|
||||||
|
if (withUniqueKeys) {
|
||||||
|
return [name, iconNode]
|
||||||
|
}
|
||||||
|
|
||||||
|
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs}]) => {
|
||||||
|
return [name, attrs]
|
||||||
|
})
|
||||||
|
|
||||||
|
acc[name] = newIconNode
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
})
|
||||||
45
docs/.vitepress/api/icons/[iconName].get.ts
Normal file
45
docs/.vitepress/api/icons/[iconName].get.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { eventHandler, getQuery, setResponseHeader, createError } from 'h3'
|
||||||
|
import iconNodes from '../../data/iconNodes'
|
||||||
|
import createLucideIcon from 'lucide-react/src/createLucideIcon'
|
||||||
|
import { renderToString } from 'react-dom/server'
|
||||||
|
import { createElement } from 'react'
|
||||||
|
|
||||||
|
export default eventHandler((event) => {
|
||||||
|
const { params } = event.context
|
||||||
|
|
||||||
|
const iconNode = iconNodes[params.iconName]
|
||||||
|
|
||||||
|
if (iconNode == null) {
|
||||||
|
const error = createError({
|
||||||
|
statusCode: 404,
|
||||||
|
message: `Icon "${params.iconName}" not found`,
|
||||||
|
})
|
||||||
|
|
||||||
|
return sendError(event, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = getQuery(event).width || undefined
|
||||||
|
const height = getQuery(event).height || undefined
|
||||||
|
const color = getQuery(event).color || undefined
|
||||||
|
const strokeWidth = getQuery(event).strokeWidth || undefined
|
||||||
|
|
||||||
|
const LucideIcon = createLucideIcon(params.iconName, iconNode)
|
||||||
|
|
||||||
|
const svg = Buffer.from(
|
||||||
|
renderToString(
|
||||||
|
createElement(LucideIcon, {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
color: color ? `#${color}` : undefined,
|
||||||
|
strokeWidth,
|
||||||
|
}
|
||||||
|
))
|
||||||
|
).toString('utf8');
|
||||||
|
|
||||||
|
defaultContentType(event, 'image/svg+xml')
|
||||||
|
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000')
|
||||||
|
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||||
|
|
||||||
|
return svg
|
||||||
|
|
||||||
|
})
|
||||||
11
docs/.vitepress/api/tags/index.get.ts
Normal file
11
docs/.vitepress/api/tags/index.get.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { eventHandler, setResponseHeader } from 'h3'
|
||||||
|
import iconMetaData from '../../data/iconMetaData'
|
||||||
|
|
||||||
|
export default eventHandler((event) => {
|
||||||
|
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
|
||||||
|
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||||
|
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(iconMetaData).map(([name, { tags }]) => [ name, tags ])
|
||||||
|
)
|
||||||
|
})
|
||||||
3
docs/.vitepress/api/test.ts
Normal file
3
docs/.vitepress/api/test.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default eventHandler(() => {
|
||||||
|
return { nitro: 'Is Awesome! asda' }
|
||||||
|
})
|
||||||
157
docs/.vitepress/config.ts
Normal file
157
docs/.vitepress/config.ts
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
import path from 'path';
|
||||||
|
import { defineConfig } from 'vitepress'
|
||||||
|
import { createWriteStream } from 'node:fs'
|
||||||
|
import { resolve } from 'node:path'
|
||||||
|
import { SitemapStream } from 'sitemap'
|
||||||
|
import sidebar from './sidebar';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
const links = []
|
||||||
|
|
||||||
|
|
||||||
|
const title = "Lucide";
|
||||||
|
const socialTitle = "Lucide Icons";
|
||||||
|
const description = "Beautiful & consistent icon toolkit made by the community."
|
||||||
|
|
||||||
|
// https://vitepress.dev/reference/site-config
|
||||||
|
export default defineConfig({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
cleanUrls: true,
|
||||||
|
outDir: '.vercel/output/static',
|
||||||
|
vite: {
|
||||||
|
resolve: {
|
||||||
|
alias: [
|
||||||
|
{
|
||||||
|
find: /^.*\/VPIconAlignLeft\.vue$/,
|
||||||
|
replacement: fileURLToPath(
|
||||||
|
new URL('./theme/components/overrides/VPIconAlignLeft.vue', import.meta.url)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: /^.*\/VPFooter\.vue$/,
|
||||||
|
replacement: fileURLToPath(
|
||||||
|
new URL('./theme/components/overrides/VPFooter.vue', import.meta.url)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
head: [
|
||||||
|
[ 'script', {
|
||||||
|
src: 'https://plausible.io/js/script.js',
|
||||||
|
'data-domain': 'lucide.dev',
|
||||||
|
defer: ''
|
||||||
|
}],
|
||||||
|
[ 'meta', {
|
||||||
|
property:"og:locale",
|
||||||
|
content:"en_US"
|
||||||
|
}],
|
||||||
|
[ 'meta', {
|
||||||
|
property:"og:type",
|
||||||
|
content:"website"
|
||||||
|
}],
|
||||||
|
[ 'meta', {
|
||||||
|
property:"og:site_name",
|
||||||
|
content: title,
|
||||||
|
}],
|
||||||
|
[ 'meta', {
|
||||||
|
property:"og:title",
|
||||||
|
content: socialTitle,
|
||||||
|
}],
|
||||||
|
[ 'meta', {
|
||||||
|
property:"og:description",
|
||||||
|
content: description
|
||||||
|
}],
|
||||||
|
[ 'meta', {
|
||||||
|
property:"og:url",
|
||||||
|
content:"https://lucide.dev"
|
||||||
|
}],
|
||||||
|
[ 'meta', {
|
||||||
|
property:"og:image",
|
||||||
|
content: "https://lucide.dev/og.png"
|
||||||
|
}],
|
||||||
|
[ 'meta', {
|
||||||
|
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: {
|
||||||
|
// https://vitepress.dev/reference/default-theme-config
|
||||||
|
logo: {
|
||||||
|
light: '/logo.light.svg',
|
||||||
|
dark: '/logo.dark.svg'
|
||||||
|
},
|
||||||
|
nav: [
|
||||||
|
{ text: 'Icons', link: '/icons/' },
|
||||||
|
{ text: 'Guide', link: '/guide/' },
|
||||||
|
{ text: 'Packages', link: '/packages' },
|
||||||
|
{ text: 'License', link: '/license' },
|
||||||
|
],
|
||||||
|
sidebar,
|
||||||
|
socialLinks: [
|
||||||
|
{ icon: 'github', link: 'https://github.com/lucide-icons/lucide' },
|
||||||
|
{ icon: 'discord', link: 'https://discord.gg/EH6nSts' }
|
||||||
|
],
|
||||||
|
footer: {
|
||||||
|
message: 'Released under the ISC License.',
|
||||||
|
copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`
|
||||||
|
},
|
||||||
|
editLink: {
|
||||||
|
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transformHtml: (_, id, { pageData }) => {
|
||||||
|
if (/[\\/]404\.html$/.test(id)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageData.relativePath.startsWith('icons/')) {
|
||||||
|
links.push({
|
||||||
|
url: pageData.relativePath.replace(/((^|\/)index)?\.md$/, '$2'),
|
||||||
|
lastmod: pageData?.params?.changedRelease?.date
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
links.push({
|
||||||
|
url: pageData.relativePath.replace(/((^|\/)index)?\.md$/, '$2'),
|
||||||
|
lastmod: pageData.lastUpdated
|
||||||
|
})
|
||||||
|
},
|
||||||
|
buildEnd: async ({ outDir }) => {
|
||||||
|
const sitemap = new SitemapStream({
|
||||||
|
hostname: 'https://lucide.dev/'
|
||||||
|
})
|
||||||
|
const writeStream = createWriteStream(resolve(outDir, 'sitemap.xml'))
|
||||||
|
sitemap.pipe(writeStream)
|
||||||
|
links.forEach((link) => sitemap.write(link))
|
||||||
|
sitemap.end()
|
||||||
|
await new Promise((r) => writeStream.on('finish', r))
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
"name": "hyva-lucide-icons",
|
"name": "hyva-lucide-icons",
|
||||||
"description": "Implementation of Lucide icon's using Hyvä's svg php viewmodal to render icons for Magento 2 Hyva theme based projects.",
|
"description": "Implementation of Lucide icon's using Hyvä's svg php viewmodal to render icons for Magento 2 Hyva theme based projects.",
|
||||||
"icon": "/framework-logos/hyva.svg",
|
"icon": "/framework-logos/hyva.svg",
|
||||||
|
"iconDark": "/framework-logos/hyva-dark.svg",
|
||||||
"shields": [
|
"shields": [
|
||||||
{
|
{
|
||||||
"alt": "Latest Stable Version",
|
"alt": "Latest Stable Version",
|
||||||
@@ -41,6 +42,7 @@
|
|||||||
"name": "eleventy-lucide-icons",
|
"name": "eleventy-lucide-icons",
|
||||||
"description": "Using this plugin, Eleventy projects can incorporate Lucide icons. it makes it simple to use Lucide icons into your themes via shortcodes, improving your website's overall usability and visual appeal.",
|
"description": "Using this plugin, Eleventy projects can incorporate Lucide icons. it makes it simple to use Lucide icons into your themes via shortcodes, improving your website's overall usability and visual appeal.",
|
||||||
"icon": "/framework-logos/11ty.svg",
|
"icon": "/framework-logos/11ty.svg",
|
||||||
|
"iconClass": "package-icon-invert",
|
||||||
"shields": [
|
"shields": [
|
||||||
{
|
{
|
||||||
"alt": "Latest Stable Version",
|
"alt": "Latest Stable Version",
|
||||||
71
docs/.vitepress/lib/SvgPreview/Backdrop.tsx
Normal file
71
docs/.vitepress/lib/SvgPreview/Backdrop.tsx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface BackdropProps {
|
||||||
|
src: string
|
||||||
|
backdropString: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Backdrop = ({ src, backdropString }: BackdropProps): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<defs xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<pattern
|
||||||
|
id="pattern"
|
||||||
|
width=".1"
|
||||||
|
height=".1"
|
||||||
|
patternUnits="userSpaceOnUse"
|
||||||
|
patternTransform="rotate(45 50 50)"
|
||||||
|
>
|
||||||
|
<line stroke="red" strokeWidth={0.1} y2={1} />
|
||||||
|
<line stroke="red" strokeWidth={0.1} y2={1} />
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
<mask id="svg-preview-backdrop-mask-outline" maskUnits="userSpaceOnUse">
|
||||||
|
<g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} />
|
||||||
|
<g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} />
|
||||||
|
</mask>
|
||||||
|
<mask id="svg-preview-backdrop-mask-fill" maskUnits="userSpaceOnUse">
|
||||||
|
<g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} />
|
||||||
|
<g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} />
|
||||||
|
<g strokeWidth={1.75} dangerouslySetInnerHTML={{ __html: backdropString }} />
|
||||||
|
</mask>
|
||||||
|
<g
|
||||||
|
strokeWidth={2.25}
|
||||||
|
stroke="url(#pattern)"
|
||||||
|
mask={'url(#svg-preview-backdrop-mask-outline)'}
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
fill="url(#pattern)"
|
||||||
|
opacity={0.5}
|
||||||
|
stroke="none"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<rect
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
fill="url(#pattern)"
|
||||||
|
stroke="none"
|
||||||
|
mask={'url(#svg-preview-backdrop-mask-fill)'}
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
fill="red"
|
||||||
|
opacity={0.5}
|
||||||
|
stroke="none"
|
||||||
|
mask={'url(#svg-preview-backdrop-mask-fill)'}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default Backdrop;
|
||||||
28
docs/.vitepress/lib/categories.ts
Normal file
28
docs/.vitepress/lib/categories.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import {Category, IconEntity} from "../theme/types";
|
||||||
|
|
||||||
|
const directory = path.join(process.cwd(), "../categories");
|
||||||
|
|
||||||
|
export function getAllCategoryFiles(): Category[] {
|
||||||
|
const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json');
|
||||||
|
|
||||||
|
return fileNames.map((fileName) => {
|
||||||
|
const name = path.basename(fileName, '.json')
|
||||||
|
const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8')
|
||||||
|
|
||||||
|
const parsedFileContent = JSON.parse(fileContent)
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
title: parsedFileContent.title,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapCategoryIconCount(categories: Category[], icons: { categories: IconEntity['categories'] }[]) {
|
||||||
|
return categories.map((category) => ({
|
||||||
|
...category,
|
||||||
|
iconCount: icons.reduce((acc, curr) => (curr.categories.includes(category.name) ? ++acc : acc), 0)
|
||||||
|
}))
|
||||||
|
}
|
||||||
218
docs/.vitepress/lib/createCodeExamples.ts
Normal file
218
docs/.vitepress/lib/createCodeExamples.ts
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
import {
|
||||||
|
BUNDLED_LANGUAGES,
|
||||||
|
type IThemeRegistration
|
||||||
|
} from 'shiki'
|
||||||
|
import {
|
||||||
|
getHighlighter,
|
||||||
|
} from 'shiki-processor'
|
||||||
|
|
||||||
|
type CodeExampleType = {
|
||||||
|
title: string,
|
||||||
|
lang: string,
|
||||||
|
codes: {
|
||||||
|
language?: string,
|
||||||
|
code: string,
|
||||||
|
metastring?: string,
|
||||||
|
}[],
|
||||||
|
}[]
|
||||||
|
|
||||||
|
const getIconCodes = (): CodeExampleType => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
lang: 'html',
|
||||||
|
title: 'HTML',
|
||||||
|
codes: [
|
||||||
|
{
|
||||||
|
language: 'html',
|
||||||
|
code: `<i data-lucide-name="Name"></i>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lang: 'tsx',
|
||||||
|
title: 'React',
|
||||||
|
codes: [
|
||||||
|
{
|
||||||
|
language: 'tsx',
|
||||||
|
code: `import { PascalCase } from 'lucide-react';
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
return (
|
||||||
|
<PascalCase />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
`,
|
||||||
|
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lang: 'vue',
|
||||||
|
title: 'Vue 3',
|
||||||
|
codes: [
|
||||||
|
{
|
||||||
|
language: 'vue',
|
||||||
|
code: `<script setup>
|
||||||
|
import { PascalCase } from 'lucide-vue-next';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PascalCase />
|
||||||
|
</template>
|
||||||
|
`,
|
||||||
|
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lang: 'svelte',
|
||||||
|
title: 'Svelte',
|
||||||
|
codes: [
|
||||||
|
{
|
||||||
|
language: 'svelte',
|
||||||
|
code: `<script>
|
||||||
|
import { PascalCase } from 'lucide-svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<PascalCase />
|
||||||
|
`,
|
||||||
|
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lang: 'preact',
|
||||||
|
title: 'Preact',
|
||||||
|
codes: [
|
||||||
|
{
|
||||||
|
language: 'tsx',
|
||||||
|
code: `import { PascalCase } from 'lucide-preact';
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
return (
|
||||||
|
<PascalCase />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
`,
|
||||||
|
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lang: 'solid',
|
||||||
|
title: 'Solid',
|
||||||
|
codes: [
|
||||||
|
{
|
||||||
|
language: 'tsx',
|
||||||
|
code: `import { PascalCase } from 'lucide-solid';
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
return (
|
||||||
|
<PascalCase />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
`,
|
||||||
|
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lang: 'angular',
|
||||||
|
title: 'Angular',
|
||||||
|
codes: [
|
||||||
|
{
|
||||||
|
language: 'tsx',
|
||||||
|
code: `// app.module.ts
|
||||||
|
import { LucideAngularModule, PascalCase } from 'lucide-angular';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
LucideAngularModule.pick({ PascalCase })
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
// app.component.html
|
||||||
|
<lucide-icon name="Name"></lucide-icon>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lang: 'html',
|
||||||
|
title: 'Icon Font',
|
||||||
|
codes: [
|
||||||
|
{
|
||||||
|
language: 'html',
|
||||||
|
code: `<style>
|
||||||
|
@import ('~lucide-static/font/Lucide.css');
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="icon-Name"></div>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lang: 'dart',
|
||||||
|
title: 'Flutter',
|
||||||
|
codes: [
|
||||||
|
{
|
||||||
|
language: 'dart',
|
||||||
|
code: `Icon(LucideIcons.Name);
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ThemeOptions =
|
||||||
|
| IThemeRegistration
|
||||||
|
| { light: IThemeRegistration; dark: IThemeRegistration }
|
||||||
|
|
||||||
|
const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
||||||
|
const highlighter = await getHighlighter({
|
||||||
|
themes: ['material-theme-palenight'],
|
||||||
|
langs: [...BUNDLED_LANGUAGES],
|
||||||
|
processors: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const highlightedCode = highlighter.codeToHtml(code, {
|
||||||
|
lang,
|
||||||
|
// lineOptions,
|
||||||
|
theme: 'material-theme-palenight'
|
||||||
|
}).replace('background-color: #292D3E', '')
|
||||||
|
|
||||||
|
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 (codeTemplate, index) => {
|
||||||
|
const { title, lang, codes } = codeTemplate;
|
||||||
|
const isFirst = index === 0;
|
||||||
|
|
||||||
|
const code = await highLightCode(codes[0].code, codes[0].language || lang, isFirst);
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
language: codes[0].language || lang,
|
||||||
|
code,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.all(codeExamplePromises);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
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 '../components/Package';
|
import { PackageItem } from '../theme/types';
|
||||||
|
|
||||||
const fileExist = (filePath) => fs.access(filePath, constants.F_OK).then(() => true).catch(() => false)
|
const fileExist = (filePath) => fs.access(filePath, constants.F_OK).then(() => true).catch(() => false)
|
||||||
|
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import { createLucideIcon, LucideProps } from "lucide-react"
|
import { createLucideIcon } from "lucide-react/src/lucide-react"
|
||||||
import { IconEntity } from "src/types"
|
import { type LucideProps, type IconNode } from "lucide-react/src/createLucideIcon"
|
||||||
|
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
|
return icons
|
||||||
.map<IconContent>((icon) => {
|
.map<IconContent>((icon) => {
|
||||||
const Icon = createLucideIcon(icon.name, icon.iconNode)
|
const Icon = createLucideIcon(icon.name, icon.iconNode as IconNode)
|
||||||
const src = renderToStaticMarkup(<Icon {...params} />)
|
const src = renderToStaticMarkup(<Icon {...params} />)
|
||||||
return [icon.name, src]
|
return [icon.name, src]
|
||||||
})
|
})
|
||||||
47
docs/.vitepress/lib/icons.ts
Normal file
47
docs/.vitepress/lib/icons.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import { IconNodeWithKeys } from "../theme/types";
|
||||||
|
import iconNodes from '../data/iconNodes'
|
||||||
|
import releaseMeta from "../data/releaseMetaData.json";
|
||||||
|
|
||||||
|
const DATE_OF_FORK = '2020-06-08T16:39:52+0100';
|
||||||
|
|
||||||
|
const directory = path.join(process.cwd(), "../icons");
|
||||||
|
|
||||||
|
export interface GetDataOptions {
|
||||||
|
withChildKeys?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getData(name: string) {
|
||||||
|
const jsonPath = path.join(directory, `${name}.json`);
|
||||||
|
const jsonContent = fs.readFileSync(jsonPath, "utf8");
|
||||||
|
const { tags, categories, contributors } = JSON.parse(jsonContent);
|
||||||
|
|
||||||
|
const iconNode = iconNodes[name]
|
||||||
|
|
||||||
|
const releaseData = releaseMeta?.[name] ?? {
|
||||||
|
"createdRelease": {
|
||||||
|
"version": "0.0.0",
|
||||||
|
"date": DATE_OF_FORK
|
||||||
|
},
|
||||||
|
"changedRelease": {
|
||||||
|
"version": "0.0.0",
|
||||||
|
"date": DATE_OF_FORK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
tags,
|
||||||
|
categories,
|
||||||
|
iconNode,
|
||||||
|
contributors,
|
||||||
|
...releaseData
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllData(): Promise<{ name: string, iconNode: IconNodeWithKeys}[]> {
|
||||||
|
const names = Object.keys(iconNodes);
|
||||||
|
|
||||||
|
return Promise.all(names.map((name) => getData(name)));
|
||||||
|
}
|
||||||
118
docs/.vitepress/sidebar.ts
Normal file
118
docs/.vitepress/sidebar.ts
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import { DefaultTheme, UserConfig } from "vitepress"
|
||||||
|
|
||||||
|
const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
|
||||||
|
'guide':[
|
||||||
|
{
|
||||||
|
text: 'Introduction',
|
||||||
|
items: [
|
||||||
|
{ text: 'What is lucide?', link: '/guide/' },
|
||||||
|
{ text: 'Installation', link: '/guide/installation' },
|
||||||
|
{ text: 'Comparison', link: '/guide/comparison' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// text: 'Using Icons',
|
||||||
|
// items: [
|
||||||
|
// {
|
||||||
|
// text: 'How to use icons',
|
||||||
|
// link: 'how-to-use-icons'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// text: 'Styling icons',
|
||||||
|
// link: 'styling-icons'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// text: 'Accessibility',
|
||||||
|
// link: 'accessibility'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// text: 'What should I use',
|
||||||
|
// link: 'what-should-i-use'
|
||||||
|
// },
|
||||||
|
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
text: 'Packages',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Lucide',
|
||||||
|
link: '/guide/packages/lucide'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Lucide React',
|
||||||
|
link: '/guide/packages/lucide-react'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Lucide React Native',
|
||||||
|
link: '/guide/packages/lucide-react-native'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Lucide Vue',
|
||||||
|
link: '/guide/packages/lucide-vue'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Lucide Vue Next (Vue 3)',
|
||||||
|
link: '/guide/packages/lucide-vue-next'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Lucide Svelte',
|
||||||
|
link: '/guide/packages/lucide-svelte'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Lucide Solid',
|
||||||
|
link: '/guide/packages/lucide-solid'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Lucide Preact',
|
||||||
|
link: '/guide/packages/lucide-preact'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Lucide Angular',
|
||||||
|
link: '/guide/packages/lucide-angular'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Lucide Static',
|
||||||
|
link: '/guide/packages/lucide-static'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Lucide Flutter',
|
||||||
|
link: '/guide/packages/lucide-flutter'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Contributing',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Icon Design Principles',
|
||||||
|
link: '/guide/design/icon-design-guide'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Designing in Illustrator',
|
||||||
|
link: '/guide/design/illustrator-guide'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Designing in InkScape',
|
||||||
|
link: '/guide/design/inkscape-guide'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Designing in Figma',
|
||||||
|
link: '/guide/design/figma-guide'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'icons': [
|
||||||
|
{ text: '', link: '/' },
|
||||||
|
// { text: 'Categorized', link: '/icons/categorized' },
|
||||||
|
// {
|
||||||
|
// text: 'Categories',
|
||||||
|
// items: [
|
||||||
|
// ...(getAllCategoryFiles().map((category) => ({ text: category, link: `/icons/category/${category}` })))
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export default sidebar
|
||||||
11
docs/.vitepress/theme/components/PageContainer.vue
Normal file
11
docs/.vitepress/theme/components/PageContainer.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
62
docs/.vitepress/theme/components/base/Badge.vue
Normal file
62
docs/.vitepress/theme/components/base/Badge.vue
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { useRouter } from 'vitepress';
|
||||||
|
|
||||||
|
const { go } = useRouter()
|
||||||
|
const props = defineProps<{
|
||||||
|
href?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const component = computed(() => props.href ? 'a' : 'div')
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<component
|
||||||
|
:is="component"
|
||||||
|
:href="href"
|
||||||
|
class="badge"
|
||||||
|
@click="props?.href ? go(href) : undefined"
|
||||||
|
>
|
||||||
|
<slot/>
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.badge, a.badge {
|
||||||
|
display: block;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 2px 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
/* width: 56px;
|
||||||
|
height: 56px; */
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge[href]:hover, a.badge:hover {
|
||||||
|
border-color: var(--vp-button-alt-hover-border);
|
||||||
|
color: var(--vp-button-alt-hover-text);
|
||||||
|
background-color: var(--vp-button-alt-hover-bg);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge[href]:active {
|
||||||
|
border-color: var(--vp-button-alt-active-border);
|
||||||
|
color: var(--vp-button-alt-active-text);
|
||||||
|
background-color: var(--vp-button-alt-active-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.active {
|
||||||
|
border-color: var(--vp-c-brand);
|
||||||
|
/* color: var(--vp-button-alt-active-text);
|
||||||
|
background-color: var(--vp-button-alt-active-bg); */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
187
docs/.vitepress/theme/components/base/ButtonMenu.vue
Normal file
187
docs/.vitepress/theme/components/base/ButtonMenu.vue
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import {
|
||||||
|
Listbox,
|
||||||
|
ListboxButton,
|
||||||
|
ListboxOptions,
|
||||||
|
ListboxOption,
|
||||||
|
} from '@headlessui/vue'
|
||||||
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
||||||
|
import { chevronUp } from '../../../data/iconNodes'
|
||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
options: {
|
||||||
|
text: string
|
||||||
|
onClick?: () => void
|
||||||
|
}[],
|
||||||
|
callOptionOnClick?: boolean
|
||||||
|
buttonClass?: string
|
||||||
|
id: string
|
||||||
|
popoverPosition?: 'top' | 'bottom'
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
callOptionOnClick: false,
|
||||||
|
popoverPosition: 'bottom'
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['click', 'optionClick'])
|
||||||
|
|
||||||
|
const buttonRef = ref(null)
|
||||||
|
|
||||||
|
const selectedOption = useStorage(props.id, props.options[0].text)
|
||||||
|
const selectionOptionAction = computed(() => props.options.find(option => option.text === selectedOption.value).onClick)
|
||||||
|
|
||||||
|
function onClick(event) {
|
||||||
|
selectionOptionAction.value()
|
||||||
|
|
||||||
|
|
||||||
|
emit('click', event)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOptionClick(event, option) {
|
||||||
|
if(!props.callOptionOnClick) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
option.onClick()
|
||||||
|
|
||||||
|
emit('optionClick', event)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChevronUp = createLucideIcon('ChevronUp', chevronUp)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Listbox v-model="selectedOption">
|
||||||
|
<div class="menu" >
|
||||||
|
<div class="button-wrapper">
|
||||||
|
<VPButton
|
||||||
|
v-bind="$attrs"
|
||||||
|
:text="selectedOption"
|
||||||
|
@click="onClick"
|
||||||
|
theme="alt"
|
||||||
|
class="main-button"
|
||||||
|
:class="[props.buttonClass]"
|
||||||
|
ref="buttonRef"
|
||||||
|
/>
|
||||||
|
<ListboxButton
|
||||||
|
:as="VPButton"
|
||||||
|
:text="''"
|
||||||
|
theme="alt"
|
||||||
|
class="arrow-up-button"
|
||||||
|
:class="popoverPosition"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ListboxOptions class="menu-items" :class="popoverPosition">
|
||||||
|
<ListboxOption
|
||||||
|
as="button"
|
||||||
|
class="menu-item"
|
||||||
|
v-for="option in options"
|
||||||
|
:value="option.text"
|
||||||
|
@click="onOptionClick($event, option)"
|
||||||
|
>
|
||||||
|
{{ option.text }}
|
||||||
|
</ListboxOption>
|
||||||
|
</ListboxOptions>
|
||||||
|
</div>
|
||||||
|
</Listbox>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.menu {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.menu-items {
|
||||||
|
--menu-offset: 44px;
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
min-width: 128px;
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
background-color: var(--vp-c-bg);
|
||||||
|
box-shadow: var(--vp-shadow-3);
|
||||||
|
transition: background-color 0.5s;
|
||||||
|
max-height: calc(100vh - var(--vp-nav-height));
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 90;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
padding: 2px 8px;
|
||||||
|
text-align: left;
|
||||||
|
display: block;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0 12px;
|
||||||
|
line-height: 32px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: background-color .25s,color .25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:hover {
|
||||||
|
color: var(--vp-c-brand);
|
||||||
|
background-color: var(--vp-c-bg-elv-mute);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:active {
|
||||||
|
color: var(--vp-c-brand);
|
||||||
|
background-color: var(--vp-c-bg-elv);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-button {
|
||||||
|
border-top-right-radius: 0 !important;
|
||||||
|
border-bottom-right-radius: 0 !important;
|
||||||
|
padding-right: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-wrapper {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-up-button {
|
||||||
|
display: inline-flex;
|
||||||
|
height: 40px;
|
||||||
|
border-top-left-radius: 0 !important;
|
||||||
|
border-bottom-left-radius: 0 !important;
|
||||||
|
padding-left: 4px !important;
|
||||||
|
padding-right: 8px !important;
|
||||||
|
position: relative;
|
||||||
|
left: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-up-button::before {
|
||||||
|
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%0A%3E%3Cpolyline points='18 15 12 9 6 15' /%3E%3C/svg%3E%0A");
|
||||||
|
width: 20px;
|
||||||
|
height: 28px;
|
||||||
|
margin: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .arrow-up-button::before {
|
||||||
|
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%0A%3E%3Cpolyline points='18 15 12 9 6 15' /%3E%3C/svg%3E%0A");
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-items.bottom {
|
||||||
|
top: var(--menu-offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-items.top {
|
||||||
|
bottom: var(--menu-offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-up-button.top::before {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-up-button.bottom::before {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
51
docs/.vitepress/theme/components/base/CodeGroup.vue
Normal file
51
docs/.vitepress/theme/components/base/CodeGroup.vue
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, defineProps, onMounted } from 'vue'
|
||||||
|
const props = defineProps<{
|
||||||
|
groups: string[] | undefined,
|
||||||
|
groupName: string,
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const getSaveIdname = (name: string) => {
|
||||||
|
return name.toLowerCase().replace(/\s/g, '-')
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabs = computed(() => props.groups?.map((group) => {
|
||||||
|
return {
|
||||||
|
id: getSaveIdname(group),
|
||||||
|
name: group,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const saveTabId = (id: string) => {
|
||||||
|
localStorage.setItem(props.groupName, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const id = localStorage.getItem(props.groupName)
|
||||||
|
if (id) {
|
||||||
|
const tab = document.getElementById(`label-tab-${id}`)
|
||||||
|
|
||||||
|
if (tab) {
|
||||||
|
tab.click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="vp-code-group">
|
||||||
|
<div class="tabs">
|
||||||
|
<template v-for="(tab, index) in tabs">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
:id="`tab-${tab.id}`"
|
||||||
|
:name="`group-${groupName}`"
|
||||||
|
:checked="index === 0"
|
||||||
|
@change="saveTabId(tab.id)"
|
||||||
|
>
|
||||||
|
<label :for="`tab-${tab.id}`" :id="`label-tab-${tab.id}`">{{ tab.name }}</label>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
93
docs/.vitepress/theme/components/base/ColorPicker.vue
Normal file
93
docs/.vitepress/theme/components/base/ColorPicker.vue
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: string
|
||||||
|
id: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const value = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (val) => emit('update:modelValue', val)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="color-picker">
|
||||||
|
<div class="color-input-wrapper">
|
||||||
|
<!-- TODO: Add currentColor div if value is currentColor -->
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
:id="id"
|
||||||
|
:name="id"
|
||||||
|
class="color-input"
|
||||||
|
v-model="value"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
:id="`${id}-input`"
|
||||||
|
:name="`${id}-input`"
|
||||||
|
class="color-input-text"
|
||||||
|
aria-label="Color picker input"
|
||||||
|
v-model="value"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.color-input {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
left: -5px;
|
||||||
|
}
|
||||||
|
.color-input-wrapper {
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.color-picker {
|
||||||
|
background: var(--color-picker-bg, var(--vp-c-bg-soft));
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
padding: 4px 8px;
|
||||||
|
height: auto;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: left;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
cursor: text;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-input-text {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 8px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: left;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: text;
|
||||||
|
transition: border-color 0.25s, background 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker:hover, .color-picker:focus {
|
||||||
|
border-color: var(--vp-c-brand);
|
||||||
|
background: var(--vp-c-bg-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-input[value="currentColor"] {
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
docs/.vitepress/theme/components/base/EndOfPage.vue
Normal file
15
docs/.vitepress/theme/components/base/EndOfPage.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { vIntersectionObserver } from '@vueuse/components'
|
||||||
|
|
||||||
|
const emit = defineEmits(['end-of-page'])
|
||||||
|
|
||||||
|
const onIntersectionObserver: IntersectionObserverCallback = ([{ isIntersecting }]) => {
|
||||||
|
if (isIntersecting) {
|
||||||
|
emit('end-of-page')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-intersection-observer="onIntersectionObserver" />
|
||||||
|
</template>
|
||||||
36
docs/.vitepress/theme/components/base/FakeInput.vue
Normal file
36
docs/.vitepress/theme/components/base/FakeInput.vue
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<script setup>
|
||||||
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
||||||
|
import { search } from '../../../data/iconNodes'
|
||||||
|
|
||||||
|
const SearchIcon = createLucideIcon('search', search)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button class="fake-input">
|
||||||
|
<component :is="SearchIcon" class="search-icon"/>
|
||||||
|
<slot/>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.fake-input {
|
||||||
|
background: var(--vp-c-bg-soft);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
padding: 12px 16px;
|
||||||
|
height: auto;
|
||||||
|
font-size: 14px;
|
||||||
|
/* box-shadow: var(--vp-shadow-4), var(--vp-shadow-2); */
|
||||||
|
text-align: left;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
cursor: text;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fake-input:hover, .fake-input:focus {
|
||||||
|
border-color: var(--vp-c-brand);
|
||||||
|
background: var(--vp-c-bg-alt);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
43
docs/.vitepress/theme/components/base/IconButton.vue
Normal file
43
docs/.vitepress/theme/components/base/IconButton.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<button v-bind="$attrs" class="icon-button">
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.icon-button {
|
||||||
|
display: inline-flex;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
/* width: 56px;
|
||||||
|
height: 56px; */
|
||||||
|
font-size: 24px;
|
||||||
|
transition: color 0.1s, border-color 0.1s, background-color 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button:hover {
|
||||||
|
border-color: var(--vp-button-alt-hover-border);
|
||||||
|
color: var(--vp-button-alt-hover-text);
|
||||||
|
background-color: var(--vp-button-alt-hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button:active {
|
||||||
|
border-color: var(--vp-button-alt-active-border);
|
||||||
|
color: var(--vp-button-alt-active-text);
|
||||||
|
background-color: var(--vp-button-alt-active-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button.active {
|
||||||
|
border-color: var(--vp-c-brand);
|
||||||
|
/* color: var(--vp-button-alt-active-text);
|
||||||
|
background-color: var(--vp-button-alt-active-bg); */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
81
docs/.vitepress/theme/components/base/Input.vue
Normal file
81
docs/.vitepress/theme/components/base/Input.vue
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
inheritAttrs: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InputProps {
|
||||||
|
type: string
|
||||||
|
modelValue: string
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<InputProps>(), {
|
||||||
|
type: 'text'
|
||||||
|
})
|
||||||
|
|
||||||
|
const input = ref()
|
||||||
|
|
||||||
|
defineEmits(['change', 'input', 'update:modelValue'])
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
focus: () => {
|
||||||
|
input.value.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<slot name="icon" class="icon" />
|
||||||
|
<input
|
||||||
|
:type="type"
|
||||||
|
class="input"
|
||||||
|
:class="{'has-icon': $slots.icon}"
|
||||||
|
ref="input"
|
||||||
|
:value="modelValue"
|
||||||
|
v-bind="$attrs"
|
||||||
|
@input="$emit('update:modelValue', $event.target.value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.input {
|
||||||
|
justify-content: flex-start;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0 10px 0 12px;
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:hover, .input:focus {
|
||||||
|
border-color: var(--vp-c-brand);
|
||||||
|
background: var(--vp-c-bg-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input.has-icon {
|
||||||
|
padding-left: 52px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.input-wrapper svg {
|
||||||
|
position: absolute;
|
||||||
|
left: 16px;
|
||||||
|
top: 12px;
|
||||||
|
z-index: 1;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
48
docs/.vitepress/theme/components/base/InputField.vue
Normal file
48
docs/.vitepress/theme/components/base/InputField.vue
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
label: string
|
||||||
|
id: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="input-field">
|
||||||
|
<div class="input-label" v-if="label">
|
||||||
|
<label :for="id" class="customize-label">
|
||||||
|
{{ label }}
|
||||||
|
</label>
|
||||||
|
<div class="display-value" >
|
||||||
|
<slot name="display"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.customize-label {
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--vt-c-text-1);
|
||||||
|
transition: color .5s;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-field:not(:last-child) {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-value {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
74
docs/.vitepress/theme/components/base/InputSearch.vue
Normal file
74
docs/.vitepress/theme/components/base/InputSearch.vue
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
inheritAttrs: false,
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import Input from './Input.vue'
|
||||||
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
||||||
|
import { search } from '../../../data/iconNodes'
|
||||||
|
|
||||||
|
const SearchIcon = createLucideIcon('search', search)
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const input = ref()
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
focus: () => {
|
||||||
|
input.value.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const value = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (val) => emit('update:modelValue', val)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Input
|
||||||
|
ref="input"
|
||||||
|
type="search"
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-model="value"
|
||||||
|
class="input-wrapper"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<component :is="SearchIcon" class="search-icon" />
|
||||||
|
</template>
|
||||||
|
</Input>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.input {
|
||||||
|
justify-content: flex-start;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0 10px 0 12px;
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:hover, .input:focus {
|
||||||
|
border-color: var(--vp-c-brand);
|
||||||
|
background: var(--vp-c-bg-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper:deep(.input) {
|
||||||
|
/* padding: 12px 24px; */
|
||||||
|
padding-block: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
16
docs/.vitepress/theme/components/base/Label.vue
Normal file
16
docs/.vitepress/theme/components/base/Label.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
<template>
|
||||||
|
<h2 class="label"><slot/></h2>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.label {
|
||||||
|
letter-spacing: 0.4px;
|
||||||
|
line-height: 28px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
19
docs/.vitepress/theme/components/base/LucideIcon.vue
Normal file
19
docs/.vitepress/theme/components/base/LucideIcon.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||||
|
|
||||||
|
export type IconNode = [elementName: string, attrs: Record<string, string>][]
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
name: string;
|
||||||
|
tags: string[];
|
||||||
|
categories: string[];
|
||||||
|
// contributors: Contributor[];
|
||||||
|
iconNode: IconNode;
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const icon = createLucideIcon(props.name, props.iconNode)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<component :is="icon" />
|
||||||
|
</template>
|
||||||
127
docs/.vitepress/theme/components/base/RangeSlider.vue
Normal file
127
docs/.vitepress/theme/components/base/RangeSlider.vue
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
inheritAttrs: false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: number | string;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step?: number;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
min: 0,
|
||||||
|
max: 48,
|
||||||
|
})
|
||||||
|
|
||||||
|
defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const percentage = computed<string>(() => `${((Number(props.modelValue) - props.min) / (props.max - props.min)) * 100}%`);
|
||||||
|
// TODO: Steps must be implemented
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="slider">
|
||||||
|
<input
|
||||||
|
:id="id"
|
||||||
|
type="range"
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-bind:value="modelValue"
|
||||||
|
v-on:input="$emit('update:modelValue', Number($event.target.value))"
|
||||||
|
:min="min"
|
||||||
|
:max="max"
|
||||||
|
:step="step"
|
||||||
|
/>
|
||||||
|
<div class="bar"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.slider {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
line-height: 10px;
|
||||||
|
height: 20px;
|
||||||
|
--bar-color: var(--slider-bar-color, var(--vp-c-bg-soft));
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider:hover input{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.slider .bar {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.slider .bar:before, .slider .bar:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
height: 4px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.slider .bar:before {
|
||||||
|
width: v-bind(percentage);
|
||||||
|
z-index: 1;
|
||||||
|
left: 0;
|
||||||
|
background: var(--vp-c-brand);
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
}
|
||||||
|
.slider .bar:after {
|
||||||
|
background: var(--bar-color);
|
||||||
|
width: calc(100% - v-bind(percentage));
|
||||||
|
right: 0;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
.slider input {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: calc(100% + 20px);
|
||||||
|
height: 20px;
|
||||||
|
left: -10px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
background: transparent;
|
||||||
|
outline: none;
|
||||||
|
/* opacity: 0.7; */
|
||||||
|
-webkit-transition: 0.2s;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
/* @apply
|
||||||
|
md:opacity-0 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider input::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: var(--vp-shadow-2);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider input::-moz-range-thumb {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
appearance: none;
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 10px;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
box-shadow: var(--vp-shadow-2);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
43
docs/.vitepress/theme/components/base/ResetButton.vue
Normal file
43
docs/.vitepress/theme/components/base/ResetButton.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { rotateCw } from '../../../data/iconNodes'
|
||||||
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
||||||
|
import IconButton from "./IconButton.vue";
|
||||||
|
|
||||||
|
const RotateIcon = createLucideIcon('RotateIcon', rotateCw)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<IconButton class="reset-button">
|
||||||
|
<RotateIcon :size="20"/>
|
||||||
|
</IconButton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.reset-button {
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-button .lucide {
|
||||||
|
transition: ease-in-out 0.1s transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-button:hover {
|
||||||
|
background: none;
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* a rotate css animation keyframes */
|
||||||
|
@keyframes rotate {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(359deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-button:active .lucide {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
77
docs/.vitepress/theme/components/base/Switch.vue
Normal file
77
docs/.vitepress/theme/components/base/Switch.vue
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { Switch } from '@headlessui/vue'
|
||||||
|
|
||||||
|
const enabled = ref(false)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Switch
|
||||||
|
v-model="enabled"
|
||||||
|
class="switch"
|
||||||
|
:class="{ enabled }"
|
||||||
|
>
|
||||||
|
<span class="thumb" />
|
||||||
|
</Switch>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.switch {
|
||||||
|
display: inline-flex;
|
||||||
|
position: relative;
|
||||||
|
width: 40px;
|
||||||
|
height: 22px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border: 1px solid var(--vp-input-border-color);
|
||||||
|
background-color: var(--vp-input-switch-bg-color);
|
||||||
|
transition: border-color 0.25s, background-color 0.4s ease;
|
||||||
|
border-radius: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch.enabled {
|
||||||
|
background-color: var(--vp-c-brand);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.switch:hover {
|
||||||
|
border-color: var(--vp-input-hover-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #fff;
|
||||||
|
transition: transform 0.25s;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
/* background-color: var(--vp-c-neutral); */
|
||||||
|
box-shadow: var(--vp-shadow-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch.enabled .thumb {
|
||||||
|
transform: translateX(18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------- */
|
||||||
|
.VPSwitch {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 11px;
|
||||||
|
display: block;
|
||||||
|
width: 40px;
|
||||||
|
height: 22px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border: 1px solid var(--vp-input-border-color);
|
||||||
|
background-color: var(--vp-input-switch-bg-color);
|
||||||
|
transition: border-color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPSwitch:hover {
|
||||||
|
border-color: var(--vp-input-hover-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .icon :deep(svg) {
|
||||||
|
fill: var(--vp-c-text-1);
|
||||||
|
transition: opacity 0.25s;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
55
docs/.vitepress/theme/components/base/Tooltip.vue
Normal file
55
docs/.vitepress/theme/components/base/Tooltip.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {onMounted, ref} from 'vue';
|
||||||
|
import {useFloating, offset, shift} from '@floating-ui/vue';
|
||||||
|
|
||||||
|
const reference = ref(null);
|
||||||
|
const tooltip = ref(null);
|
||||||
|
const middleware = ref([shift(), offset(8)]);
|
||||||
|
const { floatingStyles, update } = useFloating(reference, tooltip, {
|
||||||
|
middleware,
|
||||||
|
transform: false
|
||||||
|
});
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
title: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
update()
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span ref="reference" class="reference">
|
||||||
|
<slot/>
|
||||||
|
</span>
|
||||||
|
<div ref="tooltip" class="tooltip" :style="floatingStyles">
|
||||||
|
{{title}}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.reference:hover + .tooltip{
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
transform: scale(0.9);
|
||||||
|
font-weight: 400;
|
||||||
|
background: var(--vp-c-brand-dark);
|
||||||
|
color: white;
|
||||||
|
z-index: 10;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: var(--vp-shadow-1);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: cubic-bezier(0.19, 1, 0.22, 1) .2s;
|
||||||
|
transition-property: opacity, transform;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
35
docs/.vitepress/theme/components/home/HomeContainer.vue
Normal file
35
docs/.vitepress/theme/components/home/HomeContainer.vue
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<template>
|
||||||
|
<div class="home-container">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.home-container {
|
||||||
|
position: relative;
|
||||||
|
padding-inline: 24px;
|
||||||
|
margin-block: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.home-container {
|
||||||
|
padding-inline: 48px;
|
||||||
|
margin-block: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.home-container {
|
||||||
|
padding-inline: 64px;
|
||||||
|
margin-block: 64px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
.home-container {
|
||||||
|
margin: 64px auto;
|
||||||
|
max-width: 1152px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
16
docs/.vitepress/theme/components/home/HomeHeroBefore.data.ts
Normal file
16
docs/.vitepress/theme/components/home/HomeHeroBefore.data.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export default {
|
||||||
|
async load() {
|
||||||
|
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest').then(res => {
|
||||||
|
if (res.ok) {
|
||||||
|
const releaseData = res.json() as Promise<{ tag_name: string }>
|
||||||
|
|
||||||
|
return releaseData
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}).then(res => res.tag_name)
|
||||||
|
|
||||||
|
return {
|
||||||
|
version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
docs/.vitepress/theme/components/home/HomeHeroBefore.vue
Normal file
44
docs/.vitepress/theme/components/home/HomeHeroBefore.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Badge from '../base/Badge.vue';
|
||||||
|
import HomeContainer from './HomeContainer.vue';
|
||||||
|
import { data } from './HomeHeroBefore.data'
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<HomeContainer class="container">
|
||||||
|
<Badge
|
||||||
|
:href="`https://github.com/lucide-icons/lucide/releases/tag/${data.version}`"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
>{{ data.version }}</Badge>
|
||||||
|
</HomeContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
margin-block: 0;;
|
||||||
|
margin-top: 37px;
|
||||||
|
margin-bottom: -96px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.container {
|
||||||
|
margin-bottom: -131px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.container {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import iconNodes from '../../../data/iconNodes'
|
||||||
|
|
||||||
|
const getRandomItem = <Item>(items: Item[]): Item => items[Math.floor(Math.random()*items.length)];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
async load() {
|
||||||
|
const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode }))
|
||||||
|
|
||||||
|
const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons))
|
||||||
|
|
||||||
|
return {
|
||||||
|
icons: randomIcons,
|
||||||
|
iconsCount: icons.length,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
160
docs/.vitepress/theme/components/home/HomeHeroIconsCard.vue
Normal file
160
docs/.vitepress/theme/components/home/HomeHeroIconsCard.vue
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, shallowRef, onBeforeUnmount} from 'vue';
|
||||||
|
import { data } from './HomeHeroIconsCard.data'
|
||||||
|
import LucideIcon from '../base/LucideIcon.vue'
|
||||||
|
import { useRouter } from 'vitepress';
|
||||||
|
import { random } from 'lodash-es'
|
||||||
|
import FakeInput from '../base/FakeInput.vue'
|
||||||
|
|
||||||
|
const { go } = useRouter()
|
||||||
|
const intervalTime = shallowRef()
|
||||||
|
|
||||||
|
const getInitialItems = () => data.icons.slice(0, 48)
|
||||||
|
const items = ref(getInitialItems())
|
||||||
|
let id = items.value.length + 1
|
||||||
|
|
||||||
|
function getRandomNewIcon() {
|
||||||
|
const randomIndex = random(0, 200)
|
||||||
|
const newRandomIcon = data.icons[randomIndex]
|
||||||
|
|
||||||
|
if (items.value.some((item) => item.name === newRandomIcon.name)) {
|
||||||
|
return getRandomNewIcon()
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRandomIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
function insert() {
|
||||||
|
const replaceIndex = random(0, 48)
|
||||||
|
const newIcon = getRandomNewIcon()
|
||||||
|
|
||||||
|
// items.value.splice(replaceIndex, 0, newIcon);
|
||||||
|
|
||||||
|
items.value[replaceIndex] = newIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
function startInterval() {
|
||||||
|
intervalTime.value = setInterval(() => {
|
||||||
|
insert()
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Try maybe something else for better pref performance
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('mousemove', startInterval, { once: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
clearInterval(intervalTime.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="card-wrapper">
|
||||||
|
<div class="icons-card">
|
||||||
|
<div class="card-grid">
|
||||||
|
<TransitionGroup name="list" mode="out-in">
|
||||||
|
<div
|
||||||
|
v-for="icon in items"
|
||||||
|
:key="icon.name"
|
||||||
|
class="random-icon"
|
||||||
|
>
|
||||||
|
<LucideIcon
|
||||||
|
v-bind="icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TransitionGroup>
|
||||||
|
</div>
|
||||||
|
<FakeInput @click="go('/icons/?focus')" class="search-box">
|
||||||
|
Search {{ data.iconsCount }} icons...
|
||||||
|
</FakeInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.card-wrapper {
|
||||||
|
/* padding: 0 24px; */
|
||||||
|
margin-left: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
margin-top: 48px;
|
||||||
|
}
|
||||||
|
.icons-card {
|
||||||
|
background: var(--vp-c-bg-alt);
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 100%;
|
||||||
|
height:100%;
|
||||||
|
/* box-shadow: var(--vp-shadow-2); */
|
||||||
|
max-height: 220px;
|
||||||
|
max-width: 560px;
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
/* max-height: 240px; */
|
||||||
|
/* margin-top: 96px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(36px, 1fr));
|
||||||
|
grid-template-rows: repeat(auto-fill, minmax(36px, 1fr));
|
||||||
|
width: 100%;
|
||||||
|
height:100%;
|
||||||
|
max-height: 168px;
|
||||||
|
max-width: 512px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
/* white-space: nowrap; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-enter-active {
|
||||||
|
transition: all 0.5s cubic-bezier(.85,.85,.25,1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-enter-from,
|
||||||
|
.list-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: -64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.random-icon {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.search-box {
|
||||||
|
top: unset;
|
||||||
|
bottom: -24px;
|
||||||
|
left: -24px;
|
||||||
|
|
||||||
|
box-shadow: var(--vp-shadow-3);
|
||||||
|
background: var(--vp-c-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .search-box {
|
||||||
|
background: var(--vp-c-bg-soft);
|
||||||
|
}
|
||||||
|
.card-wrapper {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
195
docs/.vitepress/theme/components/home/HomeIconCustomizer.vue
Normal file
195
docs/.vitepress/theme/components/home/HomeIconCustomizer.vue
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { syncRef, useCssVar } from '@vueuse/core'
|
||||||
|
import HomeContainer from './HomeContainer.vue'
|
||||||
|
import RangeSlider from '../base/RangeSlider.vue'
|
||||||
|
import InputField from '../base/InputField.vue'
|
||||||
|
import ColorPicker from '../base/ColorPicker.vue'
|
||||||
|
import ResetButton from '../base/ResetButton.vue'
|
||||||
|
import HomeIconCustomizerIcons from './HomeIconCustomizerIcons.vue'
|
||||||
|
import Switch from '../base/Switch.vue'
|
||||||
|
|
||||||
|
|
||||||
|
const iconContainer = ref<HTMLElement | null>()
|
||||||
|
const color = ref('currentColor')
|
||||||
|
const strokeWidth = ref(2)
|
||||||
|
const size = ref(24)
|
||||||
|
const absoluteStrokeWidth = ref(false)
|
||||||
|
|
||||||
|
const colorCssVar = useCssVar(
|
||||||
|
'--customize-color',
|
||||||
|
iconContainer,
|
||||||
|
{
|
||||||
|
initialValue: 'default'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const strokeWidthCssVar = useCssVar(
|
||||||
|
'--customize-strokeWidth',
|
||||||
|
iconContainer,
|
||||||
|
{
|
||||||
|
initialValue: '2'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const sizeCssVar = useCssVar(
|
||||||
|
'--customize-size',
|
||||||
|
iconContainer,
|
||||||
|
{
|
||||||
|
initialValue: '24'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
syncRef(color, colorCssVar)
|
||||||
|
syncRef(strokeWidth, strokeWidthCssVar)
|
||||||
|
syncRef(size, sizeCssVar)
|
||||||
|
|
||||||
|
function resetStyle () {
|
||||||
|
color.value = 'currentColor'
|
||||||
|
strokeWidth.value = 2
|
||||||
|
size.value = 24
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(absoluteStrokeWidth, (enabled) => {
|
||||||
|
iconContainer.value?.classList.toggle('absolute-stroke-width', enabled)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<HomeContainer>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-column">
|
||||||
|
<h2 class="title">
|
||||||
|
Style as you please
|
||||||
|
<ResetButton @click="resetStyle"></ResetButton>
|
||||||
|
</h2>
|
||||||
|
<p class="copy">
|
||||||
|
Lucide has a lot of customization options to match the icons with your UI.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="customizer">
|
||||||
|
<InputField
|
||||||
|
id="icon-color"
|
||||||
|
label="Color"
|
||||||
|
class="color-picker-field"
|
||||||
|
>
|
||||||
|
<template #display>
|
||||||
|
<ColorPicker v-model="color" id="icon-color" />
|
||||||
|
</template>
|
||||||
|
</InputField>
|
||||||
|
|
||||||
|
<InputField
|
||||||
|
id="stroke-width"
|
||||||
|
label="Stroke width"
|
||||||
|
>
|
||||||
|
<template #display>
|
||||||
|
<span class="customize-label">{{ strokeWidth }}px</span>
|
||||||
|
</template>
|
||||||
|
<RangeSlider
|
||||||
|
id="stroke-width"
|
||||||
|
name="stroke-width"
|
||||||
|
v-model="strokeWidth"
|
||||||
|
:min="1"
|
||||||
|
:max="3"
|
||||||
|
:step="0.25"
|
||||||
|
/>
|
||||||
|
</InputField>
|
||||||
|
|
||||||
|
<InputField
|
||||||
|
id="size"
|
||||||
|
label="Size"
|
||||||
|
>
|
||||||
|
<template #display>
|
||||||
|
<span class="customize-label">{{ size }}px</span>
|
||||||
|
</template>
|
||||||
|
<RangeSlider
|
||||||
|
id="size"
|
||||||
|
name="size"
|
||||||
|
v-model="size"
|
||||||
|
:min="16"
|
||||||
|
:max="48"
|
||||||
|
:step="4"
|
||||||
|
/>
|
||||||
|
</InputField>
|
||||||
|
|
||||||
|
<InputField
|
||||||
|
id="absolute-stroke-width"
|
||||||
|
label="Absolute Stroke width"
|
||||||
|
>
|
||||||
|
<template #display>
|
||||||
|
<Switch
|
||||||
|
id="absolute-stroke-width"
|
||||||
|
name="absolute-stroke-width"
|
||||||
|
v-model="absoluteStrokeWidth"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</InputField>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="icons-container card-column" ref="iconContainer">
|
||||||
|
<HomeIconCustomizerIcons />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</HomeContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.card {
|
||||||
|
display: block;
|
||||||
|
border-radius: 12px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--vp-c-bg-soft);
|
||||||
|
padding: 24px;
|
||||||
|
--slider-bar-color: var(--vp-c-bg-soft-down);
|
||||||
|
--color-picker-bg: var(--vp-c-bg-soft-down);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
line-height: 32px;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy {
|
||||||
|
padding-top: 8px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.customizer {
|
||||||
|
margin-top: 32px;
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
max-width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
|
||||||
|
.card {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 8fr 10fr;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
.card-column {
|
||||||
|
flex: 1;
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.card {
|
||||||
|
grid-template-columns: 1fr 2fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker-field:deep(.display-value) {
|
||||||
|
width: 138px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { data } from './HomeHeroIconsCard.data'
|
||||||
|
import LucideIcon from '../base/LucideIcon.vue'
|
||||||
|
import { vIntersectionObserver } from '@vueuse/components'
|
||||||
|
|
||||||
|
const getInitialItems = () => data.icons.slice(0, 64)
|
||||||
|
const items = ref(getInitialItems())
|
||||||
|
const showIcons = ref(false)
|
||||||
|
|
||||||
|
// Added intersection observer to improve performance
|
||||||
|
const onIntersectionObserver: IntersectionObserverCallback = ([{ isIntersecting }]) => {
|
||||||
|
if (isIntersecting) {
|
||||||
|
showIcons.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="icon-grid" v-intersection-observer="onIntersectionObserver">
|
||||||
|
<template v-if="showIcons">
|
||||||
|
<div
|
||||||
|
v-for="icon in items"
|
||||||
|
class="icon-grid-item"
|
||||||
|
>
|
||||||
|
<LucideIcon v-bind="icon" class="lucide-icon"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.icon-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 1px;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(68px, 1fr));
|
||||||
|
grid-template-rows: repeat(auto-fill, minmax(68px, 1fr));
|
||||||
|
width: 100%;
|
||||||
|
height:100%;
|
||||||
|
max-height: 360px;
|
||||||
|
gap: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 8px solid var(--vp-c-bg);
|
||||||
|
position: relative;
|
||||||
|
top: 48px;
|
||||||
|
right: 0;
|
||||||
|
box-shadow: var(--vp-shadow-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-grid-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: var(--vp-c-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.icon-grid {
|
||||||
|
top: 0;
|
||||||
|
right: -48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lucide-icon {
|
||||||
|
will-change: width, height, stroke-width, stroke;
|
||||||
|
color: var(--customize-color, currentColor);
|
||||||
|
stroke-width: var(--customize-strokeWidth, 2);
|
||||||
|
width: calc(var(--customize-size, 24) * 1px);
|
||||||
|
height: calc(var(--customize-size, 24) * 1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icons-container.absolute-stroke-width .lucide-icon {
|
||||||
|
stroke-width: calc(var(--customize-strokeWidth, 2) * 24 / var(--customize-size, 24));
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
export default {
|
||||||
|
async load() {
|
||||||
|
return {
|
||||||
|
packages: [
|
||||||
|
{
|
||||||
|
name: 'lucide',
|
||||||
|
logo: '/framework-logos/js.svg',
|
||||||
|
label: 'Lucide documentation for JavaScript',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lucide-react',
|
||||||
|
logo: '/framework-logos/react.svg',
|
||||||
|
label: 'Lucide documentation for React',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lucide-vue-next',
|
||||||
|
logo: '/framework-logos/vue.svg',
|
||||||
|
label: 'Lucide documentation for Vue 3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lucide-svelte',
|
||||||
|
logo: '/framework-logos/svelte.svg',
|
||||||
|
label: 'Lucide documentation for Svelte',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lucide-preact',
|
||||||
|
logo: '/framework-logos/preact.svg',
|
||||||
|
label: 'Lucide documentation for Preact',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lucide-solid',
|
||||||
|
logo: '/framework-logos/solid.svg',
|
||||||
|
label: 'Lucide documentation for Solid',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lucide-angular',
|
||||||
|
logo: '/framework-logos/angular.svg',
|
||||||
|
label: 'Lucide documentation for Angular',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lucide-react-native',
|
||||||
|
logo: '/framework-logos/react-native.svg',
|
||||||
|
label: 'Lucide documentation for React Native',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lucide-flutter',
|
||||||
|
logo: '/framework-logos/flutter.svg',
|
||||||
|
label: 'Lucide documentation for Flutter',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import HomeContainer from './HomeContainer.vue'
|
||||||
|
import { useRouter } from 'vitepress';
|
||||||
|
import { data } from './HomePackagesSection.data'
|
||||||
|
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||||
|
|
||||||
|
const { go } = useRouter()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<HomeContainer>
|
||||||
|
<h2 class="section-title">Available For:</h2>
|
||||||
|
<div class="packages-list">
|
||||||
|
<a
|
||||||
|
v-for="{ name, logo } in data.packages"
|
||||||
|
:href="`/guide/packages/${name}`"
|
||||||
|
class="package-logo"
|
||||||
|
:aria-label="`Read more about: ${name} package`"
|
||||||
|
@click.prevent="go(`/guide/packages/${name}`)"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="logo"
|
||||||
|
height="36"
|
||||||
|
width="36"
|
||||||
|
loading="lazy"
|
||||||
|
:alt="`${name} logo`"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="more-button-wrapper">
|
||||||
|
<VPButton text="And more" href="/packages" theme="alt" class="more-button"/>
|
||||||
|
</div>
|
||||||
|
</HomeContainer>
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
.packages-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 -0.5rem;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-button-wrapper {
|
||||||
|
margin-top: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-logo {
|
||||||
|
transition: opacity ease-in .15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-logo:hover {
|
||||||
|
opacity: .6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
16
docs/.vitepress/theme/components/icons/CategoryList.data.ts
Normal file
16
docs/.vitepress/theme/components/icons/CategoryList.data.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { getAllData } from '../../../lib/icons';
|
||||||
|
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
|
||||||
|
import iconsMetaData from '../../../data/iconMetaData'
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
async load() {
|
||||||
|
let categories = getAllCategoryFiles()
|
||||||
|
|
||||||
|
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData))
|
||||||
|
|
||||||
|
return {
|
||||||
|
categories,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
99
docs/.vitepress/theme/components/icons/CategoryList.vue
Normal file
99
docs/.vitepress/theme/components/icons/CategoryList.vue
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { useData } from 'vitepress'
|
||||||
|
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
|
||||||
|
import { isActive } from 'vitepress/dist/client/shared'
|
||||||
|
import { useActiveAnchor } from '../../composables/useActiveAnchor'
|
||||||
|
import { data } from './CategoryList.data'
|
||||||
|
import CategoryListItem from './CategoryListItem.vue'
|
||||||
|
|
||||||
|
const { page } = useData()
|
||||||
|
|
||||||
|
const categoriesIsActive = computed(() => {
|
||||||
|
return isActive(page.value.relativePath, '/icons/categories');
|
||||||
|
});
|
||||||
|
|
||||||
|
const overviewIsActive = computed(() => {
|
||||||
|
return isActive(page.value.relativePath, '/icons/');
|
||||||
|
});
|
||||||
|
|
||||||
|
const headers = computed(() => {
|
||||||
|
const linkPrefix = page.value.relativePath.startsWith('icons/categories')
|
||||||
|
? '' : '/icons/categories'
|
||||||
|
|
||||||
|
return data.categories.map(({ name, title, iconCount }) => ({
|
||||||
|
level: 2,
|
||||||
|
link: `${linkPrefix}#${name}`,
|
||||||
|
title,
|
||||||
|
iconCount
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
const container = ref()
|
||||||
|
const marker = ref()
|
||||||
|
|
||||||
|
useActiveAnchor(container, marker)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="category-list" ref="container">
|
||||||
|
<VPLink class="sidebar-title" href="/icons/" :class="{ 'active': overviewIsActive } ">
|
||||||
|
All
|
||||||
|
</VPLink>
|
||||||
|
<VPLink class="sidebar-title" href="/icons/categories" :class="{ 'active': categoriesIsActive } ">
|
||||||
|
Categories
|
||||||
|
</VPLink>
|
||||||
|
<div class="content">
|
||||||
|
<div class="outline-marker" ref="marker" />
|
||||||
|
<nav aria-labelledby="doc-outline-aria-label">
|
||||||
|
<CategoryListItem :headers="headers" :root="true" />
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.sidebar-title {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
display: block;
|
||||||
|
transition: color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-title:hover, .sidebar-title.active {
|
||||||
|
color: var(--vp-c-brand);
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
margin-top: 12px;
|
||||||
|
position: relative;
|
||||||
|
border-left: 1px solid var(--vp-c-divider);
|
||||||
|
padding-left: 16px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline-marker {
|
||||||
|
position: absolute;
|
||||||
|
top: 32px;
|
||||||
|
left: -1px;
|
||||||
|
z-index: 0;
|
||||||
|
opacity: 0;
|
||||||
|
width: 1px;
|
||||||
|
height: 18px;
|
||||||
|
background-color: var(--vp-c-brand);
|
||||||
|
transition: top 0.25s cubic-bezier(0, 1, 0.5, 1), background-color 0.5s, opacity 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline-title {
|
||||||
|
letter-spacing: 0.4px;
|
||||||
|
line-height: 28px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.root {
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
95
docs/.vitepress/theme/components/icons/CategoryListItem.vue
Normal file
95
docs/.vitepress/theme/components/icons/CategoryListItem.vue
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
import { useCategoryView } from '../../composables/useCategoryView'
|
||||||
|
|
||||||
|
interface Header {
|
||||||
|
level: number
|
||||||
|
title: string
|
||||||
|
slug: string
|
||||||
|
iconCount: number
|
||||||
|
link: string
|
||||||
|
children: Header[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type MenuItem = Omit<Header, 'slug' | 'children'> & {
|
||||||
|
children?: MenuItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
headers: MenuItem[]
|
||||||
|
root?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { selectedCategory } = useCategoryView()
|
||||||
|
|
||||||
|
function onClick(event: Event) {
|
||||||
|
const target = (event.target as HTMLElement).nodeName === 'span' ? (event.target as HTMLElement).parentNode : event.target as HTMLElement
|
||||||
|
const id = '#' + (target as HTMLAnchorElement).href!.split('#')[1]
|
||||||
|
const decodedId = decodeURIComponent(id)
|
||||||
|
|
||||||
|
selectedCategory.value = decodedId.replace('#', '')
|
||||||
|
|
||||||
|
const heading = document.querySelector<HTMLAnchorElement>(decodedId)
|
||||||
|
heading?.focus()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ul :class="root ? 'root' : 'nested'">
|
||||||
|
<li v-for="{ children, link, title, iconCount } in headers">
|
||||||
|
<a
|
||||||
|
class="outline-link"
|
||||||
|
:href="link"
|
||||||
|
@click="onClick"
|
||||||
|
:title="title"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{ title }}
|
||||||
|
</span>
|
||||||
|
<span class="icon-count" :aria-label="`Count of icons in ${title}`">
|
||||||
|
{{ iconCount }}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.root {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested {
|
||||||
|
padding-left: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
line-height: 28px;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: color 0.5s;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline-link:hover,
|
||||||
|
.outline-link.active {
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
transition: color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline-link.nested {
|
||||||
|
padding-left: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-count {
|
||||||
|
opacity: 0.5;
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
111
docs/.vitepress/theme/components/icons/CopyCodeButton.vue
Normal file
111
docs/.vitepress/theme/components/icons/CopyCodeButton.vue
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { startCase, camelCase } from 'lodash-es'
|
||||||
|
import ButtonMenu from '../base/ButtonMenu.vue'
|
||||||
|
import { useIconStyleContext } from '../../composables/useIconStyle';
|
||||||
|
import useConfetti from '../../composables/useConfetti';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
name: string
|
||||||
|
popoverPosition?: 'top' | 'bottom'
|
||||||
|
}>()
|
||||||
|
const { size, color, strokeWidth, absoluteStrokeWidth } = useIconStyleContext()
|
||||||
|
const { animate, confetti } = useConfetti()
|
||||||
|
const componentName = computed(() => {
|
||||||
|
return startCase(camelCase(props.name)).replace(/\s/g, '')
|
||||||
|
})
|
||||||
|
|
||||||
|
function copyJSX() {
|
||||||
|
let attrs = ['']
|
||||||
|
|
||||||
|
if (size.value && size.value !== 24) {
|
||||||
|
attrs.push(`size={${size.value}}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color.value && color.value !== 'currentColor') {
|
||||||
|
attrs.push(`color="${color.value}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strokeWidth.value && strokeWidth.value !== 2) {
|
||||||
|
attrs.push(`strokeWidth={${strokeWidth.value}}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (absoluteStrokeWidth.value) {
|
||||||
|
attrs.push(`absoluteStrokeWidth`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = `<${componentName.value}${attrs.join(' ')} />`
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyVue() {
|
||||||
|
let attrs = ['']
|
||||||
|
|
||||||
|
if (size.value && size.value !== 24) {
|
||||||
|
attrs.push(`:size="${size.value}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color.value && color.value !== 'currentColor') {
|
||||||
|
attrs.push(`color="${color.value}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strokeWidth.value && strokeWidth.value !== 2) {
|
||||||
|
attrs.push(`:stroke-width="${strokeWidth.value}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (absoluteStrokeWidth.value) {
|
||||||
|
attrs.push(`absoluteStrokeWidth`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = `<${componentName.value}${attrs.join(' ')} />`
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyAngular() {
|
||||||
|
let attrs = ['']
|
||||||
|
|
||||||
|
attrs.push(`name="${props.name}"`)
|
||||||
|
|
||||||
|
if (size.value && size.value !== 24) {
|
||||||
|
attrs.push(`[size]="${size.value}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color.value && color.value !== 'currentColor') {
|
||||||
|
attrs.push(`color="${color.value}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strokeWidth.value && strokeWidth.value !== 2) {
|
||||||
|
attrs.push(`[strokeWidth]="${strokeWidth.value}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (absoluteStrokeWidth.value) {
|
||||||
|
attrs.push(`[absoluteStrokeWidth]="true"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = `<lucide-icon${attrs.join(' ')}></lucide-icon>`
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(code)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ButtonMenu
|
||||||
|
:buttonClass="`confetti-button ${animate ? 'animate' : ''}`"
|
||||||
|
id="copy-code-button"
|
||||||
|
callOptionOnClick
|
||||||
|
@click="confetti"
|
||||||
|
@optionClick="confetti"
|
||||||
|
data-confetti-text="Copied!"
|
||||||
|
:popoverPosition="popoverPosition"
|
||||||
|
:options="[
|
||||||
|
{ text: 'Copy JSX' , onClick: copyJSX },
|
||||||
|
{ text: 'Copy Vue' , onClick: copyVue },
|
||||||
|
{ text: 'Copy Svelte' , onClick: copyJSX },
|
||||||
|
{ text: 'Copy Angular' , onClick: copyAngular },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style src="./confetti.css" />
|
||||||
86
docs/.vitepress/theme/components/icons/CopySVGButton.vue
Normal file
86
docs/.vitepress/theme/components/icons/CopySVGButton.vue
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import ButtonMenu from '../base/ButtonMenu.vue'
|
||||||
|
import { useIconStyleContext } from '../../composables/useIconStyle';
|
||||||
|
import useConfetti from '../../composables/useConfetti';
|
||||||
|
import getSVGIcon from '../../utils/getSVGIcon';
|
||||||
|
import downloadData from '../../utils/downloadData';
|
||||||
|
|
||||||
|
const downloadText = 'Download!'
|
||||||
|
const copiedText = 'Copied!'
|
||||||
|
const confettiText = ref(copiedText)
|
||||||
|
const props = defineProps<{
|
||||||
|
name: string
|
||||||
|
popoverPosition?: 'top' | 'bottom'
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { size } = useIconStyleContext()
|
||||||
|
|
||||||
|
const { animate, confetti } = useConfetti()
|
||||||
|
|
||||||
|
function copySVG() {
|
||||||
|
confettiText.value = copiedText
|
||||||
|
const svgString = getSVGIcon()
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(svgString)
|
||||||
|
|
||||||
|
confetti()
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyDataUrl() {
|
||||||
|
confettiText.value = copiedText
|
||||||
|
const svgString = getSVGIcon()
|
||||||
|
|
||||||
|
// Create SVG data url
|
||||||
|
const dataUrl = `data:image/svg+xml;base64,${btoa(svgString)}`
|
||||||
|
navigator.clipboard.writeText(dataUrl)
|
||||||
|
|
||||||
|
confetti()
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadSVG() {
|
||||||
|
confettiText.value = downloadText
|
||||||
|
const svgString = getSVGIcon()
|
||||||
|
|
||||||
|
downloadData(`${props.name}.svg`, `data:image/svg+xml;base64,${btoa(svgString)}`)
|
||||||
|
confetti()
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadPNG() {
|
||||||
|
confettiText.value = downloadText
|
||||||
|
const svgString = getSVGIcon()
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = size.value;
|
||||||
|
canvas.height = size.value;
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
const image = new Image();
|
||||||
|
image.src = `data:image/svg+xml;base64,${btoa(svgString)}`;
|
||||||
|
image.onload = function() {
|
||||||
|
ctx.drawImage(image, 0, 0);
|
||||||
|
downloadData(`${props.name}.png`, canvas.toDataURL('image/png'))
|
||||||
|
confetti()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ButtonMenu
|
||||||
|
:buttonClass="`confetti-button ${animate ? 'animate' : ''}`"
|
||||||
|
callOptionOnClick
|
||||||
|
id="copy-svg-button"
|
||||||
|
:data-confetti-text="confettiText"
|
||||||
|
:popoverPosition="popoverPosition"
|
||||||
|
:options="[
|
||||||
|
{ text: 'Copy SVG' , onClick: copySVG },
|
||||||
|
{ text: 'Copy Data URL' , onClick: copyDataUrl },
|
||||||
|
{ text: 'Download SVG' , onClick: downloadSVG },
|
||||||
|
{ text: 'Download PNG' , onClick: downloadPNG },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style src="./confetti.css" />
|
||||||
88
docs/.vitepress/theme/components/icons/IconContributors.vue
Normal file
88
docs/.vitepress/theme/components/icons/IconContributors.vue
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {IconEntity} from "../../types";
|
||||||
|
import Label from "../base/Label.vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
icon: IconEntity
|
||||||
|
}>()
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="contributors" v-if="props.icon.contributors?.length>0">
|
||||||
|
<Label>Contributors:</Label>
|
||||||
|
<div class="avatar-group">
|
||||||
|
<a class="avatar"
|
||||||
|
v-for="contributor in props.icon.contributors"
|
||||||
|
:key="contributor"
|
||||||
|
:href="`https://github.com/${contributor}`"
|
||||||
|
target="_blank"
|
||||||
|
:data-name="contributor"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
>
|
||||||
|
<img class="avatar-image" :alt="contributor" :src="`https://github.com/${contributor}.png?size=128`" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.contributors {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
/* justify-content: flex-end; */
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.avatar-group {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.avatar:not(:first-child) {
|
||||||
|
margin-left: -24px;
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.avatar:before {
|
||||||
|
content: attr(data-name);
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
transform: translateX(-50%) scale(0.9);
|
||||||
|
font-weight: 400;
|
||||||
|
position: absolute;
|
||||||
|
top: -28px;
|
||||||
|
left: 50%;
|
||||||
|
background: var(--vp-c-brand-dark);
|
||||||
|
color: white;
|
||||||
|
z-index: 10;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: var(--vp-shadow-1);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: cubic-bezier(0.19, 1, 0.22, 1) .2s;
|
||||||
|
transition-property: opacity, transform;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
.avatar:hover:before {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) scale(1);
|
||||||
|
}
|
||||||
|
.avatar-image {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid var(--vp-c-bg-elv);
|
||||||
|
background-color: var(--vp-c-neutral);
|
||||||
|
}
|
||||||
|
.avatar:hover .avatar-image {
|
||||||
|
border: 2px solid var(--vp-c-bg-soft-mute);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar:hover {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
56
docs/.vitepress/theme/components/icons/IconDetail.vue
Normal file
56
docs/.vitepress/theme/components/icons/IconDetail.vue
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted, computed } from 'vue'
|
||||||
|
import { useData } from 'vitepress'
|
||||||
|
import IconPreview from './IconPreview.vue'
|
||||||
|
|
||||||
|
const { params } = useData()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
console.log(params, 'data')
|
||||||
|
})
|
||||||
|
const tags = computed(() => {
|
||||||
|
if (!params.tags) return []
|
||||||
|
return params.tags.join(' • ')
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- <PageContainer class="overview"> -->
|
||||||
|
<IconPreview
|
||||||
|
:name="$params.name"
|
||||||
|
:iconNode="$params.iconNode"
|
||||||
|
class="preview"
|
||||||
|
customizable
|
||||||
|
/>
|
||||||
|
<div class="details">
|
||||||
|
<h1 class="title">
|
||||||
|
{{ $params.name }}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<!-- </PageContainer> -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.overview {
|
||||||
|
display: flex;
|
||||||
|
gap: 32px;
|
||||||
|
}
|
||||||
|
.preview {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.details {
|
||||||
|
flex: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 32px;
|
||||||
|
margin: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: var(--vp-text-color);
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
74
docs/.vitepress/theme/components/icons/IconDetailName.vue
Normal file
74
docs/.vitepress/theme/components/icons/IconDetailName.vue
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, useSlots } from 'vue';
|
||||||
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
||||||
|
import { copy } from '../../../data/iconNodes'
|
||||||
|
import useConfetti from '../../composables/useConfetti';
|
||||||
|
const { animate, confetti } = useConfetti()
|
||||||
|
const slots = useSlots()
|
||||||
|
|
||||||
|
const copiedText = computed(() => slots.default?.()[0].children)
|
||||||
|
|
||||||
|
function copyText() {
|
||||||
|
navigator.clipboard.writeText(copiedText.value)
|
||||||
|
|
||||||
|
confetti()
|
||||||
|
}
|
||||||
|
|
||||||
|
const Copy = createLucideIcon('ChevronUp', copy)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1
|
||||||
|
class="icon-name confetti-button"
|
||||||
|
:class="{animate}"
|
||||||
|
data-confetti-text="Copied!"
|
||||||
|
@click="copyText"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
<Copy :size="20" class="copy-icon"/>
|
||||||
|
</h1>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@import './confetti.css';
|
||||||
|
.icon-name {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 32px;
|
||||||
|
transition: background ease-in .15s;;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: auto;
|
||||||
|
display: inline-flex;
|
||||||
|
margin-left: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-name:hover {
|
||||||
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-name:hover .copy-icon {
|
||||||
|
opacity: .9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-name:before,
|
||||||
|
.icon-name:after {
|
||||||
|
left: unset !important;
|
||||||
|
right: -20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-name:before {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-icon {
|
||||||
|
opacity: 0;
|
||||||
|
margin-left: 12px;
|
||||||
|
margin-top: 6px;
|
||||||
|
transition:ease .3s opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-name:hover .copy-icon:hover {
|
||||||
|
opacity: .6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
161
docs/.vitepress/theme/components/icons/IconDetailOverlay.vue
Normal file
161
docs/.vitepress/theme/components/icons/IconDetailOverlay.vue
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { IconEntity } from '../../types'
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||||
|
import IconButton from '../base/IconButton.vue';
|
||||||
|
import IconContributors from './IconContributors.vue';
|
||||||
|
import IconPreview from './IconPreview.vue';
|
||||||
|
import { x, expand } from '../../../data/iconNodes'
|
||||||
|
import { useRouter } from 'vitepress';
|
||||||
|
import IconInfo from './IconInfo.vue';
|
||||||
|
import Badge from '../base/Badge.vue';
|
||||||
|
import { computedAsync } from '@vueuse/core';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
iconName: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const icon = computedAsync<IconEntity | null>(async () => {
|
||||||
|
if (props.iconName) {
|
||||||
|
return (await import(`../../../data/iconDetails/${props.iconName}.ts`)).default as IconEntity
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}, null)
|
||||||
|
|
||||||
|
const emit = defineEmits(['close'])
|
||||||
|
const isOpen = computed(() => !!icon.value)
|
||||||
|
|
||||||
|
function onClose() {
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
const { go } = useRouter()
|
||||||
|
|
||||||
|
const CloseIcon = createLucideIcon('Close', x)
|
||||||
|
const Expand = createLucideIcon('Expand', expand)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Transition name="drawer" appear>
|
||||||
|
<div class="overlay-container" v-if="icon">
|
||||||
|
<div class="overlay-panel">
|
||||||
|
<nav class="overlay-menu">
|
||||||
|
<Badge
|
||||||
|
v-if="icon.createdRelease"
|
||||||
|
class="version"
|
||||||
|
:href="`https://github.com/lucide-icons/lucide/releases/tag/v${icon.createdRelease.version}`"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
>v{{ icon.createdRelease.version }}</Badge>
|
||||||
|
<IconButton @click="go(`/icons/${icon.name}`)">
|
||||||
|
<component :is="Expand" />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton @click="onClose">
|
||||||
|
<component :is="CloseIcon" />
|
||||||
|
</IconButton>
|
||||||
|
</nav>
|
||||||
|
<IconPreview
|
||||||
|
id="previewer"
|
||||||
|
:name="icon.name"
|
||||||
|
:iconNode="icon.iconNode"
|
||||||
|
customizable
|
||||||
|
/>
|
||||||
|
<IconInfo :icon="icon" popoverPosition="top">
|
||||||
|
<template v-slot:footer>
|
||||||
|
<IconContributors :icon="icon" class="contributors" />
|
||||||
|
</template>
|
||||||
|
</IconInfo>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.overlay-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: var(--left, 0);
|
||||||
|
right: var(--right, 0);
|
||||||
|
bottom: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.overlay-container {
|
||||||
|
--left: var(--vp-sidebar-width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1440px) {
|
||||||
|
.overlay-container {
|
||||||
|
--left: calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px);
|
||||||
|
--right: calc(((100% - (var(--vp-layout-max-width) - var(--vp-sidebar-width))) - 272px) / 2);
|
||||||
|
}
|
||||||
|
.overlay-panel {
|
||||||
|
border-top-right-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-panel {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: var(--vp-c-bg-elv);
|
||||||
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||||
|
will-change: transform;
|
||||||
|
pointer-events: all;
|
||||||
|
height: 288px;
|
||||||
|
padding: 24px;
|
||||||
|
display: flex;
|
||||||
|
box-shadow: var(--vp-shadow-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-info {
|
||||||
|
padding-left: 24px;
|
||||||
|
flex-basis: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-tags {
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: 24px;
|
||||||
|
right: 24px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-enter-active {
|
||||||
|
transition: all 0.2s cubic-bezier(.21,.8,.46,.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-leave-active {
|
||||||
|
transition: all 0.4s cubic-bezier(1, 0.5, 0.8, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-enter-from,
|
||||||
|
.drawer-leave-to {
|
||||||
|
transform: translateY(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version {
|
||||||
|
margin-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contributors {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
48
docs/.vitepress/theme/components/icons/IconGrid.vue
Normal file
48
docs/.vitepress/theme/components/icons/IconGrid.vue
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { IconEntity } from '../../types'
|
||||||
|
import IconItem from './IconItem.vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['setActiveIcon'])
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
icons: IconEntity[]
|
||||||
|
activeIcon?: string
|
||||||
|
overlayMode?: boolean
|
||||||
|
hideIcons?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
function setActiveIcon(name: string) {
|
||||||
|
emit('setActiveIcon', name)
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="icons">
|
||||||
|
<div class="icon" v-for="icon in icons" :key="icon.name">
|
||||||
|
<IconItem
|
||||||
|
v-bind="icon"
|
||||||
|
@setActiveIcon="setActiveIcon(icon.name)"
|
||||||
|
:active="activeIcon === icon.name"
|
||||||
|
customizable
|
||||||
|
:overlayMode="overlayMode"
|
||||||
|
:hideIcon="hideIcons"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.icons {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
|
||||||
|
/* padding: 32px 32px 96px; */
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
137
docs/.vitepress/theme/components/icons/IconInfo.vue
Normal file
137
docs/.vitepress/theme/components/icons/IconInfo.vue
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { IconEntity } from '../../types';
|
||||||
|
import IconDetailName from './IconDetailName.vue';
|
||||||
|
import Badge from '../base/Badge.vue';
|
||||||
|
import CopySVGButton from './CopySVGButton.vue';
|
||||||
|
import CopyCodeButton from './CopyCodeButton.vue';
|
||||||
|
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||||
|
import {useData, useRouter} from 'vitepress';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
icon: IconEntity
|
||||||
|
popoverPosition?: 'top' | 'bottom'
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { go } = useRouter()
|
||||||
|
const { page } = useData()
|
||||||
|
|
||||||
|
const tags = computed(() => {
|
||||||
|
if (!props.icon || !props?.icon?.tags) return []
|
||||||
|
return props.icon.tags.join(' • ')
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="icon-info">
|
||||||
|
<IconDetailName class="icon-name">
|
||||||
|
{{ icon.name }}
|
||||||
|
</IconDetailName>
|
||||||
|
<div class="tags-scroller" v-if="tags.length">
|
||||||
|
<p class="icon-tags horizontal-scroller">
|
||||||
|
{{ tags }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="group">
|
||||||
|
<Badge
|
||||||
|
v-for="category in icon.categories"
|
||||||
|
class="category"
|
||||||
|
:href="`/icons/categories#${category}`"
|
||||||
|
>
|
||||||
|
{{ category }}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group buttons">
|
||||||
|
<VPButton
|
||||||
|
v-if="!page?.relativePath?.startsWith?.(`icons/${icon.name}`)"
|
||||||
|
:href="`/icons/${icon.name}`"
|
||||||
|
text="See in action"
|
||||||
|
@click="go(`/icons/${icon.name}`)"
|
||||||
|
/>
|
||||||
|
<CopySVGButton :name="icon.name" :popoverPosition="popoverPosition"/>
|
||||||
|
<CopyCodeButton :name="icon.name" :popoverPosition="popoverPosition"/>
|
||||||
|
</div>
|
||||||
|
<slot name="footer" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.category {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
.icon-name {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-tags {
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 28px;
|
||||||
|
white-space: nowrap;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-scroller {
|
||||||
|
position: relative;
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 28px;
|
||||||
|
padding: 8px 0 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
margin-top: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
--gradient-background: var(--tags-gradient-background, var(--vp-c-bg-elv))
|
||||||
|
}
|
||||||
|
.horizontal-scroller {
|
||||||
|
overflow-x: scroll;
|
||||||
|
/* Hide Scrollbar */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
scrollbar-width: thin; /* can also be normal, or none, to not render scrollbar */
|
||||||
|
scrollbar-color: currentColor transparent; /* foreground background */
|
||||||
|
}
|
||||||
|
.horizontal-scroller::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal-scroller::-webkit-scrollbar-track {
|
||||||
|
background: transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal-scroller::-webkit-scrollbar-thumb {
|
||||||
|
background: transparent;
|
||||||
|
border: none
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.tags-scroller::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 32px;
|
||||||
|
height: 100%;
|
||||||
|
/* Background Gradient left to right */
|
||||||
|
background: linear-gradient(to right, rgba(255,255,255,0) 0%,var(--gradient-background) 100%);
|
||||||
|
right: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
162
docs/.vitepress/theme/components/icons/IconItem.vue
Normal file
162
docs/.vitepress/theme/components/icons/IconItem.vue
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||||
|
import { useMediaQuery } from '@vueuse/core';
|
||||||
|
import { useRouter } from 'vitepress';
|
||||||
|
import getSVGIcon from '../../utils/getSVGIcon';
|
||||||
|
import useConfetti from '../../composables/useConfetti';
|
||||||
|
import Tooltip from '../base/Tooltip.vue';
|
||||||
|
|
||||||
|
const downloadText = 'Download!'
|
||||||
|
const copiedText = 'Copied!'
|
||||||
|
|
||||||
|
export type IconNode = [elementName: string, attrs: Record<string, string>][]
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
name: string;
|
||||||
|
iconNode: IconNode;
|
||||||
|
active: boolean;
|
||||||
|
customizable?: boolean;
|
||||||
|
overlayMode?: boolean
|
||||||
|
hideIcon?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits(['setActiveIcon'])
|
||||||
|
|
||||||
|
const { go } = useRouter()
|
||||||
|
const showOverlay = useMediaQuery('(min-width: 860px)');
|
||||||
|
const { animate, confetti, confettiText } = useConfetti()
|
||||||
|
|
||||||
|
|
||||||
|
const icon = createLucideIcon(props.name, props.iconNode)
|
||||||
|
|
||||||
|
async function navigateToIcon(event) {
|
||||||
|
|
||||||
|
if (event.shiftKey) {
|
||||||
|
event.preventDefault()
|
||||||
|
const svgString = getSVGIcon(event.target.firstChild, {
|
||||||
|
class: `lucide lucide-${props.name}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
await navigator.clipboard.writeText(svgString)
|
||||||
|
|
||||||
|
confettiText.value = copiedText
|
||||||
|
confetti()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if(props.overlayMode && showOverlay.value) {
|
||||||
|
event.preventDefault()
|
||||||
|
window.history.pushState({}, '', `/icons/${props.name}`)
|
||||||
|
emit('setActiveIcon', props.name)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
event.preventDefault()
|
||||||
|
go(`/icons/${props.name}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Tooltip :title="name">
|
||||||
|
<a
|
||||||
|
class="icon-button confetti-button vp-raw"
|
||||||
|
@click="navigateToIcon"
|
||||||
|
:class="{ active, animate }"
|
||||||
|
:aria-label="name"
|
||||||
|
:href="`/icons/${props.name}`"
|
||||||
|
:data-confetti-text="confettiText"
|
||||||
|
ref="ref"
|
||||||
|
>
|
||||||
|
<KeepAlive>
|
||||||
|
<component
|
||||||
|
v-if="!hideIcon"
|
||||||
|
:is="icon"
|
||||||
|
class="lucide-icon"
|
||||||
|
:class="{
|
||||||
|
customizable,
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</KeepAlive>
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style src="./confetti.css" />
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.icon-button {
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
display: inline-flex;
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
font-size: 24px;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.confetti-button:before,
|
||||||
|
.confetti-button:after {
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confetti-button:before {
|
||||||
|
line-height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button:active {
|
||||||
|
transition: color 0.1s, border-color 0.1s, background-color 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button.medium {
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 0 20px;
|
||||||
|
line-height: 38px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button.big {
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 0 24px;
|
||||||
|
line-height: 46px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button:hover {
|
||||||
|
border-color: var(--vp-button-alt-hover-border);
|
||||||
|
color: var(--vp-button-alt-hover-text);
|
||||||
|
background-color: var(--vp-button-alt-hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button:active {
|
||||||
|
border-color: var(--vp-button-alt-active-border);
|
||||||
|
color: var(--vp-button-alt-active-text);
|
||||||
|
background-color: var(--vp-button-alt-active-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button.active {
|
||||||
|
border-color: var(--vp-c-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lucide-icon {
|
||||||
|
margin: auto;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.lucide-icon.customizable {
|
||||||
|
will-change: width, height, stroke-width, stroke;
|
||||||
|
color: var(--customize-color, currentColor);
|
||||||
|
stroke-width: var(--customize-strokeWidth, 2);
|
||||||
|
width: calc(var(--customize-size, 24) * 1px);
|
||||||
|
height: calc(var(--customize-size, 24) * 1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.absolute-stroke-width .lucide-icon.customizable {
|
||||||
|
stroke-width: calc(var(--customize-strokeWidth, 2) * 24 / var(--customize-size, 24));
|
||||||
|
}
|
||||||
|
</style>
|
||||||
73
docs/.vitepress/theme/components/icons/IconPreview.vue
Normal file
73
docs/.vitepress/theme/components/icons/IconPreview.vue
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { IconEntity } from '../../types'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||||
|
import { useIconStyleContext } from '../../composables/useIconStyle';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
name: IconEntity['name']
|
||||||
|
iconNode: IconEntity['iconNode']
|
||||||
|
customizable?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { size, color, strokeWidth, absoluteStrokeWidth } = useIconStyleContext()
|
||||||
|
const previewIcon = ref()
|
||||||
|
|
||||||
|
const gridLines = computed(() => Array.from({ length:(size.value - 1) }))
|
||||||
|
|
||||||
|
const iconComponent = computed(() => {
|
||||||
|
if (!props.name || !props.iconNode) return null
|
||||||
|
return createLucideIcon(props.name, props.iconNode)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="icon-container">
|
||||||
|
<component
|
||||||
|
ref="previewIcon"
|
||||||
|
:is="iconComponent"
|
||||||
|
:width="size"
|
||||||
|
:height="size"
|
||||||
|
:stroke="color"
|
||||||
|
:stroke-width="absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth"
|
||||||
|
/>
|
||||||
|
<svg class="icon-grid" :viewBox="`0 0 ${size} ${size}`" fill="none" stroke-width="0.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g :key="`grid-${i}`" v-for="(_, i) in gridLines">
|
||||||
|
<line :key="`horizontal-${i}`" :x1="0" :y1="i + 1" :x2="size" :y2="i + 1" />
|
||||||
|
<line :key="`vertical-${i}`" :x1="i + 1" y1="0" :x2="i + 1" :y2="size" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.icon-grid {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
stroke: var(--vp-c-divider);
|
||||||
|
}
|
||||||
|
.icon-container {
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
position: relative;
|
||||||
|
background: var(--vp-c-bg-alt);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.icon-container > :deep(svg:not(.icon-grid)) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
color: var(--vp-c-neutral);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-component.customizable {
|
||||||
|
will-change: width, height, stroke-width, stroke;
|
||||||
|
/* color: var(--customize-color, currentColor);
|
||||||
|
stroke-width: var(--customize-strokeWidth, 2); */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
69
docs/.vitepress/theme/components/icons/IconPreviewSmall.vue
Normal file
69
docs/.vitepress/theme/components/icons/IconPreviewSmall.vue
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { IconEntity } from '../../types'
|
||||||
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
name: IconEntity['name']
|
||||||
|
iconNode: IconEntity['iconNode']
|
||||||
|
customizable?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const Icon = createLucideIcon(props.name, props.iconNode)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="icons-small-preview">
|
||||||
|
<div class="icon-wrapper">
|
||||||
|
<Icon :size="48" class="lucide-icon"/>
|
||||||
|
</div>
|
||||||
|
<div class="icon-wrapper">
|
||||||
|
<Icon :size="32" class="lucide-icon"/>
|
||||||
|
</div>
|
||||||
|
<div class="icon-wrapper">
|
||||||
|
<Icon class="lucide-icon"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.icons-small-preview {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
/* align-items: center; */
|
||||||
|
justify-content: center;
|
||||||
|
gap: 24px;
|
||||||
|
/* margin-top: 24px; */
|
||||||
|
}
|
||||||
|
.icon-wrapper {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
display: inline-flex;
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
font-size: 24px;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lucide-icon {
|
||||||
|
will-change: width, height, stroke-width, stroke;
|
||||||
|
color: var(--customize-color, currentColor);
|
||||||
|
stroke-width: var(--customize-strokeWidth, 2);
|
||||||
|
/* Not sure if this is logical for 100% previews */
|
||||||
|
/* width: calc(var(--customize-size, 24) * 1px);
|
||||||
|
height: calc(var(--customize-size, 24) * 1px); */
|
||||||
|
}
|
||||||
|
|
||||||
|
html.absolute-stroke-width .lucide-icon {
|
||||||
|
stroke-width: calc(var(--customize-strokeWidth, 2) * 24 / var(--customize-size, 24));
|
||||||
|
}
|
||||||
|
</style>
|
||||||
56
docs/.vitepress/theme/components/icons/IconsCategory.vue
Normal file
56
docs/.vitepress/theme/components/icons/IconsCategory.vue
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Category } from '../../types';
|
||||||
|
import IconGrid from './IconGrid.vue'
|
||||||
|
import { vIntersectionObserver } from '@vueuse/components'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
activeIconName: string
|
||||||
|
category: Category
|
||||||
|
}>()
|
||||||
|
|
||||||
|
|
||||||
|
const emit = defineEmits(['setActiveIcon'])
|
||||||
|
|
||||||
|
const showIcons = ref(false)
|
||||||
|
|
||||||
|
// Added intersection observer to improve performance
|
||||||
|
const onIntersectionObserver: IntersectionObserverCallback = ([{ isIntersecting }]) => {
|
||||||
|
showIcons.value = isIntersecting
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section
|
||||||
|
class="category"
|
||||||
|
:key="category.name"
|
||||||
|
:id="category.name"
|
||||||
|
v-intersection-observer="onIntersectionObserver"
|
||||||
|
>
|
||||||
|
<h2 class="title" >
|
||||||
|
<a class="header-anchor" :href="`#${category.name}`" :aria-label="`Permalink to "${category.title}"`">​</a>
|
||||||
|
{{ category.title }}
|
||||||
|
</h2>
|
||||||
|
<IconGrid
|
||||||
|
:activeIcon="activeIconName"
|
||||||
|
:icons="category.icons"
|
||||||
|
@setActiveIcon="$event => $emit('setActiveIcon', $event)"
|
||||||
|
overlayMode
|
||||||
|
:hideIcons="!showIcons"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.title {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: 500;
|
||||||
|
padding-top: 86px;
|
||||||
|
/* scroll-padding-top: 240px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.category {
|
||||||
|
margin-bottom: calc(-86px + 32px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
130
docs/.vitepress/theme/components/icons/IconsCategoryOverview.vue
Normal file
130
docs/.vitepress/theme/components/icons/IconsCategoryOverview.vue
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, defineAsyncComponent } from 'vue'
|
||||||
|
import type { IconEntity, Category } from '../../types'
|
||||||
|
import useSearch from '../../composables/useSearch'
|
||||||
|
import InputSearch from '../base/InputSearch.vue'
|
||||||
|
import useSearchInput from '../../composables/useSearchInput'
|
||||||
|
import StickyBar from './StickyBar.vue'
|
||||||
|
import IconsCategory from './IconsCategory.vue'
|
||||||
|
import { useFetch } from '@vueuse/core'
|
||||||
|
import useFetchTags from '../../composables/useFetchTags'
|
||||||
|
import useFetchCategories from '../../composables/useFetchCategories'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
icons: IconEntity[]
|
||||||
|
categories: Category[]
|
||||||
|
iconCategories: Record<string, string[]>
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const activeIconName = ref(null)
|
||||||
|
const { searchInput, searchQuery, searchQueryThrottled } = useSearchInput()
|
||||||
|
|
||||||
|
const isSearching = computed(() => !!searchQuery.value)
|
||||||
|
|
||||||
|
function setActiveIconName(name: string) {
|
||||||
|
activeIconName.value = name
|
||||||
|
}
|
||||||
|
|
||||||
|
const { execute: fetchTags, data: tags } = useFetchTags()
|
||||||
|
const { execute: fetchCategories, data: categoriesMap } = useFetchCategories()
|
||||||
|
|
||||||
|
const mappedIcons = computed(() => {
|
||||||
|
if(tags.value == null) {
|
||||||
|
return props.icons
|
||||||
|
}
|
||||||
|
return props.icons.map((icon) => {
|
||||||
|
const iconTags = tags.value[icon.name]
|
||||||
|
const iconCategories = categoriesMap.value?.[icon.name] ?? []
|
||||||
|
|
||||||
|
return {
|
||||||
|
...icon,
|
||||||
|
tags: iconTags,
|
||||||
|
categories: iconCategories,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const searchResults = useSearch(searchQuery, mappedIcons, [
|
||||||
|
{ name: 'name', weight: 2 },
|
||||||
|
{ name: 'tags', weight: 1 },
|
||||||
|
])
|
||||||
|
|
||||||
|
const categories = computed(() => {
|
||||||
|
if( !props.categories?.length || !props.icons?.length ) return []
|
||||||
|
|
||||||
|
return props.categories.map(({ name, title }) => {
|
||||||
|
const categoryIcons = props.icons.filter((icon) => {
|
||||||
|
const iconCategories = props.iconCategories[icon.name]
|
||||||
|
|
||||||
|
return iconCategories?.includes(name)
|
||||||
|
})
|
||||||
|
|
||||||
|
const searchedCategoryIcons = isSearching
|
||||||
|
? categoryIcons.filter(icon => searchResults.value.some((item) => item?.name === icon?.name))
|
||||||
|
: categoryIcons;
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
name,
|
||||||
|
icons: searchedCategoryIcons,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(({ icons }) => icons.length)
|
||||||
|
})
|
||||||
|
|
||||||
|
function onFocusSearchInput() {
|
||||||
|
if (tags.value == null) {
|
||||||
|
fetchTags()
|
||||||
|
}
|
||||||
|
if (categoriesMap.value == null) {
|
||||||
|
fetchCategories()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const NoResults = defineAsyncComponent(() =>
|
||||||
|
import('./NoResults.vue')
|
||||||
|
)
|
||||||
|
|
||||||
|
const IconDetailOverlay = defineAsyncComponent(() =>
|
||||||
|
import('./IconDetailOverlay.vue')
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<StickyBar class="search-bar category-search">
|
||||||
|
<InputSearch
|
||||||
|
:placeholder="`Search ${icons.length} icons ...`"
|
||||||
|
v-model="searchQuery"
|
||||||
|
class="input-wrapper"
|
||||||
|
ref="searchInput"
|
||||||
|
@focus="onFocusSearchInput"
|
||||||
|
/>
|
||||||
|
</StickyBar>
|
||||||
|
<NoResults
|
||||||
|
v-if="categories.length === 0"
|
||||||
|
:searchQuery="searchQuery"
|
||||||
|
@clear="searchQuery = ''"
|
||||||
|
/>
|
||||||
|
<IconsCategory
|
||||||
|
v-for="category in categories"
|
||||||
|
:key="category.name"
|
||||||
|
:category="category"
|
||||||
|
:activeIconName="activeIconName"
|
||||||
|
@setActiveIcon="setActiveIconName"
|
||||||
|
/>
|
||||||
|
<IconDetailOverlay
|
||||||
|
v-if="activeIconName != null"
|
||||||
|
:iconName="activeIconName"
|
||||||
|
@close="setActiveIconName('')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.input-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar.category-search {
|
||||||
|
margin-bottom: -54px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
154
docs/.vitepress/theme/components/icons/IconsOverview.vue
Normal file
154
docs/.vitepress/theme/components/icons/IconsOverview.vue
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch, defineAsyncComponent } from 'vue'
|
||||||
|
import type { IconEntity } from '../../types'
|
||||||
|
import { useMediaQuery, useOffsetPagination } from '@vueuse/core'
|
||||||
|
import IconGrid from './IconGrid.vue'
|
||||||
|
import InputSearch from '../base/InputSearch.vue'
|
||||||
|
import useSearch from '../../composables/useSearch'
|
||||||
|
import EndOfPage from '../base/EndOfPage.vue'
|
||||||
|
import useSearchInput from '../../composables/useSearchInput'
|
||||||
|
import StickyBar from './StickyBar.vue'
|
||||||
|
import useFetchTags from '../../composables/useFetchTags'
|
||||||
|
import useFetchCategories from '../../composables/useFetchCategories'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
icons: IconEntity[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
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: fetchCategories, data: categories } = useFetchCategories()
|
||||||
|
|
||||||
|
const mappedIcons = computed(() => {
|
||||||
|
if(tags.value == null) {
|
||||||
|
return props.icons
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.icons.map((icon) => {
|
||||||
|
const iconTags = tags.value[icon.name]
|
||||||
|
const iconCategories = categories.value?.[icon.name] ?? []
|
||||||
|
|
||||||
|
return {
|
||||||
|
...icon,
|
||||||
|
tags: iconTags,
|
||||||
|
categories: iconCategories,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const { searchInput, searchQuery, searchQueryThrottled } = useSearchInput()
|
||||||
|
const searchResults = useSearch(searchQueryThrottled, mappedIcons, [
|
||||||
|
{ name: 'name', weight: 3 },
|
||||||
|
{ name: 'tags', weight: 2 },
|
||||||
|
{ name: 'categories', weight: 1 },
|
||||||
|
])
|
||||||
|
|
||||||
|
const { next, currentPage } = useOffsetPagination( { pageSize })
|
||||||
|
|
||||||
|
|
||||||
|
const paginatedIcons = computed(() => {
|
||||||
|
const end = pageSize.value * currentPage.value
|
||||||
|
|
||||||
|
return searchResults.value.slice(0, end)
|
||||||
|
})
|
||||||
|
|
||||||
|
function setActiveIconName(name: string) {
|
||||||
|
activeIconName.value = name
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(searchQueryThrottled, (searchString) => {
|
||||||
|
currentPage.value = 1
|
||||||
|
})
|
||||||
|
|
||||||
|
function onFocusSearchInput() {
|
||||||
|
if (tags.value == null) {
|
||||||
|
fetchTags()
|
||||||
|
}
|
||||||
|
if (categories.value == null) {
|
||||||
|
fetchCategories()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const NoResults = defineAsyncComponent(() =>
|
||||||
|
import('./NoResults.vue')
|
||||||
|
)
|
||||||
|
|
||||||
|
const IconDetailOverlay = defineAsyncComponent(() =>
|
||||||
|
import('./IconDetailOverlay.vue')
|
||||||
|
)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<StickyBar>
|
||||||
|
<InputSearch
|
||||||
|
:placeholder="`Search ${icons.length} icons ...`"
|
||||||
|
v-model="searchQuery"
|
||||||
|
ref="searchInput"
|
||||||
|
class="input-wrapper"
|
||||||
|
@focus="onFocusSearchInput"
|
||||||
|
/>
|
||||||
|
</StickyBar>
|
||||||
|
<NoResults
|
||||||
|
v-if="paginatedIcons.length === 0"
|
||||||
|
:searchQuery="searchQuery"
|
||||||
|
@clear="searchQuery = ''"
|
||||||
|
/>
|
||||||
|
<IconGrid
|
||||||
|
overlayMode
|
||||||
|
:activeIcon="activeIconName"
|
||||||
|
:icons="paginatedIcons"
|
||||||
|
@setActiveIcon="setActiveIconName"
|
||||||
|
/>
|
||||||
|
<EndOfPage @end-of-page="next" class="bottom-page"/>
|
||||||
|
<IconDetailOverlay
|
||||||
|
v-if="activeIconName != null"
|
||||||
|
:iconName="activeIconName"
|
||||||
|
@close="setActiveIconName('')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.icons {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-page {
|
||||||
|
height: 288px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
83
docs/.vitepress/theme/components/icons/NoResults.vue
Normal file
83
docs/.vitepress/theme/components/icons/NoResults.vue
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {ref} from 'vue'
|
||||||
|
import {bird} from '../../../data/iconNodes'
|
||||||
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
||||||
|
import {useEventListener} from '@vueuse/core'
|
||||||
|
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
searchQuery: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
defineEmits(['clear'])
|
||||||
|
|
||||||
|
const birdIcon = ref<HTMLElement>()
|
||||||
|
const Bird = createLucideIcon('bird', bird)
|
||||||
|
const flip = ref(false)
|
||||||
|
|
||||||
|
useEventListener(document, 'mousemove', (mouseEvent) => {
|
||||||
|
const {width, height, x, y} = birdIcon.value.getBoundingClientRect()
|
||||||
|
|
||||||
|
const centerX = (width / 2) + x
|
||||||
|
|
||||||
|
flip.value = mouseEvent.x < centerX
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="no-results">
|
||||||
|
<Bird class="bird-icon" ref="birdIcon" :class="{ flip }" :strokeWidth="1"/>
|
||||||
|
<h2 class="no-results-text">
|
||||||
|
No icons found for '{{ searchQuery }}'
|
||||||
|
</h2>
|
||||||
|
<VPButton text="Clear your search and try again" theme="alt" @click="$emit('clear')"/>
|
||||||
|
<span class="text-divider">or</span>
|
||||||
|
<VPButton text="Search on Github issues"
|
||||||
|
theme="alt"
|
||||||
|
:href="`https://github.com/lucide-icons/lucide/issues?q=is%3Aopen+${searchQuery}`"
|
||||||
|
target="_blank"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.no-results {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bird-icon {
|
||||||
|
width: 160px;
|
||||||
|
height: 160px;
|
||||||
|
color: var(--vp-c-neutral);
|
||||||
|
opacity: 0.8;
|
||||||
|
margin-top: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bird-icon.flip {
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.bird-icon {
|
||||||
|
width: 240px;
|
||||||
|
height: 240px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-results-text {
|
||||||
|
line-height: 40px;
|
||||||
|
font-size: 24px;
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-divider {
|
||||||
|
margin: 12px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--vp-c-neutral);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
30
docs/.vitepress/theme/components/icons/RelatedIcons.vue
Normal file
30
docs/.vitepress/theme/components/icons/RelatedIcons.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { IconEntity } from '../../types'
|
||||||
|
import IconGrid from './IconGrid.vue'
|
||||||
|
defineProps<{
|
||||||
|
icons: IconEntity[]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="related-icons">
|
||||||
|
<h2 class="title">
|
||||||
|
Related Icons
|
||||||
|
</h2>
|
||||||
|
<IconGrid
|
||||||
|
:icons="icons"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.title {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-icons {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
169
docs/.vitepress/theme/components/icons/SidebarIconCustomizer.vue
Normal file
169
docs/.vitepress/theme/components/icons/SidebarIconCustomizer.vue
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { shallowRef, type Ref, watch, computed } from 'vue'
|
||||||
|
import { useCssVar, syncRef } from '@vueuse/core'
|
||||||
|
import { STYLE_DEFAULTS, useIconStyleContext } from '../../composables/useIconStyle'
|
||||||
|
import RangeSlider from '../base/RangeSlider.vue'
|
||||||
|
import InputField from '../base/InputField.vue'
|
||||||
|
import ColorPicker from '../base/ColorPicker.vue'
|
||||||
|
import ResetButton from '../base/ResetButton.vue'
|
||||||
|
import Switch from '../base/Switch.vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
rootEl?: Ref<HTMLElement>
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { color, strokeWidth, size, absoluteStrokeWidth } = useIconStyleContext()
|
||||||
|
const documentRef = shallowRef<HTMLElement | undefined>(typeof document !== 'undefined' ? document?.documentElement : undefined)
|
||||||
|
|
||||||
|
const colorCssVar = useCssVar(
|
||||||
|
'--customize-color',
|
||||||
|
props.rootEl?.value ?? documentRef.value,
|
||||||
|
{
|
||||||
|
initialValue: `${STYLE_DEFAULTS.color}`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const strokeWidthCssVar = useCssVar(
|
||||||
|
'--customize-strokeWidth',
|
||||||
|
props.rootEl?.value ?? documentRef.value,
|
||||||
|
{
|
||||||
|
initialValue: `${STYLE_DEFAULTS.strokeWidth}`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const sizeCssVar = useCssVar(
|
||||||
|
'--customize-size',
|
||||||
|
props.rootEl?.value ?? documentRef.value,
|
||||||
|
{
|
||||||
|
initialValue: `${STYLE_DEFAULTS.size}`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
syncRef(color, colorCssVar, { direction: 'ltr' })
|
||||||
|
syncRef(strokeWidth, strokeWidthCssVar, { direction: 'ltr' })
|
||||||
|
syncRef(size, sizeCssVar, { direction: 'ltr' })
|
||||||
|
|
||||||
|
function resetStyle () {
|
||||||
|
color.value = STYLE_DEFAULTS.color
|
||||||
|
strokeWidth.value = STYLE_DEFAULTS.strokeWidth
|
||||||
|
size.value = STYLE_DEFAULTS.size
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(absoluteStrokeWidth, (enabled) => {
|
||||||
|
const htmlEl = document.documentElement
|
||||||
|
|
||||||
|
htmlEl.classList.toggle('absolute-stroke-width', enabled)
|
||||||
|
})
|
||||||
|
|
||||||
|
const customizingActive = computed(() => {
|
||||||
|
return color.value !== STYLE_DEFAULTS.color
|
||||||
|
|| strokeWidth.value !== STYLE_DEFAULTS.strokeWidth
|
||||||
|
|| size.value !== STYLE_DEFAULTS.size
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="customizer-card" :class="{ customized: customizingActive }">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-title">
|
||||||
|
Customizer
|
||||||
|
</h2>
|
||||||
|
<ResetButton @click="resetStyle"></ResetButton>
|
||||||
|
</div>
|
||||||
|
<InputField
|
||||||
|
id="icon-color"
|
||||||
|
label="Color"
|
||||||
|
>
|
||||||
|
<template #display>
|
||||||
|
<ColorPicker v-model="color" id="icon-color" class="color-picker"/>
|
||||||
|
</template>
|
||||||
|
</InputField>
|
||||||
|
|
||||||
|
<InputField
|
||||||
|
id="stroke-width"
|
||||||
|
label="Stroke width"
|
||||||
|
>
|
||||||
|
<template #display>
|
||||||
|
<span class="customize-label">{{ strokeWidth }}px</span>
|
||||||
|
</template>
|
||||||
|
<RangeSlider
|
||||||
|
id="stroke-width"
|
||||||
|
name="stroke-width"
|
||||||
|
v-model="strokeWidth"
|
||||||
|
:min="0.5"
|
||||||
|
:max="3"
|
||||||
|
:step="0.25"
|
||||||
|
/>
|
||||||
|
</InputField>
|
||||||
|
|
||||||
|
<InputField
|
||||||
|
id="size"
|
||||||
|
label="Size"
|
||||||
|
>
|
||||||
|
<template #display>
|
||||||
|
<span class="customize-label">{{ size }}px</span>
|
||||||
|
</template>
|
||||||
|
<RangeSlider
|
||||||
|
id="size"
|
||||||
|
name="size"
|
||||||
|
v-model="size"
|
||||||
|
:min="16"
|
||||||
|
:max="48"
|
||||||
|
:step="4"
|
||||||
|
/>
|
||||||
|
</InputField>
|
||||||
|
|
||||||
|
<InputField
|
||||||
|
id="absolute-stroke-width"
|
||||||
|
label="Absolute Stroke width"
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
id="size"
|
||||||
|
name="size"
|
||||||
|
v-model="absoluteStrokeWidth"
|
||||||
|
/>
|
||||||
|
</InputField>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
line-height: 32px;
|
||||||
|
font-size: 16px;
|
||||||
|
/* margin-bottom: 12px; */
|
||||||
|
}
|
||||||
|
.customizer-card {
|
||||||
|
background: var(--vp-c-bg);
|
||||||
|
padding: 12px 24px 24px;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
transition: border-color .4s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customizer-card.customized {
|
||||||
|
border-color: var(--vp-c-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#absolute-stroke-width {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
28
docs/.vitepress/theme/components/icons/StickyBar.vue
Normal file
28
docs/.vitepress/theme/components/icons/StickyBar.vue
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search-bar">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.search-bar {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
position: sticky;
|
||||||
|
z-index: 10;
|
||||||
|
top: 48px;
|
||||||
|
padding-top: 24px;
|
||||||
|
margin-top: -32px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
background: var(--vp-c-bg);
|
||||||
|
box-shadow: 0 16px 24px var(--vp-c-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.search-bar {
|
||||||
|
padding-top: 32px;
|
||||||
|
top: 64px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
104
docs/.vitepress/theme/components/icons/confetti.css
Normal file
104
docs/.vitepress/theme/components/icons/confetti.css
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
.confetti-button {
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
--confetti-color: var(--vp-c-brand);
|
||||||
|
--text-color: 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .confetti-button {
|
||||||
|
--confetti-color: var(--vp-c-brand-dark);
|
||||||
|
--text-color: 255 255 255;
|
||||||
|
}
|
||||||
|
.confetti-button:before,
|
||||||
|
.confetti-button:after {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
width: 140%;
|
||||||
|
max-width: 160px;
|
||||||
|
height: 100%;
|
||||||
|
left: -20%;
|
||||||
|
z-index: -1000;
|
||||||
|
transition: all ease-in-out 0.5s;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confetti-button:before {
|
||||||
|
content: attr(data-confetti-text);
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-weight: bold;
|
||||||
|
transform: rotate(-8deg);
|
||||||
|
color: rgb(var(--text-color) / 1);
|
||||||
|
display: none;
|
||||||
|
top: -85%;
|
||||||
|
background-image: radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
||||||
|
radial-gradient(circle, transparent 20%, var(--confetti-color) 20%, transparent 30%),
|
||||||
|
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
||||||
|
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
||||||
|
radial-gradient(circle, transparent 10%, var(--confetti-color) 15%, 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: 10% 10%, 20% 20%, 15% 15%, 20% 20%, 18% 18%, 10% 10%, 15% 15%,
|
||||||
|
10% 10%, 18% 18%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confetti-button:after {
|
||||||
|
display: none;
|
||||||
|
bottom: -75%;
|
||||||
|
background-image: radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
||||||
|
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
||||||
|
radial-gradient(circle, transparent 10%, var(--confetti-color) 15%, 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: 15% 15%, 20% 20%, 18% 18%, 20% 20%, 15% 15%, 10% 10%, 20% 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confetti-button.animate:before {
|
||||||
|
display: block;
|
||||||
|
animation: topBubbles ease-in-out 1s forwards;
|
||||||
|
}
|
||||||
|
.confetti-button.animate:after {
|
||||||
|
display: block;
|
||||||
|
animation: bottomBubbles ease-in-out 1s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes topBubbles {
|
||||||
|
0% {
|
||||||
|
color: rgb(var(--text-color) / 0);
|
||||||
|
background-position: 5% 90%, 10% 90%, 10% 90%, 15% 90%, 25% 90%, 25% 90%,
|
||||||
|
40% 90%, 55% 90%, 70% 90%;
|
||||||
|
}
|
||||||
|
30% {
|
||||||
|
color: rgb(var(--text-color) / 1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 0% 80%, 0% 20%, 10% 40%, 20% 0%, 30% 30%, 22% 50%,
|
||||||
|
50% 50%, 65% 20%, 90% 30%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0% 70%, 0% 10%, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes bottomBubbles {
|
||||||
|
0% {
|
||||||
|
background-position: 10% -10%, 30% 10%, 55% -10%, 70% -10%, 85% -10%,
|
||||||
|
70% -10%, 70% 0%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 0% 80%, 20% 80%, 45% 60%, 60% 100%, 75% 70%, 95% 60%,
|
||||||
|
105% 0%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0% 90%, 20% 90%, 45% 70%, 60% 110%, 75% 80%, 95% 70%,
|
||||||
|
110% 10%;
|
||||||
|
background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%;
|
||||||
|
}
|
||||||
|
}
|
||||||
108
docs/.vitepress/theme/components/overrides/VPFooter.vue
Normal file
108
docs/.vitepress/theme/components/overrides/VPFooter.vue
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useData } from 'vitepress'
|
||||||
|
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar'
|
||||||
|
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const { theme } = useData()
|
||||||
|
const { hasSidebar } = useSidebar()
|
||||||
|
|
||||||
|
const githubLink = computed(() => theme.value.socialLinks.find(({icon}) => icon === 'github').link)
|
||||||
|
|
||||||
|
const links = computed(() => [
|
||||||
|
{
|
||||||
|
text: 'License',
|
||||||
|
href: '/license'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Contribute',
|
||||||
|
href: '/contributing'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Changelog',
|
||||||
|
href: `${githubLink.value}/releases`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Github',
|
||||||
|
href: `${githubLink.value}/issues`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Issues',
|
||||||
|
href: `${githubLink.value}/issues`
|
||||||
|
}
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<footer v-if="theme.footer" class="VPFooter" :class="{ 'has-sidebar': hasSidebar }">
|
||||||
|
<div class="container">
|
||||||
|
<p v-if="theme.footer.message" class="message" v-html="theme.footer.message"></p>
|
||||||
|
<p v-if="theme.footer.copyright" class="copyright" v-html="theme.footer.copyright"></p>
|
||||||
|
<div class="links">
|
||||||
|
<VPLink v-for="link in links" :href="link.href" :key="link.text" :rel="link.href.startsWith('http') ? 'noreferrer noopener': undefined">
|
||||||
|
{{ link.text }}
|
||||||
|
</VPLink>
|
||||||
|
<a href="https://vercel.com?utm_source=lucide&utm_campaign=oss" rel="noreferrer noopener">
|
||||||
|
<img src="/vercel.svg" alt="Powered by Vercel" width="200" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPFooter {
|
||||||
|
position: relative;
|
||||||
|
z-index: var(--vp-z-index-footer);
|
||||||
|
border-top: 1px solid var(--vp-c-gutter);
|
||||||
|
padding: 32px 24px;
|
||||||
|
background-color: var(--vp-c-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPFooter.has-sidebar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.container {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: var(--vp-layout-max-width);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message,
|
||||||
|
.copyright {
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message { order: 2; }
|
||||||
|
.copyright { order: 1; }
|
||||||
|
|
||||||
|
.links {
|
||||||
|
display: flex;
|
||||||
|
gap: 32px;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1152px) {
|
||||||
|
.VPFooter {
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
.links {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { menu } from '../../../data/iconNodes'
|
||||||
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
||||||
|
|
||||||
|
const Menu = createLucideIcon('menu', menu)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Menu />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import packageData from '../../../data/packageData.json';
|
||||||
|
import thirdPartyPackages from '../../../data/packageData.thirdParty.json';
|
||||||
|
import fetchPackages from "../../../lib/fetchPackages";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
async load() {
|
||||||
|
const packages = await fetchPackages();
|
||||||
|
return {
|
||||||
|
packages: packages
|
||||||
|
.filter(p => p.name in packageData)
|
||||||
|
.map((pData) => ({
|
||||||
|
...pData,
|
||||||
|
...packageData[pData.name],
|
||||||
|
documentation: `/guide/packages/${pData.name}`,
|
||||||
|
source: `https://github.com/lucide-icons/lucide/tree/main/packages/${pData.name}`,
|
||||||
|
icon: `/framework-logos/${packageData[pData.name].icon}.svg`,
|
||||||
|
})).sort((a, b) => a.order - b.order),
|
||||||
|
thirdPartyPackages,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
64
docs/.vitepress/theme/components/packages/PackageList.vue
Normal file
64
docs/.vitepress/theme/components/packages/PackageList.vue
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {data} from './PackageList.data'
|
||||||
|
import PackageListItem from "./PackageListItem.vue";</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="package-group">
|
||||||
|
<h1 class="name">Packages</h1>
|
||||||
|
<div class="grid package-list" ref="container">
|
||||||
|
<div v-for="packageData in data.packages" class="item">
|
||||||
|
<PackageListItem :packageData="packageData"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="package-group">
|
||||||
|
<h2 class="name">Third-party packages</h2>
|
||||||
|
<div class="grid package-list" ref="container">
|
||||||
|
<div v-for="packageData in data.thirdPartyPackages" class="item">
|
||||||
|
<PackageListItem :packageData="packageData"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.name {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-group {
|
||||||
|
margin-bottom: 96px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: space-evenly;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid > * {
|
||||||
|
flex-basis: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.grid > * {
|
||||||
|
flex-basis: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
.grid > * {
|
||||||
|
flex-basis: 33.33%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
110
docs/.vitepress/theme/components/packages/PackageListItem.vue
Normal file
110
docs/.vitepress/theme/components/packages/PackageListItem.vue
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from 'vitepress';
|
||||||
|
import {PackageItem} from "../../types";
|
||||||
|
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||||
|
|
||||||
|
const { go } = useRouter()
|
||||||
|
const props = defineProps<{
|
||||||
|
packageData: PackageItem,
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<article class="package">
|
||||||
|
<header class="package-header">
|
||||||
|
<div class="package-icon-well">
|
||||||
|
<img :src="packageData.icon" alt="" class="package-icon" :class="{[packageData.iconClass]: true, light: packageData.iconDark}" />
|
||||||
|
<img v-if="packageData.iconDark" :src="packageData.iconDark" alt="" class="package-icon dark" :class="packageData.iconClass" />
|
||||||
|
</div>
|
||||||
|
<div class="package-title">
|
||||||
|
<h2 class="title">{{ props.packageData.name }}</h2>
|
||||||
|
<a v-for="shield in props.packageData.shields" :href="shield.href" class="package-shield" rel="noreferrer noopener">
|
||||||
|
<img :src="shield.src" :alt="shield.href" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="package-details">
|
||||||
|
{{ packageData.description }}
|
||||||
|
</div>
|
||||||
|
<footer class="package-footer">
|
||||||
|
<VPButton
|
||||||
|
:href="packageData.documentation"
|
||||||
|
text="Guide"
|
||||||
|
theme="brand"
|
||||||
|
@click="go(packageData.documentation)"
|
||||||
|
/>
|
||||||
|
<VPButton
|
||||||
|
:href="packageData.source"
|
||||||
|
text="Source"
|
||||||
|
theme="alt"
|
||||||
|
@click="go(packageData.source)"
|
||||||
|
/>
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.package {
|
||||||
|
border: 1px solid var(--vp-c-bg-soft);
|
||||||
|
border-radius: 12px;
|
||||||
|
background-color: var(--vp-c-bg-soft);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
.package {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.package-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
@media screen and (min-width: 480px) {
|
||||||
|
.package-header {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.package-icon-well {
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background-color: var(--vp-c-bg);
|
||||||
|
}
|
||||||
|
.package-icon {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
}
|
||||||
|
h2.title {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.package-title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.package-details {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.package-footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
html.dark .package-icon-invert {
|
||||||
|
filter: invert(1) hue-rotate(180deg);
|
||||||
|
}
|
||||||
|
html.dark .package-icon.light {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
html:not(.dark) .package-icon.dark {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
87
docs/.vitepress/theme/composables/useActiveAnchor.ts
Normal file
87
docs/.vitepress/theme/composables/useActiveAnchor.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { onMounted, onUpdated, onUnmounted } from 'vue';
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function useActiveAnchor(container, marker) {
|
||||||
|
const onScroll = throttleAndDebounce(setActiveLink, 100);
|
||||||
|
let prevActiveLink = null;
|
||||||
|
onMounted(() => {
|
||||||
|
requestAnimationFrame(setActiveLink);
|
||||||
|
window.addEventListener('scroll', onScroll);
|
||||||
|
});
|
||||||
|
onUpdated(() => {
|
||||||
|
// sidebar update means a route change
|
||||||
|
activateLink(location.hash);
|
||||||
|
});
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('scroll', onScroll);
|
||||||
|
});
|
||||||
|
function setActiveLink() {
|
||||||
|
const links = [].slice.call(container.value.querySelectorAll('.outline-link'));
|
||||||
|
const anchors = [].slice
|
||||||
|
.call(document.querySelectorAll('.content .header-anchor'))
|
||||||
|
.filter((anchor) => {
|
||||||
|
return links.some((link) => {
|
||||||
|
return link.hash === anchor.hash && anchor.offsetParent !== null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const scrollY = window.scrollY;
|
||||||
|
const innerHeight = window.innerHeight;
|
||||||
|
const offsetHeight = document.body.offsetHeight;
|
||||||
|
const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1;
|
||||||
|
// page bottom - highlight last one
|
||||||
|
if (anchors.length && isBottom) {
|
||||||
|
activateLink(anchors[anchors.length - 1].hash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < anchors.length; i++) {
|
||||||
|
const anchor = anchors[i];
|
||||||
|
const nextAnchor = anchors[i + 1];
|
||||||
|
const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor);
|
||||||
|
if (isActive) {
|
||||||
|
activateLink(hash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function activateLink(hash) {
|
||||||
|
if (prevActiveLink) {
|
||||||
|
prevActiveLink.classList.remove('active');
|
||||||
|
}
|
||||||
|
if (hash !== null) {
|
||||||
|
prevActiveLink = container.value.querySelector(`a[href="${decodeURIComponent(hash)}"]`);
|
||||||
|
}
|
||||||
|
const activeLink = prevActiveLink;
|
||||||
|
if (activeLink) {
|
||||||
|
activeLink.classList.add('active');
|
||||||
|
marker.value.style.top = activeLink.offsetTop + 5 + 'px';
|
||||||
|
marker.value.style.opacity = '1';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
marker.value.style.top = '33px';
|
||||||
|
marker.value.style.opacity = '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PAGE_OFFSET = 64;
|
||||||
|
|
||||||
|
function getAnchorTop(anchor) {
|
||||||
|
return anchor.parentElement.offsetTop - PAGE_OFFSET;
|
||||||
|
}
|
||||||
|
function isAnchorActive(index, anchor, nextAnchor) {
|
||||||
|
const scrollTop = window.scrollY;
|
||||||
|
if (index === 0 && scrollTop === 0) {
|
||||||
|
return [true, null];
|
||||||
|
}
|
||||||
|
if (scrollTop < getAnchorTop(anchor)) {
|
||||||
|
return [false, null];
|
||||||
|
}
|
||||||
|
if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor)) {
|
||||||
|
return [true, anchor.hash];
|
||||||
|
}
|
||||||
|
return [false, null];
|
||||||
|
}
|
||||||
25
docs/.vitepress/theme/composables/useCategoryView.ts
Normal file
25
docs/.vitepress/theme/composables/useCategoryView.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import {
|
||||||
|
ref, inject, Ref
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
|
export const CATEGORY_VIEW_CONTEXT = Symbol('categoryView');
|
||||||
|
|
||||||
|
interface CategoryViewContext {
|
||||||
|
selectedCategory: Ref<string>
|
||||||
|
categoryCounts: Ref<Record<string, number>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const categoryViewContext = {
|
||||||
|
selectedCategory: ref(''),
|
||||||
|
categoryCounts: ref({}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useCategoryView(): CategoryViewContext {
|
||||||
|
const context = inject<CategoryViewContext>(CATEGORY_VIEW_CONTEXT);
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useCategoryView must be used with categoryView context');
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
20
docs/.vitepress/theme/composables/useConfetti.ts
Normal file
20
docs/.vitepress/theme/composables/useConfetti.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
export default function useConfetti() {
|
||||||
|
const animate = ref(false)
|
||||||
|
const confettiText = ref('confetti!')
|
||||||
|
|
||||||
|
function confetti() {
|
||||||
|
animate.value = true;
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
animate.value = false;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
animate,
|
||||||
|
confetti,
|
||||||
|
confettiText
|
||||||
|
}
|
||||||
|
}
|
||||||
12
docs/.vitepress/theme/composables/useFetchCategories.ts
Normal file
12
docs/.vitepress/theme/composables/useFetchCategories.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { useFetch } from "@vueuse/core"
|
||||||
|
|
||||||
|
const useFetchCategories = () => useFetch<Record<string, string[]>>(
|
||||||
|
`${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/categories`,
|
||||||
|
{
|
||||||
|
immediate:
|
||||||
|
typeof window !== 'undefined'
|
||||||
|
&& new URLSearchParams(window.location.search).has('search'),
|
||||||
|
}
|
||||||
|
).json()
|
||||||
|
|
||||||
|
export default useFetchCategories
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user