mirror of
https://github.com/lucide-icons/lucide.git
synced 2025-12-17 16:37:42 +01:00
Compare commits
42 Commits
package/an
...
update-sit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
797cf7667d | ||
|
|
979e07fbf9 | ||
|
|
2f5f78c1c2 | ||
|
|
634322e96c | ||
|
|
53ebc4c076 | ||
|
|
7d023209f0 | ||
|
|
d58a2e43c6 | ||
|
|
5ecf78bb8a | ||
|
|
aa8f74eb9e | ||
|
|
7327637532 | ||
|
|
08bd4b33a0 | ||
|
|
b23d4f110b | ||
|
|
7d86d083bd | ||
|
|
5c523be780 | ||
|
|
498b8390d7 | ||
|
|
528de5284b | ||
|
|
a19fb21c5d | ||
|
|
5770f62d02 | ||
|
|
94e3f56166 | ||
|
|
a432aeee69 | ||
|
|
b0ed2827f8 | ||
|
|
bfbd5d7bda | ||
|
|
1090cbc810 | ||
|
|
fa6e015520 | ||
|
|
1756617885 | ||
|
|
f5d0a2a220 | ||
|
|
b1675c4c33 | ||
|
|
5e27d7d11a | ||
|
|
3a608bac58 | ||
|
|
bcebce5927 | ||
|
|
84346a148e | ||
|
|
fac9fa4efd | ||
|
|
cde1ebaa0b | ||
|
|
9f67c0943b | ||
|
|
999bca4575 | ||
|
|
d7e7493bd6 | ||
|
|
f34e889831 | ||
|
|
65403c9298 | ||
|
|
8eb64f4574 | ||
|
|
d1dd94a717 | ||
|
|
758fa4b75f | ||
|
|
7bbb1e1fea |
@@ -9,9 +9,3 @@ strikethrough
|
||||
touchpad
|
||||
ungroup
|
||||
toc
|
||||
|
||||
# Brands
|
||||
codepen
|
||||
codesandbox
|
||||
dribbble
|
||||
x.com
|
||||
|
||||
56
.github/workflows/ci.yml
vendored
56
.github/workflows/ci.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
@@ -36,19 +36,25 @@ jobs:
|
||||
id: latest-tag
|
||||
run: echo "LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Log latest tag
|
||||
run: echo '${{ steps.latest-tag.outputs.LATEST_TAG }}'
|
||||
|
||||
- name: Check if we can patch
|
||||
run: pnpm semver $LATEST_TAG -i minor
|
||||
env:
|
||||
LATEST_TAG: ${{ steps.latest-tag.outputs.LATEST_TAG }}
|
||||
run: .github/workflows/version-up.sh --minor
|
||||
|
||||
- name: Create new version
|
||||
id: new-version
|
||||
run: echo "NEW_VERSION=$(pnpm semver $LATEST_TAG -i minor)" >> $GITHUB_OUTPUT
|
||||
run: echo "NEW_VERSION=$(.github/workflows/version-up.sh --minor)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create change log
|
||||
id: change-log
|
||||
run: |
|
||||
CHANGE_LOG=$(pnpm run generate:changelog --old-tag=${{ steps.latest-tag.outputs.LATEST_TAG }})
|
||||
CHANGE_LOG=$(tail -n +5 <<< $CHANGE_LOG)
|
||||
echo $CHANGE_LOG
|
||||
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
|
||||
echo "CHANGE_LOG<<$EOF" >> $GITHUB_OUTPUT
|
||||
echo "$CHANGE_LOG" >> $GITHUB_OUTPUT
|
||||
echo "$EOF" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
LATEST_TAG: ${{ steps.latest-tag.outputs.LATEST_TAG }}
|
||||
GITHUB_API_KEY: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Check output
|
||||
run: |
|
||||
@@ -62,6 +68,38 @@ jobs:
|
||||
name: Version ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||
generate_release_notes: true
|
||||
|
||||
test-semantic-release:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Semantic Release
|
||||
id: semantic
|
||||
uses: cycjimmy/semantic-release-action@v4
|
||||
with:
|
||||
tag_format: ${version}
|
||||
branches: |
|
||||
['new-release-workflow']
|
||||
extends: |
|
||||
semantic-release-monorepo
|
||||
extra_plugins: |
|
||||
@semantic-release/github
|
||||
@semantic-release/git
|
||||
@semantic-release/release-notes-generator
|
||||
conventional-changelog-conventionalcommits
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Log output
|
||||
if: steps.semantic.outputs.new_release_published == 'true'
|
||||
run: |
|
||||
echo ${{ steps.semantic.outputs.new_release_version }}
|
||||
echo ${{ steps.semantic.outputs.new_release_major_version }}
|
||||
echo ${{ steps.semantic.outputs.new_release_minor_version }}
|
||||
echo ${{ steps.semantic.outputs.new_release_patch_version }}
|
||||
|
||||
start-release:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
needs: create-release
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Close Icon Requests with Brand Terms
|
||||
name: Close Issue with Banned Phrases
|
||||
|
||||
on:
|
||||
issues:
|
||||
@@ -13,36 +13,23 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load stopwords from JSON & check issue title & body
|
||||
if: contains(github.event.issue.labels.*.name, '🙌 icon request')
|
||||
- name: Check for blocked phrases in issue title
|
||||
run: |
|
||||
ISSUE_TITLE=$(jq -r '.issue.title' "$GITHUB_EVENT_PATH")
|
||||
ISSUE_BODY=$(jq -r '.issue.body // ""' "$GITHUB_EVENT_PATH")
|
||||
ICON_NAME_SECTION=$(printf '%s\n' "$ISSUE_BODY" | awk '/### Icon name/{flag=1; next} /^### /{flag=0} flag')
|
||||
BLOCKED_PHRASES=("twitter" "whatsapp" "logo" "google" "tiktok" "facebook" "slack" "discord" "bluesky" "spotify" "behance" "pix" "x.com" "telegram")
|
||||
|
||||
jq -r 'to_entries[] | "\(.key) \(.value)"' brand-stopwords.json | while read -r KEY VALUE; do
|
||||
SAFE_KEY=$(printf '%s\n' "$KEY" | sed 's/[][\.^$*]/\\&/g')
|
||||
SAFE_VALUE=$(printf '%s\n' "$VALUE" | sed 's/[][\.^$*]/\\&/g')
|
||||
# Check title and body for blocked phrases
|
||||
for PHRASE in "${BLOCKED_PHRASES[@]}"
|
||||
do
|
||||
if echo "$ISSUE_TITLE" | grep -i "$PHRASE"; then
|
||||
gh issue close ${{ github.event.issue.number }} --reason "not planned" --comment "This looks like a duplicate, use the [search](https://github.com/lucide-icons/lucide/issues?q=is%3Aissue+$PHRASE) to find similar issues.
|
||||
|
||||
if echo "$ISSUE_TITLE" | grep -iqE "$SAFE_KEY|$SAFE_VALUE" || \
|
||||
{ [ -n "$ICON_NAME_SECTION" ] && echo "$ICON_NAME_SECTION" | grep -iqE "$SAFE_KEY|$SAFE_VALUE"; }; then
|
||||
|
||||
gh issue close ${{ github.event.issue.number }} \
|
||||
--reason "not_planned" \
|
||||
--comment "It looks like this request is about **${VALUE}**, which is a brand logo.
|
||||
|
||||
Lucide **does not accept** brand logos, and we do not plan to add them in the future. This is due to a combination of **legal restrictions**, **design consistency concerns**, and **practical maintenance reasons**.
|
||||
|
||||
[Click here to read our official statement about brand logos in Lucide.](./BRAND_LOGOS_STATEMENT.md)
|
||||
|
||||
You can [search for similar issues.](https://github.com/lucide-icons/lucide/issues?q=is%3Aissue+${VALUE})
|
||||
|
||||
We’re always happy to help on [Discord](https://discord.gg/EH6nSts)."
|
||||
Read [our official statement about brand logos in Lucide](https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md).
|
||||
|
||||
Always happy to help on [Discord](https://discord.gg/EH6nSts)."
|
||||
gh issue lock ${{ github.event.issue.number }} --reason spam
|
||||
exit 0
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
43
.github/workflows/lucide-vue.yml
vendored
Normal file
43
.github/workflows/lucide-vue.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Lucide Vue checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/lucide-vue/**
|
||||
- packages/shared/**
|
||||
- tools/build-icons/**
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter @lucide/vue build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter @lucide/vue test
|
||||
13
.github/workflows/release.yml
vendored
13
.github/workflows/release.yml
vendored
@@ -20,7 +20,7 @@ on:
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
pre-release:
|
||||
@@ -60,6 +60,7 @@ jobs:
|
||||
'lucide-svelte',
|
||||
'@lucide/astro',
|
||||
'@lucide/svelte',
|
||||
'@lucide/vue',
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
@@ -72,6 +73,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Set Auth Token
|
||||
run: npm config set //registry.npmjs.org/:_authToken ${{ inputs.NPM_TOKEN || secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Set new version
|
||||
run: pnpm --filter ${{ matrix.package }} version --new-version ${{ needs.pre-release.outputs.VERSION }} --no-git-tag-version
|
||||
|
||||
@@ -106,6 +110,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Set Auth Token
|
||||
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Set new version
|
||||
run: pnpm --filter lucide-static version --new-version ${{ needs.pre-release.outputs.VERSION }} --no-git-tag-version
|
||||
|
||||
@@ -151,9 +158,7 @@ jobs:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [pre-release, lucide-font]
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/download-artifact@v4
|
||||
|
||||
284
.github/workflows/version-up.sh
vendored
Executable file
284
.github/workflows/version-up.sh
vendored
Executable file
@@ -0,0 +1,284 @@
|
||||
#!/usr/bin/env bash
|
||||
## Copyright (C) 2017, Oleksandr Kucherenko
|
||||
## Last revisit: 2017-09-29
|
||||
|
||||
## get highest version tag for all branches
|
||||
function highest_tag(){
|
||||
local TAG=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
echo "$TAG"
|
||||
}
|
||||
|
||||
## extract current branch name
|
||||
function current_branch(){
|
||||
## expected: heads/{branch_name}
|
||||
## expected: {branch_name}
|
||||
local BRANCH=$(git rev-parse --abbrev-ref HEAD | cut -d"/" -f2)
|
||||
echo "$BRANCH"
|
||||
}
|
||||
|
||||
## get latest/head commit hash number
|
||||
function head_hash(){
|
||||
local COMMIT_HASH=$(git rev-parse --verify HEAD)
|
||||
echo "$COMMIT_HASH"
|
||||
}
|
||||
|
||||
## extract tag commit hash code, tag name provided by argument
|
||||
function tag_hash(){
|
||||
local TAG_HASH=$(git log -1 --format=format:"%H" $1 2>/dev/null | tail -n1)
|
||||
echo "$TAG_HASH"
|
||||
}
|
||||
|
||||
## get latest revision number
|
||||
function latest_revision(){
|
||||
local REV=$(git rev-list --count HEAD 2>/dev/null)
|
||||
echo "$REV"
|
||||
}
|
||||
|
||||
## parse last found tag, extract it PARTS
|
||||
function parse_last(){
|
||||
local position=$(($1-1))
|
||||
|
||||
# two parts found only
|
||||
local SUBS=( ${PARTS[$position]//-/ } )
|
||||
#echo ${SUBS[@]}, size: ${#SUBS}
|
||||
|
||||
# found NUMBER
|
||||
PARTS[$position]=${SUBS[0]}
|
||||
#echo ${PARTS[@]}
|
||||
|
||||
# found SUFFIX
|
||||
if [[ ${#SUBS} -ge 1 ]]; then
|
||||
PARTS[4]=${SUBS[1],,} #lowercase
|
||||
#echo ${PARTS[@]}, ${SUBS[@]}
|
||||
fi
|
||||
}
|
||||
|
||||
## increment REVISION part, don't touch STAGE
|
||||
function increment_revision(){
|
||||
PARTS[3]=$(( PARTS[3] + 1 ))
|
||||
IS_DIRTY=1
|
||||
}
|
||||
|
||||
## increment PATCH part, reset all other lower PARTS, don't touch STAGE
|
||||
function increment_patch(){
|
||||
PARTS[2]=$(( PARTS[2] + 1 ))
|
||||
PARTS[3]=0
|
||||
IS_DIRTY=1
|
||||
}
|
||||
|
||||
## increment MINOR part, reset all other lower PARTS, don't touch STAGE
|
||||
function increment_minor(){
|
||||
PARTS[1]=$(( PARTS[1] + 1 ))
|
||||
PARTS[2]=0
|
||||
PARTS[3]=0
|
||||
IS_DIRTY=1
|
||||
}
|
||||
|
||||
## increment MAJOR part, reset all other lower PARTS, don't touch STAGE
|
||||
function incremet_major(){
|
||||
PARTS[0]="v$(( PARTS[0] + 1 ))"
|
||||
PARTS[1]=0
|
||||
PARTS[2]=0
|
||||
PARTS[3]=0
|
||||
IS_DIRTY=1
|
||||
}
|
||||
|
||||
## increment the number only of last found PART: REVISION --> PATCH --> MINOR. don't touch STAGE
|
||||
function increment_last_found(){
|
||||
if [[ "${#PARTS[3]}" == 0 || "${PARTS[3]}" == "0" ]]; then
|
||||
if [[ "${#PARTS[2]}" == 0 || "${PARTS[2]}" == "0" ]]; then
|
||||
increment_minor
|
||||
else
|
||||
increment_patch
|
||||
fi
|
||||
else
|
||||
increment_revision
|
||||
fi
|
||||
|
||||
# stage part is not EMPTY
|
||||
if [[ "${#PARTS[4]}" != 0 ]]; then
|
||||
IS_SHIFT=1
|
||||
fi
|
||||
}
|
||||
|
||||
## compose version from PARTS
|
||||
function compose(){
|
||||
MAJOR="${PARTS[0]}"
|
||||
MINOR=".${PARTS[1]}"
|
||||
PATCH=".${PARTS[2]}"
|
||||
REVISION=".${PARTS[3]}"
|
||||
SUFFIX="-${PARTS[4]}"
|
||||
|
||||
if [[ "${#PATCH}" == 1 ]]; then # if empty {PATCH}
|
||||
PATCH=""
|
||||
fi
|
||||
|
||||
if [[ "${#REVISION}" == 1 ]]; then # if empty {REVISION}
|
||||
REVISION=""
|
||||
fi
|
||||
|
||||
if [[ "${PARTS[3]}" == "0" ]]; then # if revision is ZERO
|
||||
REVISION=""
|
||||
fi
|
||||
|
||||
# shrink patch and revision
|
||||
if [[ -z "${REVISION// }" ]]; then
|
||||
if [[ "${PARTS[2]}" == "0" ]]; then
|
||||
PATCH=".0"
|
||||
fi
|
||||
else # revision is not EMPTY
|
||||
if [[ "${#PATCH}" == 0 ]]; then
|
||||
PATCH=".0"
|
||||
fi
|
||||
fi
|
||||
|
||||
# remove suffix if we don't have a alpha/beta/rc
|
||||
if [[ "${#SUFFIX}" == 1 ]]; then
|
||||
SUFFIX=""
|
||||
fi
|
||||
|
||||
|
||||
echo "${MAJOR}${MINOR}${PATCH}${REVISION}${SUFFIX}" #full format
|
||||
}
|
||||
|
||||
# initial version used for repository without tags
|
||||
INIT_VERSION=0.0.0.0-alpha
|
||||
|
||||
# do GIT data extracting
|
||||
TAG=$(highest_tag)
|
||||
REVISION=$(latest_revision)
|
||||
BRANCH=$(current_branch)
|
||||
TAG_HASH=$(tag_hash $TAG)
|
||||
HEAD_HASH=$(head_hash)
|
||||
|
||||
# if tag and branch commit hashes are different, than print info about that
|
||||
#echo $HEAD_HASH vs $TAG_HASH
|
||||
if [[ "$@" == "" ]]; then
|
||||
if [[ "$TAG_HASH" == "$HEAD_HASH" ]]; then
|
||||
echo "Tag $TAG and HEAD are aligned. We will stay on the TAG version."
|
||||
echo ""
|
||||
NO_ARGS_VALUE='--stay'
|
||||
else
|
||||
PATTERN="^[0-9]+.[0-9]+(.[0-9]+)*(-(alpha|beta|rc))*$"
|
||||
|
||||
if [[ "$BRANCH" =~ $PATTERN ]]; then
|
||||
echo "Detected version branch '$BRANCH'. We will auto-increment the last version PART."
|
||||
echo ""
|
||||
NO_ARGS_VALUE='--default'
|
||||
else
|
||||
echo "Detected branch name '$BRANCH' than does not match version pattern. We will increase MINOR."
|
||||
echo ""
|
||||
NO_ARGS_VALUE='--minor'
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
#
|
||||
# {MAJOR}.{MINOR}[.{PATCH}[.{REVISION}][-(.*)]
|
||||
#
|
||||
# Suffix: alpha, beta, rc
|
||||
# No Suffix --> {NEW_VERSION}-alpha
|
||||
# alpha --> beta
|
||||
# beta --> rc
|
||||
# rc --> {VERSION}
|
||||
#
|
||||
PARTS=( ${TAG//./ } )
|
||||
parse_last ${#PARTS[@]} # array size as argument
|
||||
#echo ${PARTS[@]}
|
||||
|
||||
# if no parameters than emulate --default parameter
|
||||
if [[ "$@" == "" ]]; then
|
||||
set -- $NO_ARGS_VALUE
|
||||
fi
|
||||
|
||||
# parse input parameters
|
||||
for i in "$@"
|
||||
do
|
||||
key="$i"
|
||||
|
||||
case $key in
|
||||
-a|--alpha) # switched to ALPHA
|
||||
PARTS[4]="alpha"
|
||||
IS_SHIFT=1
|
||||
;;
|
||||
-b|--beta) # switched to BETA
|
||||
PARTS[4]="beta"
|
||||
IS_SHIFT=1
|
||||
;;
|
||||
-c|--release-candidate) # switched to RC
|
||||
PARTS[4]="rc"
|
||||
IS_SHIFT=1
|
||||
;;
|
||||
-r|--release) # switched to RELEASE
|
||||
PARTS[4]=""
|
||||
IS_SHIFT=1
|
||||
;;
|
||||
-p|--patch) # increment of PATCH
|
||||
increment_patch
|
||||
;;
|
||||
-e|--revision) # increment of REVISION
|
||||
increment_revision
|
||||
;;
|
||||
-g|--git-revision) # use git revision number as a revision part§
|
||||
PARTS[3]=$(( REVISION ))
|
||||
IS_DIRTY=1
|
||||
;;
|
||||
-i|--minor) # increment of MINOR by default
|
||||
increment_minor
|
||||
;;
|
||||
--default) # stay on the same stage, but increment only last found PART of version code
|
||||
increment_last_found
|
||||
;;
|
||||
-m|--major) # increment of MAJOR
|
||||
incremet_major
|
||||
;;
|
||||
-s|--stay) # extract version info
|
||||
IS_DIRTY=1
|
||||
NO_APPLY_MSG=1
|
||||
;;
|
||||
-t|--tag-only) # extract version info
|
||||
TAG_ONLY=1
|
||||
;;
|
||||
--apply)
|
||||
DO_APPLY=1
|
||||
;;
|
||||
-h|--help)
|
||||
help
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# detected shift, but no increment
|
||||
if [[ "$IS_SHIFT" == "1" ]]; then
|
||||
# temporary disable stage shift
|
||||
stage=${PARTS[4]}
|
||||
PARTS[4]=''
|
||||
|
||||
# detect first run on repository, INIT_VERSION was used
|
||||
if [[ "$(compose)" == "0.0" ]]; then
|
||||
increment_minor
|
||||
fi
|
||||
|
||||
PARTS[4]=$stage
|
||||
fi
|
||||
|
||||
# no increment applied yet and no shift of state, do minor increase
|
||||
if [[ "$IS_DIRTY$IS_SHIFT" == "" ]]; then
|
||||
increment_minor
|
||||
fi
|
||||
|
||||
compose
|
||||
|
||||
# is proposed tag in conflict with any other TAG
|
||||
PROPOSED_HASH=$(tag_hash $(compose))
|
||||
if [[ "${#PROPOSED_HASH}" -gt 0 && "$NO_APPLY_MSG" == "" ]]; then
|
||||
echo -e "\033[31mERROR:\033[0m "
|
||||
echo -e "\033[31mERROR:\033[0m Found conflict with existing tag \033[32m$(compose)\033[0m / $PROPOSED_HASH"
|
||||
echo -e "\033[31mERROR:\033[0m Only manual resolving is possible now."
|
||||
echo -e "\033[31mERROR:\033[0m "
|
||||
echo -e "\033[31mERROR:\033[0m To Resolve try to add --revision or --patch modifier."
|
||||
echo -e "\033[31mERROR:\033[0m "
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -44,7 +44,7 @@ docs/.vitepress/data/releaseMetaData
|
||||
docs/.vitepress/data/categoriesData.json
|
||||
docs/.vitepress/data/iconDetails
|
||||
docs/.vitepress/data/relatedIcons.json
|
||||
docs/.vitepress/data/brandStopwords.json
|
||||
docs/.vercel
|
||||
docs/.nitro
|
||||
.gitignore
|
||||
|
||||
|
||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -2,5 +2,5 @@
|
||||
"cSpell.words": ["devs", "preact", "Preact"],
|
||||
"eslint.enable": true,
|
||||
"eslint.validate": ["javascript", "svg"],
|
||||
"svg.preview.background": "transparent"
|
||||
"svg.preview.background": "editor"
|
||||
}
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
{
|
||||
"adobe": "Adobe",
|
||||
"airplay": "AirPlay",
|
||||
"amazon": "Amazon",
|
||||
"angular": "Angular",
|
||||
"aws": "AWS",
|
||||
"azure": "Azure",
|
||||
"bandcamp": "Bandcamp",
|
||||
"behance": "Behance",
|
||||
"bitbucket": "Bitbucket",
|
||||
"blender": "Blender",
|
||||
"bluesky": "BlueSky",
|
||||
"bootstrap": "Bootstrap",
|
||||
"brave": "Brave",
|
||||
"chakra": "Chakra UI",
|
||||
"chrome": "Chrome",
|
||||
"codepen": "Codepen",
|
||||
"codesandbox": "CodeSandbox",
|
||||
"csharp": "C#",
|
||||
"cypress": "Cypress",
|
||||
"dart": "Dart",
|
||||
"deezer": "Deezer",
|
||||
"deno": "Deno",
|
||||
"discord": "Discord",
|
||||
"docker": "Docker",
|
||||
"dribbble": "Dribbble",
|
||||
"dropbox": "Dropbox",
|
||||
"edge": "Edge",
|
||||
"ember": "Ember",
|
||||
"epic": "Epic Games",
|
||||
"erlang": "Erlang",
|
||||
"esbuild": "esbuild",
|
||||
"eslint": "ESLint",
|
||||
"facebook": "Facebook",
|
||||
"figjam": "FigJam",
|
||||
"figma": "Figma",
|
||||
"firebase": "Firebase",
|
||||
"firefox": "Firefox",
|
||||
"framer": "Framer",
|
||||
"gatsby": "Gatsby",
|
||||
"gcp": "Google Cloud",
|
||||
"github": "GitHub",
|
||||
"gitlab": "GitLab",
|
||||
"golang": "GoLang",
|
||||
"google": "Google",
|
||||
"gmail": "Gmail",
|
||||
"gravatar": "Gravatar",
|
||||
"haskell": "Haskell",
|
||||
"instagram": "Instagram",
|
||||
"java": "Java",
|
||||
"javascript": "JavaScript",
|
||||
"jest": "Jest",
|
||||
"jira": "Jira",
|
||||
"kotlin": "Kotlin",
|
||||
"kubernetes": "Kubernetes",
|
||||
"less": "Less",
|
||||
"leetcode": "LeetCode",
|
||||
"leet-code": "LeetCode",
|
||||
"line": "LINE",
|
||||
"linkedin": "LinkedIn",
|
||||
"lua": "Lua",
|
||||
"mariadb": "MariaDB",
|
||||
"mcp": "MCP",
|
||||
"messenger": "Messenger",
|
||||
"microsoft": "Microsoft",
|
||||
"mongodb": "MongoDB",
|
||||
"mui": "Material UI",
|
||||
"mysql": "MySQL",
|
||||
"nestjs": "NestJS",
|
||||
"netflix": "Netflix",
|
||||
"netlify": "Netlify",
|
||||
"next": "Next.js",
|
||||
"nodejs": "Node.js",
|
||||
"notion": "Notion",
|
||||
"nostr": "Nostr",
|
||||
"npm": "npm",
|
||||
"nuxt": "Nuxt",
|
||||
"opera": "Opera",
|
||||
"oracle": "Oracle",
|
||||
"patreon": "Patreon",
|
||||
"paypal": "PayPal",
|
||||
"perl": "Perl",
|
||||
"php": "PHP",
|
||||
"pinterest": "Pinterest",
|
||||
"pix": "PiX",
|
||||
"playstation": "PlayStation",
|
||||
"playwright": "Playwright",
|
||||
"pnpm": "pnpm",
|
||||
"postcss": "PostCSS",
|
||||
"postgresql": "PostgreSQL",
|
||||
"prettier": "Prettier",
|
||||
"prisma": "Prisma",
|
||||
"python": "Python",
|
||||
"qwik": "Qwik",
|
||||
"react": "React",
|
||||
"reddit": "Reddit",
|
||||
"redis": "Redis",
|
||||
"rollup": "Rollup",
|
||||
"rust": "Rust",
|
||||
"safari": "Safari",
|
||||
"sass": "Sass",
|
||||
"scala": "Scala",
|
||||
"scss": "Sass",
|
||||
"semantic": "Semantic UI",
|
||||
"shopify": "Shopify",
|
||||
"skype": "Skype",
|
||||
"slack": "Slack",
|
||||
"solid": "SolidJS",
|
||||
"soundcloud": "SoundCloud",
|
||||
"spotify": "Spotify",
|
||||
"sqlite": "SQLite",
|
||||
"squarespace": "Squarespace",
|
||||
"steam": "Steam",
|
||||
"stripe": "Stripe",
|
||||
"substack": "Substack",
|
||||
"supabase": "Supabase",
|
||||
"surge": "Surge",
|
||||
"svelte": "Svelte",
|
||||
"swift": "Swift",
|
||||
"tailwind": "Tailwind CSS",
|
||||
"telegram": "Telegram",
|
||||
"terraform": "Terraform",
|
||||
"tesla": "Tesla",
|
||||
"tidal": "Tidal",
|
||||
"tiktok": "TikTok",
|
||||
"trello": "Trello",
|
||||
"twitch": "Twitch",
|
||||
"twitter": "Twitter",
|
||||
"typescript": "TypeScript",
|
||||
"unity": "Unity",
|
||||
"unreal": "Unreal Engine",
|
||||
"vercel": "Vercel",
|
||||
"vimeo": "Vimeo",
|
||||
"vite": "Vite",
|
||||
"vitest": "Vitest",
|
||||
"vue": "Vue",
|
||||
"webpack": "Webpack",
|
||||
"wechat": "WeChat",
|
||||
"whatsapp": "WhatsApp",
|
||||
"windows": "Windows",
|
||||
"wix": "Wix",
|
||||
"x.com": "X.com",
|
||||
"x-social": "X.com",
|
||||
"xbox": "Xbox",
|
||||
"yarn": "Yarn",
|
||||
"youtube": "YouTube",
|
||||
"zig": "Zig",
|
||||
"zoom": "Zoom"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Brands",
|
||||
"icon": "facebook"
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
import { createElement } from 'react';
|
||||
import Diff from '../../../lib/SvgPreview/Diff.tsx';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const { params } = event.context;
|
||||
|
||||
const pathData = params.data.split('/');
|
||||
const data = pathData.at(-1).slice(0, -4);
|
||||
const [operation] = pathData;
|
||||
|
||||
const newSrc = Buffer.from(data, 'base64')
|
||||
.toString('utf8')
|
||||
.replaceAll('\n', '')
|
||||
.replace(/<svg[^>]*>|<\/svg>/g, '');
|
||||
|
||||
const width = parseInt(
|
||||
(newSrc.includes('<svg ') ? newSrc.match(/width="(\d+)"/)?.[1] : null) ?? '24',
|
||||
);
|
||||
const height = parseInt(
|
||||
(newSrc.includes('<svg ') ? newSrc.match(/height="(\d+)"/)?.[1] : null) ?? '24',
|
||||
);
|
||||
|
||||
const children = [];
|
||||
|
||||
let oldSrc = '';
|
||||
if (operation.startsWith('rotate-')) {
|
||||
const degrees = parseInt(operation.replace('rotate-', ''));
|
||||
if (isNaN(degrees)) return '';
|
||||
oldSrc = `<g transform="rotate(${degrees} ${width / 2} ${height / 2})">${newSrc}</g>`;
|
||||
} else if (operation === 'flip-horizontal') {
|
||||
oldSrc = `<g transform="scale(1, -1) translate(0, -${height})">${newSrc}</g>`;
|
||||
} else if (operation === 'flip-vertical') {
|
||||
oldSrc = `<g transform="scale(-1, 1) translate(-${width}, 0)">${newSrc}</g>`;
|
||||
} else if (operation === 'flip-backslash') {
|
||||
oldSrc = `<g transform="rotate(90, ${width / 2}, ${height / 2}) scale(1, -1) translate(0, -${height})">${newSrc}</g>`;
|
||||
} else if (operation === 'flip-slash') {
|
||||
oldSrc = `<g transform="rotate(90, ${width / 2}, ${height / 2}) scale(-1, 1) translate(-${width}, 0)">${newSrc}</g>`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
|
||||
const svg = Buffer.from(
|
||||
// We can't use jsx here, is not supported here by nitro.
|
||||
renderToString(
|
||||
createElement(Diff, { oldSrc, newSrc, showGrid: true, height, width }, children),
|
||||
),
|
||||
).toString('utf8');
|
||||
|
||||
defaultContentType(event, 'image/svg+xml');
|
||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
|
||||
|
||||
return svg;
|
||||
});
|
||||
@@ -1,6 +1,10 @@
|
||||
import { fileURLToPath, URL } from 'node:url';
|
||||
import { defineConfig } from 'vitepress';
|
||||
import { groupIconMdPlugin, groupIconVitePlugin } from 'vitepress-plugin-group-icons'
|
||||
import container from 'markdown-it-container';
|
||||
import { renderSandbox } from 'vitepress-plugin-sandpack';
|
||||
import sidebar from './sidebar';
|
||||
import snackPlayer from './plugins/snackPlayer';
|
||||
|
||||
const title = 'Lucide';
|
||||
const socialTitle = 'Lucide Icons';
|
||||
@@ -13,6 +17,19 @@ export default defineConfig({
|
||||
cleanUrls: true,
|
||||
outDir: '.vercel/output/static',
|
||||
srcExclude: ['**/README.md'],
|
||||
markdown: {
|
||||
config(md) {
|
||||
md.use(groupIconMdPlugin);
|
||||
md.use(snackPlayer);
|
||||
md.use(container, 'sandbox', {
|
||||
render (tokens, idx) {
|
||||
console.log(tokens);
|
||||
|
||||
return renderSandbox(tokens, idx, 'sandbox');
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
vite: {
|
||||
resolve: {
|
||||
alias: [
|
||||
@@ -34,6 +51,9 @@ export default defineConfig({
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
groupIconVitePlugin()
|
||||
],
|
||||
},
|
||||
head: [
|
||||
[
|
||||
|
||||
@@ -15,10 +15,6 @@
|
||||
"name": "arrows",
|
||||
"title": "Arrows"
|
||||
},
|
||||
{
|
||||
"name": "brands",
|
||||
"title": "Brands"
|
||||
},
|
||||
{
|
||||
"name": "buildings",
|
||||
"title": "Buildings"
|
||||
|
||||
@@ -31,20 +31,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-vue-next": {
|
||||
"@lucide/vue": {
|
||||
"order": 2,
|
||||
"icon": "vue-next",
|
||||
"icon": "vue",
|
||||
"docsAlias": "lucide-vue",
|
||||
"packageDirname": "vue",
|
||||
"shields": [
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-vue-next",
|
||||
"href": "https://www.npmjs.com/package/lucide-vue-next"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-vue-next",
|
||||
"href": "https://www.npmjs.com/package/lucide-vue-next"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-svelte": {
|
||||
|
||||
@@ -457,18 +457,7 @@ const SvgPreview = React.forwardRef<
|
||||
<ColoredPath
|
||||
paths={paths}
|
||||
colors={[
|
||||
'#1982c4',
|
||||
'#4267AC',
|
||||
'#6a4c93',
|
||||
'#B55379',
|
||||
'#FF595E',
|
||||
'#FF7655',
|
||||
'#ff924c',
|
||||
'#FFAE43',
|
||||
'#ffca3a',
|
||||
'#C5CA30',
|
||||
'#8ac926',
|
||||
'#52A675',
|
||||
'##dfdfd6',
|
||||
]}
|
||||
/>
|
||||
<Radii
|
||||
|
||||
@@ -9,20 +9,14 @@ type CodeExampleType = {
|
||||
const getIconCodes = (): CodeExampleType => {
|
||||
return [
|
||||
{
|
||||
language: 'html',
|
||||
language: 'js',
|
||||
title: 'Vanilla',
|
||||
code: `\
|
||||
<script>
|
||||
import { createIcons, $CamelCase } from 'lucide';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
|
||||
createIcons({
|
||||
icons: {
|
||||
$CamelCase
|
||||
}
|
||||
});
|
||||
</script>
|
||||
createIcons({ icons });
|
||||
|
||||
<i data-lucide="$Name"></i>\
|
||||
document.body.append('<i data-lucide="$Name"></i>');\
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -43,7 +37,7 @@ export default App;
|
||||
language: 'vue',
|
||||
title: 'Vue',
|
||||
code: `<script setup>
|
||||
import { $PascalCase } from 'lucide-vue-next';
|
||||
import { $PascalCase } from '@lucide/vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -10,11 +10,10 @@ type CodeExampleType = {
|
||||
const getIconCodes = (): CodeExampleType => {
|
||||
return [
|
||||
{
|
||||
language: 'html',
|
||||
language: 'js',
|
||||
title: 'Vanilla',
|
||||
code: `\
|
||||
<script>
|
||||
import { createIcons } from 'lucide';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { $CamelCase } from '@lucide/lab';
|
||||
|
||||
createIcons({
|
||||
@@ -22,9 +21,8 @@ createIcons({
|
||||
$CamelCase
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<i data-lucide="$Name"></i>\
|
||||
document.body.append('<i data-lucide="$Name"></i>');\
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createLucideIcon } from 'lucide-react/src/lucide-react';
|
||||
import { type LucideProps, type IconNode } from 'lucide-react/src/types';
|
||||
import { type LucideProps, type IconNode } from 'lucide-react/src/createLucideIcon';
|
||||
import { IconEntity } from '../theme/types';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import { IconContent } from './generateZip';
|
||||
|
||||
110
docs/.vitepress/plugins/snackPlayer.ts
Normal file
110
docs/.vitepress/plugins/snackPlayer.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* SnackPlayer markdown-it plugin
|
||||
*
|
||||
* Converts fenced code blocks like:
|
||||
*
|
||||
* ```SnackPlayer name=My%20Example&description=Nice%20demo&ext=tsx
|
||||
* // code here
|
||||
* ```
|
||||
*
|
||||
* into:
|
||||
*
|
||||
* <div
|
||||
* class="snack-player"
|
||||
* data-snack-name="My Example"
|
||||
* ...
|
||||
* />
|
||||
*/
|
||||
|
||||
import type MarkdownIt from 'markdown-it';
|
||||
|
||||
type SnackParams = Record<string, string>;
|
||||
|
||||
function parseParams(paramString = ''): SnackParams {
|
||||
const params = Object.fromEntries(
|
||||
new URLSearchParams(paramString),
|
||||
) as SnackParams;
|
||||
|
||||
if (!params.platform) {
|
||||
params.platform = 'web';
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
export default function snackPlayerPlugin(md: MarkdownIt) {
|
||||
const escapeHtml = md.utils.escapeHtml;
|
||||
const defaultFence =
|
||||
md.renderer.rules.fence ||
|
||||
((tokens, idx, options, env, self) =>
|
||||
self.renderToken(tokens, idx, options));
|
||||
|
||||
md.renderer.rules.fence = (tokens, idx, options, env, self) => {
|
||||
const token = tokens[idx];
|
||||
const info = (token.info || '').trim();
|
||||
|
||||
if (!info) {
|
||||
return defaultFence(tokens, idx, options, env, self);
|
||||
}
|
||||
|
||||
const [lang, ...rest] = info.split(/\s+/);
|
||||
if (lang !== 'SnackPlayer') {
|
||||
return defaultFence(tokens, idx, options, env, self);
|
||||
}
|
||||
|
||||
const paramString = rest.join(' ');
|
||||
const params = parseParams(paramString);
|
||||
|
||||
// Gather necessary params
|
||||
const name = params.name
|
||||
? decodeURIComponent(params.name)
|
||||
: 'Example';
|
||||
const description = params.description
|
||||
? decodeURIComponent(params.description)
|
||||
: 'Example usage';
|
||||
const ext = params.ext ? decodeURIComponent(params.ext) : 'tsx';
|
||||
const filename = `App.${ext}`;
|
||||
|
||||
const files = encodeURIComponent(
|
||||
JSON.stringify({
|
||||
[filename]: {
|
||||
type: 'CODE',
|
||||
contents: token.content,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const dependencies =
|
||||
'react-native-safe-area-context' +
|
||||
(params.dependencies ? `,${params.dependencies}` : '');
|
||||
const platform = params.platform ?? 'web';
|
||||
const supportedPlatforms =
|
||||
params.supportedPlatforms ?? 'ios,android,web';
|
||||
const theme = params.theme ?? 'light';
|
||||
const preview = params.preview ?? 'true';
|
||||
const loading = params.loading ?? 'lazy';
|
||||
const deviceAppearance = params.deviceAppearance ?? 'dark';
|
||||
|
||||
// Build the HTML output (escaping where appropriate)
|
||||
return (
|
||||
`<SnackPlayer` +
|
||||
` class="snack-player"` +
|
||||
` data-snack-name="${escapeHtml(name)}"` +
|
||||
` data-snack-description="${escapeHtml(description)}"` +
|
||||
` data-snack-files="${files}"` +
|
||||
` data-snack-dependencies="${escapeHtml(dependencies)}"` +
|
||||
` data-snack-platform="${escapeHtml(platform)}"` +
|
||||
` data-snack-supported-platforms="${escapeHtml(
|
||||
supportedPlatforms,
|
||||
)}"` +
|
||||
// ` data-snack-theme="${escapeHtml(theme)}"` +
|
||||
` data-snack-preview="${escapeHtml(preview)}"` +
|
||||
` data-snack-loading="${escapeHtml(loading)}"` +
|
||||
` data-snack-device-appearance="${escapeHtml(
|
||||
deviceAppearance,
|
||||
)}"` +
|
||||
` data-snack-device-frame="false"` +
|
||||
`></SnackPlayer>`
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
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: 'Basics',
|
||||
items: [
|
||||
{
|
||||
text: 'Color',
|
||||
link: '/guide/basics/color',
|
||||
},
|
||||
{
|
||||
text: 'Sizing',
|
||||
link: '/guide/basics/sizing',
|
||||
},
|
||||
{
|
||||
text: 'Stroke width',
|
||||
link: '/guide/basics/stroke-width',
|
||||
},
|
||||
],
|
||||
},
|
||||
// TODO: Add this section
|
||||
{
|
||||
text: 'Advanced',
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility',
|
||||
link: '/guide/advanced/accessibility',
|
||||
},
|
||||
{
|
||||
text: 'Global styling',
|
||||
link: '/guide/advanced/global-styling',
|
||||
},
|
||||
// {
|
||||
// text: 'Animations',
|
||||
// },
|
||||
{
|
||||
text: 'Filled icons',
|
||||
link: '/guide/advanced/filled-icons',
|
||||
},
|
||||
{
|
||||
text: 'Aliased Names',
|
||||
link: '/guide/advanced/aliased-names',
|
||||
},
|
||||
// {
|
||||
// text: 'Combining icons',
|
||||
// },
|
||||
// {
|
||||
// text: 'Dynamic imports'
|
||||
// },
|
||||
// {
|
||||
// text: 'Auto importing'
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Packages',
|
||||
items: [
|
||||
{
|
||||
text: 'Lucide',
|
||||
link: '/guide/packages/lucide',
|
||||
},
|
||||
{
|
||||
text: 'Lucide React',
|
||||
link: '/guide/packages/lucide-react',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Vue',
|
||||
link: '/guide/packages/lucide-vue-next',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Svelte',
|
||||
link: '/guide/packages/lucide-svelte',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Solid',
|
||||
link: '/guide/packages/lucide-solid',
|
||||
},
|
||||
{
|
||||
text: 'Lucide React Native',
|
||||
link: '/guide/packages/lucide-react-native',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Angular',
|
||||
link: '/guide/packages/lucide-angular',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Preact',
|
||||
link: '/guide/packages/lucide-preact',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Astro',
|
||||
link: '/guide/packages/lucide-astro',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Static',
|
||||
link: '/guide/packages/lucide-static',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
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',
|
||||
},
|
||||
{
|
||||
text: 'Designing in Affinity Designer',
|
||||
link: '/guide/design/affinity-designer-guide',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
// This should be here to keep the sidebar shown on the icons page
|
||||
icons: [{ text: '', link: '/' }],
|
||||
};
|
||||
|
||||
export default sidebar;
|
||||
66
docs/.vitepress/sidebar/index.ts
Normal file
66
docs/.vitepress/sidebar/index.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { DefaultTheme, UserConfig } from 'vitepress';
|
||||
import { reactSidebar } from './react';
|
||||
import { vueSidebar } from './vue';
|
||||
import { svelteSidebar } from './svelte';
|
||||
import { lucideSidebar } from './lucide';
|
||||
import { solidSidebar } from './solid';
|
||||
import { preactSidebar } from './preact';
|
||||
import { reactNativeSidebar } from './react-native';
|
||||
|
||||
type Sidebar = UserConfig<DefaultTheme.Config>['themeConfig']['sidebar']
|
||||
|
||||
export const guideSidebarTop: DefaultTheme.SidebarItem[] = [
|
||||
{
|
||||
text: 'Introduction',
|
||||
items: [
|
||||
{ text: 'What is lucide?', link: '/guide/' },
|
||||
{ text: 'Installation', link: '/guide/installation' },
|
||||
{ text: 'Comparison', link: '/guide/comparison' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const sidebar: Sidebar = {
|
||||
'/guide': [{ text: '', link: '/' }],
|
||||
'/guide/lucide': lucideSidebar,
|
||||
'/guide/react': reactSidebar,
|
||||
'/guide/vue': vueSidebar,
|
||||
'/guide/svelte': svelteSidebar,
|
||||
'/guide/solid': solidSidebar,
|
||||
'/guide/preact': preactSidebar,
|
||||
'/guide/react-native/': reactNativeSidebar,
|
||||
'/resources': [
|
||||
{
|
||||
text: "Community",
|
||||
},
|
||||
{
|
||||
text: 'Designing icons',
|
||||
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',
|
||||
},
|
||||
{
|
||||
text: 'Designing in Affinity Designer',
|
||||
link: '/guide/design/affinity-designer-guide',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
// This should be here to keep the sidebar shown on the icons page
|
||||
icons: [{ text: '', link: '/' }],
|
||||
};
|
||||
|
||||
export default sidebar;
|
||||
68
docs/.vitepress/sidebar/lucide.ts
Normal file
68
docs/.vitepress/sidebar/lucide.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { DefaultTheme } from "vitepress";
|
||||
|
||||
export const lucideSidebar = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
text: 'Overview',
|
||||
link: '/guide/lucide/',
|
||||
},
|
||||
{
|
||||
text: 'Getting started',
|
||||
link: '/guide/lucide/getting-started',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Basics',
|
||||
items: [
|
||||
{
|
||||
text: 'Color',
|
||||
desc: 'Adjust the color of your icons',
|
||||
link: '/guide/lucide/basics/color',
|
||||
},
|
||||
{
|
||||
text: 'Sizing',
|
||||
desc: 'Adjust the size of your icons',
|
||||
link: '/guide/lucide/basics/sizing',
|
||||
},
|
||||
{
|
||||
text: 'Stroke width',
|
||||
desc: 'Adjust the stroke width of your icons',
|
||||
link: '/guide/lucide/basics/stroke-width',
|
||||
},
|
||||
],
|
||||
},
|
||||
// TODO: Add this section
|
||||
{
|
||||
text: 'Advanced',
|
||||
items: [
|
||||
{
|
||||
text: 'Typescript',
|
||||
link: '/guide/lucide/advanced/typescript',
|
||||
desc: 'All exported types and how to use them',
|
||||
},
|
||||
{
|
||||
text: 'With shadow DOM',
|
||||
link: '/guide/lucide/advanced/shadow-dom',
|
||||
desc: 'All exported types and how to use them',
|
||||
},
|
||||
{
|
||||
text: 'Accessibility',
|
||||
link: '/guide/lucide/advanced/accessibility',
|
||||
desc: 'Making your icons accessible',
|
||||
},
|
||||
{
|
||||
text: 'With lucide lab',
|
||||
link: '/guide/lucide/advanced/with-lucide-lab',
|
||||
desc: 'Using lucide-lab with lucide',
|
||||
},
|
||||
{
|
||||
text: 'Filled icons',
|
||||
link: '/guide/lucide/advanced/filled-icons',
|
||||
desc: 'Using filled icons in lucide',
|
||||
},
|
||||
],
|
||||
},
|
||||
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];
|
||||
|
||||
97
docs/.vitepress/sidebar/preact.ts
Normal file
97
docs/.vitepress/sidebar/preact.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { DefaultTheme } from "vitepress";
|
||||
|
||||
export const preactSidebar = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
text: 'Overview',
|
||||
link: '/guide/preact/',
|
||||
},
|
||||
{
|
||||
text: 'Getting started',
|
||||
link: '/guide/preact/getting-started',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Basics',
|
||||
items: [
|
||||
{
|
||||
text: 'Color',
|
||||
desc: 'Adjust the color of your icons',
|
||||
link: '/guide/preact/basics/color',
|
||||
},
|
||||
{
|
||||
text: 'Sizing',
|
||||
desc: 'Adjust the size of your icons',
|
||||
link: '/guide/preact/basics/sizing',
|
||||
},
|
||||
{
|
||||
text: 'Stroke width',
|
||||
desc: 'Adjust the stroke width of your icons',
|
||||
link: '/guide/preact/basics/stroke-width',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Advanced',
|
||||
items: [
|
||||
{
|
||||
text: 'Typescript',
|
||||
link: '/guide/preact/advanced/typescript',
|
||||
desc: 'All exported types and how to use them',
|
||||
},
|
||||
{
|
||||
text: 'Accessibility',
|
||||
link: '/guide/preact/advanced/accessibility',
|
||||
desc: 'Making your icons accessible',
|
||||
},
|
||||
{
|
||||
text: 'Global styling',
|
||||
link: '/guide/preact/advanced/global-styling',
|
||||
desc: 'Apply global styles to all icons',
|
||||
},
|
||||
{
|
||||
text: 'With lucide lab',
|
||||
link: '/guide/preact/advanced/with-lucide-lab',
|
||||
desc: 'Using lucide-lab with lucide-preact',
|
||||
},
|
||||
// {
|
||||
// text: 'Animations',
|
||||
// link: '/guide/preact/advanced/animations',
|
||||
// desc: 'Add animations to your icons',
|
||||
// },
|
||||
{
|
||||
text: 'Filled icons',
|
||||
link: '/guide/preact/advanced/filled-icons',
|
||||
desc: 'Using filled icons in lucide-preact',
|
||||
},
|
||||
{
|
||||
text: 'Aliased Names',
|
||||
link: '/guide/preact/advanced/aliased-names',
|
||||
desc: 'Using aliased icon names',
|
||||
},
|
||||
{
|
||||
text: 'Combining icons',
|
||||
link: '/guide/preact/advanced/combining-icons',
|
||||
desc: 'Combine multiple icons into one',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Resources',
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
text: 'VSCode',
|
||||
link: '/guide/vscode',
|
||||
desc: 'VSCode and Lucide',
|
||||
},
|
||||
],
|
||||
}
|
||||
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];
|
||||
|
||||
82
docs/.vitepress/sidebar/react-native.ts
Normal file
82
docs/.vitepress/sidebar/react-native.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { DefaultTheme } from "vitepress";
|
||||
|
||||
export const reactNativeSidebar = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
text: 'Overview',
|
||||
link: '/guide/react-native/',
|
||||
},
|
||||
{
|
||||
text: 'Getting started',
|
||||
link: '/guide/react-native/getting-started',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Basics',
|
||||
items: [
|
||||
{
|
||||
text: 'Color',
|
||||
desc: 'Adjust the color of your icons',
|
||||
link: '/guide/react-native/basics/color',
|
||||
},
|
||||
{
|
||||
text: 'Sizing',
|
||||
desc: 'Adjust the size of your icons',
|
||||
link: '/guide/react-native/basics/sizing',
|
||||
},
|
||||
{
|
||||
text: 'Stroke width',
|
||||
desc: 'Adjust the stroke width of your icons',
|
||||
link: '/guide/react-native/basics/stroke-width',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Advanced',
|
||||
items: [
|
||||
{
|
||||
text: 'Typescript',
|
||||
link: '/guide/react-native/advanced/typescript',
|
||||
desc: 'All exported types and how to use them',
|
||||
},
|
||||
{
|
||||
text: 'Global styling',
|
||||
link: '/guide/react-native/advanced/global-styling',
|
||||
desc: 'Apply global styles to all icons',
|
||||
},
|
||||
{
|
||||
text: 'With lucide lab',
|
||||
link: '/guide/react-native/advanced/with-lucide-lab',
|
||||
desc: 'Using lucide-lab with lucide-react-native',
|
||||
},
|
||||
{
|
||||
text: 'Filled icons',
|
||||
link: '/guide/react-native/advanced/filled-icons',
|
||||
desc: 'Using filled icons in lucide-react-native',
|
||||
},
|
||||
{
|
||||
text: 'Aliased Names',
|
||||
link: '/guide/react-native/advanced/aliased-names',
|
||||
desc: 'Using aliased icon names',
|
||||
},
|
||||
{
|
||||
text: 'Combining icons',
|
||||
link: '/guide/react-native/advanced/combining-icons',
|
||||
desc: 'Combine multiple icons into one',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Resources',
|
||||
items: [
|
||||
{
|
||||
text: 'VSCode',
|
||||
link: '/guide/vscode',
|
||||
desc: 'VSCode and Lucide',
|
||||
},
|
||||
],
|
||||
}
|
||||
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];
|
||||
|
||||
103
docs/.vitepress/sidebar/react.ts
Normal file
103
docs/.vitepress/sidebar/react.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { DefaultTheme } from "vitepress";
|
||||
|
||||
export const reactSidebar = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
text: 'Overview',
|
||||
link: '/guide/react/',
|
||||
},
|
||||
{
|
||||
text: 'Getting started',
|
||||
link: '/guide/react/getting-started',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Basics',
|
||||
items: [
|
||||
{
|
||||
text: 'Color',
|
||||
desc: 'Adjust the color of your icons',
|
||||
link: '/guide/react/basics/color',
|
||||
},
|
||||
{
|
||||
text: 'Sizing',
|
||||
desc: 'Adjust the size of your icons',
|
||||
link: '/guide/react/basics/sizing',
|
||||
},
|
||||
{
|
||||
text: 'Stroke width',
|
||||
desc: 'Adjust the stroke width of your icons',
|
||||
link: '/guide/react/basics/stroke-width',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Advanced',
|
||||
items: [
|
||||
{
|
||||
text: 'Typescript',
|
||||
link: '/guide/react/advanced/typescript',
|
||||
desc: 'All exported types and how to use them',
|
||||
},
|
||||
{
|
||||
text: 'Accessibility',
|
||||
link: '/guide/react/advanced/accessibility',
|
||||
desc: 'Making your icons accessible',
|
||||
},
|
||||
{
|
||||
text: 'Global styling',
|
||||
link: '/guide/react/advanced/global-styling',
|
||||
desc: 'Apply global styles to all icons',
|
||||
},
|
||||
{
|
||||
text: 'With lucide lab',
|
||||
link: '/guide/react/advanced/with-lucide-lab',
|
||||
desc: 'Using lucide-lab with lucide-react',
|
||||
},
|
||||
// {
|
||||
// text: 'Animations',
|
||||
// link: '/guide/react/advanced/animations',
|
||||
// desc: 'Add animations to your icons',
|
||||
// },
|
||||
{
|
||||
text: 'Filled icons',
|
||||
link: '/guide/react/advanced/filled-icons',
|
||||
desc: 'Using filled icons in lucide-react',
|
||||
},
|
||||
{
|
||||
text: 'Aliased Names',
|
||||
link: '/guide/react/advanced/aliased-names',
|
||||
desc: 'Using aliased icon names',
|
||||
},
|
||||
|
||||
{
|
||||
text: 'Combining icons',
|
||||
link: '/guide/react/advanced/combining-icons',
|
||||
desc: 'Combine multiple icons into one',
|
||||
},
|
||||
{
|
||||
text: 'Dynamic icon component',
|
||||
link: '/guide/react/advanced/dynamic-icon-component.md',
|
||||
desc: 'Dynamically import icons as needed',
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Resources',
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
text: 'VSCode',
|
||||
link: '/guide/vscode',
|
||||
desc: 'VSCode and Lucide',
|
||||
},
|
||||
],
|
||||
}
|
||||
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];
|
||||
|
||||
98
docs/.vitepress/sidebar/solid.ts
Normal file
98
docs/.vitepress/sidebar/solid.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { DefaultTheme } from "vitepress";
|
||||
|
||||
export const solidSidebar = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
text: 'Overview',
|
||||
link: '/guide/solid/',
|
||||
},
|
||||
{
|
||||
text: 'Getting started',
|
||||
link: '/guide/solid/getting-started',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Basics',
|
||||
items: [
|
||||
{
|
||||
text: 'Color',
|
||||
desc: 'Adjust the color of your icons',
|
||||
link: '/guide/solid/basics/color',
|
||||
},
|
||||
{
|
||||
text: 'Sizing',
|
||||
desc: 'Adjust the size of your icons',
|
||||
link: '/guide/solid/basics/sizing',
|
||||
},
|
||||
{
|
||||
text: 'Stroke width',
|
||||
desc: 'Adjust the stroke width of your icons',
|
||||
link: '/guide/solid/basics/stroke-width',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Advanced',
|
||||
items: [
|
||||
{
|
||||
text: 'Typescript',
|
||||
link: '/guide/solid/advanced/typescript',
|
||||
desc: 'All exported types and how to use them',
|
||||
},
|
||||
{
|
||||
text: 'Accessibility',
|
||||
link: '/guide/solid/advanced/accessibility',
|
||||
desc: 'Making your icons accessible',
|
||||
},
|
||||
{
|
||||
text: 'Global styling',
|
||||
link: '/guide/solid/advanced/global-styling',
|
||||
desc: 'Apply global styles to all icons',
|
||||
},
|
||||
{
|
||||
text: 'With lucide lab',
|
||||
link: '/guide/solid/advanced/with-lucide-lab',
|
||||
desc: 'Using lucide-lab with lucide-solid',
|
||||
},
|
||||
// {
|
||||
// text: 'Animations',
|
||||
// link: '/guide/solid/advanced/animations',
|
||||
// desc: 'Add animations to your icons',
|
||||
// },
|
||||
{
|
||||
text: 'Filled icons',
|
||||
link: '/guide/solid/advanced/filled-icons',
|
||||
desc: 'Using filled icons in lucide-solid',
|
||||
},
|
||||
{
|
||||
text: 'Aliased Names',
|
||||
link: '/guide/solid/advanced/aliased-names',
|
||||
desc: 'Using aliased icon names',
|
||||
},
|
||||
|
||||
{
|
||||
text: 'Combining icons',
|
||||
link: '/guide/solid/advanced/combining-icons',
|
||||
desc: 'Combine multiple icons into one',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Resources',
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
text: 'VSCode',
|
||||
link: '/guide/vscode',
|
||||
desc: 'VSCode and Lucide',
|
||||
},
|
||||
],
|
||||
}
|
||||
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];
|
||||
|
||||
87
docs/.vitepress/sidebar/svelte.ts
Normal file
87
docs/.vitepress/sidebar/svelte.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { DefaultTheme } from "vitepress";
|
||||
|
||||
export const svelteSidebar = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
text: 'Overview',
|
||||
link: '/guide/svelte/',
|
||||
},
|
||||
{
|
||||
text: 'Getting started',
|
||||
link: '/guide/svelte/getting-started',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Basics',
|
||||
items: [
|
||||
{
|
||||
text: 'Color',
|
||||
desc: 'Adjust the color of your icons',
|
||||
link: '/guide/svelte/basics/color',
|
||||
},
|
||||
{
|
||||
text: 'Sizing',
|
||||
desc: 'Adjust the size of your icons',
|
||||
link: '/guide/svelte/basics/sizing',
|
||||
},
|
||||
{
|
||||
text: 'Stroke width',
|
||||
desc: 'Adjust the stroke width of your icons',
|
||||
link: '/guide/svelte/basics/stroke-width',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Advanced',
|
||||
items: [
|
||||
{
|
||||
text: 'Typescript',
|
||||
link: '/guide/svelte/advanced/typescript',
|
||||
desc: 'All exported types and how to use them',
|
||||
},
|
||||
{
|
||||
text: 'Accessibility',
|
||||
link: '/guide/svelte/advanced/accessibility',
|
||||
desc: 'Making your icons accessible',
|
||||
},
|
||||
{
|
||||
text: 'Global styling',
|
||||
link: '/guide/svelte/advanced/global-styling',
|
||||
desc: 'Apply global styles to all icons',
|
||||
},
|
||||
{
|
||||
text: 'With lucide lab',
|
||||
link: '/guide/svelte/advanced/with-lucide-lab',
|
||||
desc: 'Using lucide-lab with @lucide/svelte',
|
||||
},
|
||||
{
|
||||
text: 'Filled icons',
|
||||
link: '/guide/svelte/advanced/filled-icons',
|
||||
desc: 'Using filled icons in @lucide/svelte',
|
||||
},
|
||||
{
|
||||
text: 'Combining icons',
|
||||
link: '/guide/svelte/advanced/combining-icons',
|
||||
desc: 'Combine multiple icons into one',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Resources',
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
text: 'VSCode',
|
||||
link: '/guide/vscode',
|
||||
desc: 'VSCode and Lucide',
|
||||
},
|
||||
],
|
||||
}
|
||||
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];
|
||||
|
||||
92
docs/.vitepress/sidebar/vue.ts
Normal file
92
docs/.vitepress/sidebar/vue.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { DefaultTheme } from "vitepress";
|
||||
|
||||
export const vueSidebar = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
text: 'Overview',
|
||||
link: '/guide/vue/',
|
||||
},
|
||||
{
|
||||
text: 'Getting started',
|
||||
link: '/guide/vue/getting-started',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Basics',
|
||||
items: [
|
||||
{
|
||||
text: 'Color',
|
||||
desc: 'Adjust the color of your icons',
|
||||
link: '/guide/vue/basics/color',
|
||||
},
|
||||
{
|
||||
text: 'Sizing',
|
||||
desc: 'Adjust the size of your icons',
|
||||
link: '/guide/vue/basics/sizing',
|
||||
},
|
||||
{
|
||||
text: 'Stroke width',
|
||||
desc: 'Adjust the stroke width of your icons',
|
||||
link: '/guide/vue/basics/stroke-width',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Advanced',
|
||||
items: [
|
||||
{
|
||||
text: 'Typescript',
|
||||
link: '/guide/vue/advanced/typescript',
|
||||
desc: 'All exported types and how to use them',
|
||||
},
|
||||
{
|
||||
text: 'Accessibility',
|
||||
link: '/guide/vue/advanced/accessibility',
|
||||
desc: 'Making your icons accessible',
|
||||
},
|
||||
{
|
||||
text: 'Global styling',
|
||||
link: '/guide/vue/advanced/global-styling',
|
||||
desc: 'Apply global styles to all icons',
|
||||
},
|
||||
{
|
||||
text: 'With lucide lab',
|
||||
link: '/guide/vue/advanced/with-lucide-lab',
|
||||
desc: 'Using lucide-lab with @lucide/vue',
|
||||
},
|
||||
{
|
||||
text: 'Filled icons',
|
||||
link: '/guide/vue/advanced/filled-icons',
|
||||
desc: 'Using filled icons in @lucide/vue',
|
||||
},
|
||||
{
|
||||
text: 'Aliased Names',
|
||||
link: '/guide/vue/advanced/aliased-names',
|
||||
desc: 'Using aliased icon names',
|
||||
},
|
||||
|
||||
{
|
||||
text: 'Combining icons',
|
||||
link: '/guide/vue/advanced/combining-icons',
|
||||
desc: 'Combine multiple icons into one',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Resources',
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
text: 'VSCode',
|
||||
link: '/guide/vscode',
|
||||
desc: 'VSCode and Lucide',
|
||||
},
|
||||
],
|
||||
}
|
||||
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];
|
||||
@@ -1,31 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { useData } from 'vitepress';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string;
|
||||
id: string;
|
||||
}>();
|
||||
modelValue: string
|
||||
id: string
|
||||
}>()
|
||||
|
||||
const { isDark } = useData();
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const value = computed({
|
||||
get: () => {
|
||||
if (props.modelValue == null || props.modelValue === 'currentColor') {
|
||||
return isDark.value ? '#ffffff' : '#000000';
|
||||
}
|
||||
|
||||
return props.modelValue;
|
||||
},
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
});
|
||||
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"
|
||||
@@ -41,7 +33,6 @@ const value = computed({
|
||||
class="color-input-text"
|
||||
aria-label="Color picker input"
|
||||
v-model="value"
|
||||
placeholder="[default]"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -54,21 +45,19 @@ const value = computed({
|
||||
top: -5px;
|
||||
left: -5px;
|
||||
}
|
||||
|
||||
.color-input-wrapper {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
border-radius: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.color-picker {
|
||||
background: var(--color-picker-bg, var(--vp-c-bg-alt));
|
||||
border-radius: 8px;
|
||||
color: var(--vp-c-text-2);
|
||||
padding: 3px 8px 3px 3px;
|
||||
padding: 4px 8px;
|
||||
height: auto;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
@@ -77,10 +66,6 @@ const value = computed({
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
border-color 0.25s,
|
||||
background-color 0.25s;
|
||||
}
|
||||
|
||||
.color-input-text {
|
||||
@@ -94,18 +79,15 @@ const value = computed({
|
||||
text-align: left;
|
||||
border-radius: 8px;
|
||||
cursor: text;
|
||||
transition:
|
||||
border-color 0.25s,
|
||||
background 0.4s ease;
|
||||
letter-spacing: 1px;
|
||||
transition: border-color 0.25s, background 0.4s ease;
|
||||
}
|
||||
|
||||
.color-picker:hover,
|
||||
.color-picker:focus {
|
||||
.color-picker:hover, .color-picker:focus {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
|
||||
.color-input[value='currentColor'] {
|
||||
.color-input[value="currentColor"] {
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,27 +1,22 @@
|
||||
<script setup>
|
||||
import Icon from 'lucide-vue-next/src/Icon';
|
||||
import { search } from '../../../data/iconNodes';
|
||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
||||
import { search } from '../../../data/iconNodes'
|
||||
|
||||
const SearchIcon = createLucideIcon('search', search)
|
||||
|
||||
defineProps({
|
||||
shortcut: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
required: false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button class="fake-input">
|
||||
<Icon
|
||||
:iconNode="search"
|
||||
class="search-icon"
|
||||
/>
|
||||
<slot />
|
||||
<kbd
|
||||
v-if="shortcut"
|
||||
class="shortcut"
|
||||
>{{ shortcut }}</kbd
|
||||
>
|
||||
<component :is="SearchIcon" class="search-icon"/>
|
||||
<slot/>
|
||||
<kbd v-if="shortcut" class="shortcut">{{ shortcut }}</kbd>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -39,14 +34,10 @@ defineProps({
|
||||
cursor: text;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
border-color 0.25s,
|
||||
background-color 0.25s;
|
||||
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
|
||||
}
|
||||
|
||||
.fake-input:hover,
|
||||
.fake-input:focus {
|
||||
.fake-input:hover, .fake-input:focus {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.icon-button {
|
||||
display: inline-flex;
|
||||
border: 1px solid transparent;
|
||||
@@ -29,9 +30,9 @@
|
||||
}
|
||||
|
||||
.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);
|
||||
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 {
|
||||
|
||||
@@ -1,90 +1,60 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
};
|
||||
}
|
||||
|
||||
export interface InputProps {
|
||||
type: string;
|
||||
modelValue: string;
|
||||
shortcut?: string;
|
||||
type: string
|
||||
modelValue: string
|
||||
shortcut?: string
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, nextTick, watch } from 'vue';
|
||||
import Icon from 'lucide-vue-next/src/Icon';
|
||||
import { x } from '../../../data/iconNodes';
|
||||
import IconButton from './IconButton.vue';
|
||||
import { ref, onMounted, nextTick, watch } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<InputProps>(), {
|
||||
type: 'text',
|
||||
});
|
||||
type: 'text'
|
||||
})
|
||||
|
||||
const input = ref();
|
||||
const wrapperEl = ref();
|
||||
const shortcutEl = ref();
|
||||
const input = ref()
|
||||
const wrapperEl = ref()
|
||||
const shortcutEl = ref()
|
||||
|
||||
const emit = defineEmits(['change', 'input', 'update:modelValue']);
|
||||
defineEmits(['change', 'input', 'update:modelValue'])
|
||||
|
||||
const updateShortcutSpacing = () => {
|
||||
nextTick(() => {
|
||||
if (shortcutEl.value && wrapperEl.value) {
|
||||
const shortcutWidth = shortcutEl.value.offsetWidth;
|
||||
wrapperEl.value.style.setProperty('--shortcut-width', `${shortcutWidth}px`);
|
||||
const shortcutWidth = shortcutEl.value.offsetWidth
|
||||
wrapperEl.value.style.setProperty('--shortcut-width', `${shortcutWidth}px`)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(updateShortcutSpacing);
|
||||
watch(() => props.shortcut, updateShortcutSpacing);
|
||||
|
||||
function onClear() {
|
||||
emit('update:modelValue', '');
|
||||
input.value.focus();
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(updateShortcutSpacing)
|
||||
watch(() => props.shortcut, updateShortcutSpacing)
|
||||
|
||||
defineExpose({
|
||||
focus: () => {
|
||||
input.value.focus();
|
||||
},
|
||||
});
|
||||
input.value.focus()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="input-wrapper"
|
||||
ref="wrapperEl"
|
||||
>
|
||||
<slot
|
||||
name="icon"
|
||||
class="icon"
|
||||
/>
|
||||
<div class="input-wrapper" ref="wrapperEl">
|
||||
<slot name="icon" class="icon" />
|
||||
<input
|
||||
:type="type"
|
||||
class="input"
|
||||
:class="{ 'has-icon': $slots.icon, 'has-shortcut': shortcut }"
|
||||
:class="{'has-icon': $slots.icon, 'has-shortcut': shortcut}"
|
||||
ref="input"
|
||||
:value="modelValue"
|
||||
v-bind="$attrs"
|
||||
@input="$emit('update:modelValue', $event.target.value)"
|
||||
/>
|
||||
<IconButton
|
||||
@click="onClear"
|
||||
v-if="type === 'search' && modelValue"
|
||||
class="clear-button"
|
||||
aria-label="Clear input"
|
||||
>
|
||||
<Icon
|
||||
:iconNode="x"
|
||||
:size="20"
|
||||
/>
|
||||
</IconButton>
|
||||
<kbd
|
||||
v-if="shortcut"
|
||||
class="shortcut"
|
||||
ref="shortcutEl"
|
||||
>{{ shortcut }}</kbd
|
||||
>
|
||||
<kbd v-if="shortcut" class="shortcut" ref="shortcutEl">{{ shortcut }}</kbd>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -92,7 +62,6 @@ defineExpose({
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.input {
|
||||
justify-content: flex-start;
|
||||
border: 1px solid transparent;
|
||||
@@ -102,18 +71,13 @@ defineExpose({
|
||||
height: 40px;
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
font-size: 14px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
border-color 0.25s,
|
||||
background-color 0.25s;
|
||||
}
|
||||
|
||||
.input.has-shortcut {
|
||||
padding-right: calc(var(--shortcut-width, 40px) + 22px);
|
||||
}
|
||||
|
||||
.input:hover,
|
||||
.input:focus {
|
||||
.input:hover, .input:focus {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
@@ -122,14 +86,6 @@ defineExpose({
|
||||
padding-left: 52px;
|
||||
}
|
||||
|
||||
.clear-button {
|
||||
position: absolute;
|
||||
right: 56px;
|
||||
top: 9px;
|
||||
padding: 4px;
|
||||
transition: background-color .25s;
|
||||
}
|
||||
|
||||
.shortcut {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
@@ -155,7 +111,7 @@ defineExpose({
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.input-wrapper > svg {
|
||||
.input-wrapper svg {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 12px;
|
||||
|
||||
@@ -1,36 +1,38 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import Input from './Input.vue';
|
||||
import Icon from 'lucide-vue-next/src/Icon';
|
||||
import { search } from '../../../data/iconNodes';
|
||||
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;
|
||||
shortcut?: string;
|
||||
modelValue: string
|
||||
shortcut?: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const input = ref();
|
||||
const input = ref()
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
defineExpose({
|
||||
focus: () => {
|
||||
input.value.focus();
|
||||
},
|
||||
});
|
||||
input.value.focus()
|
||||
}
|
||||
})
|
||||
|
||||
const value = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
});
|
||||
set: (val) => emit('update:modelValue', val)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -44,10 +46,7 @@ const value = computed({
|
||||
class="input-wrapper"
|
||||
>
|
||||
<template #icon>
|
||||
<Icon
|
||||
:iconNode="search"
|
||||
class="search-icon"
|
||||
/>
|
||||
<component :is="SearchIcon" class="search-icon" />
|
||||
</template>
|
||||
</Input>
|
||||
</template>
|
||||
@@ -63,8 +62,7 @@ const value = computed({
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
}
|
||||
|
||||
.input:hover,
|
||||
.input:focus {
|
||||
.input:hover, .input:focus {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ export type IconNode = [elementName: string, attrs: Record<string, string>][]
|
||||
|
||||
const props = defineProps<{
|
||||
name: string;
|
||||
tags: string[];
|
||||
categories: string[];
|
||||
tags?: string[];
|
||||
categories?: string[];
|
||||
// contributors: Contributor[];
|
||||
iconNode: IconNode;
|
||||
}>()
|
||||
|
||||
61
docs/.vitepress/theme/components/base/OverviewLink.vue
Normal file
61
docs/.vitepress/theme/components/base/OverviewLink.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<script setup lang="ts">
|
||||
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
|
||||
|
||||
defineProps<{
|
||||
href?: string
|
||||
title?: string
|
||||
desc?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VPLink
|
||||
class="overview-link"
|
||||
:href="href"
|
||||
:aria-label="`${title} - ${desc}`"
|
||||
>
|
||||
<span class="title">
|
||||
{{ title }}
|
||||
</span>
|
||||
<span
|
||||
class="desc">
|
||||
{{ desc }}
|
||||
</span>
|
||||
</VPLink>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.overview-link {
|
||||
display: block;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 11px 16px 13px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: border-color 0.25s;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.overview-link:hover {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.desc {
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.title {
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-brand-1);
|
||||
transition: color 0.25s;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
</style>
|
||||
31
docs/.vitepress/theme/components/base/OverviewLinkGrid.vue
Normal file
31
docs/.vitepress/theme/components/base/OverviewLinkGrid.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div class="overview-link-grid">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.overview-link-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.overview-link-grid > * {
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.overview-link-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.overview-link-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,15 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { rotateCw } from '../../../data/iconNodes';
|
||||
import Icon from 'lucide-vue-next/src/Icon';
|
||||
import IconButton from './IconButton.vue';
|
||||
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">
|
||||
<Icon
|
||||
:size="20"
|
||||
:iconNode="rotateCw"
|
||||
/>
|
||||
<RotateIcon :size="20"/>
|
||||
</IconButton>
|
||||
</template>
|
||||
|
||||
@@ -33,7 +32,6 @@ import IconButton from './IconButton.vue';
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
|
||||
226
docs/.vitepress/theme/components/base/Select.vue
Normal file
226
docs/.vitepress/theme/components/base/Select.vue
Normal file
@@ -0,0 +1,226 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import {
|
||||
Listbox,
|
||||
ListboxLabel,
|
||||
ListboxButton,
|
||||
ListboxOptions,
|
||||
ListboxOption,
|
||||
} from '@headlessui/vue'
|
||||
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-vue-next'
|
||||
|
||||
|
||||
defineProps<{
|
||||
id?: string
|
||||
items?: { name: string, icon: string }[]
|
||||
}>()
|
||||
|
||||
const selected = defineModel<{ name: string, icon: string }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Listbox v-model="selected">
|
||||
<div class="select-wrapper">
|
||||
<ListboxButton class="select-button" :id="id">
|
||||
<img
|
||||
:src="selected.icon"
|
||||
:class="{ 'select-item-icon': true }"
|
||||
:alt="`${selected.name} logo`"
|
||||
loading="lazy"
|
||||
/>
|
||||
<span class="select-text">{{ selected.name }}</span>
|
||||
<span class="select-icon">
|
||||
<ChevronsUpDownIcon class="chevron-icon" aria-hidden="true" />
|
||||
</span>
|
||||
</ListboxButton>
|
||||
|
||||
<transition
|
||||
leave-active-class="transition-leave"
|
||||
leave-from-class="transition-leave-from"
|
||||
leave-to-class="transition-leave-to"
|
||||
>
|
||||
<ListboxOptions class="select-options">
|
||||
<ListboxOption
|
||||
v-slot="{ active, selected }"
|
||||
v-for="item in items"
|
||||
:key="item.name"
|
||||
:value="item"
|
||||
as="template"
|
||||
>
|
||||
<li :class="['select-option', { active, selected }]">
|
||||
<img
|
||||
:src="item.icon"
|
||||
:class="{ 'select-item-icon': true }"
|
||||
:alt="`${item.name} logo`"
|
||||
loading="lazy"
|
||||
/>
|
||||
<span :class="['option-text', { selected }]">{{ item.name }}</span>
|
||||
<span v-if="selected" class="check-icon">
|
||||
<CheckIcon class="check" aria-hidden="true" />
|
||||
</span>
|
||||
</li>
|
||||
</ListboxOption>
|
||||
</ListboxOptions>
|
||||
</transition>
|
||||
</div>
|
||||
</Listbox>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.select-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.select-button {
|
||||
background: var(--vp-sidebar-input);
|
||||
border-radius: 8px;
|
||||
color: var(--vp-c-text-1);
|
||||
padding: 7px 14px;
|
||||
height: auto;
|
||||
font-size: 14px;
|
||||
border: 1px solid transparent;
|
||||
cursor: text;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.select-button:focus {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.select-text {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
}
|
||||
|
||||
.select-icon {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.select-item-icon {
|
||||
object-fit: contain;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.chevron-icon {
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.transition-leave {
|
||||
transition: opacity 100ms ease-in;
|
||||
}
|
||||
|
||||
.transition-leave-from {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.transition-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.select-options {
|
||||
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-elv);
|
||||
box-shadow: var(--vp-shadow-3);
|
||||
transition: background-color 0.5s;
|
||||
max-height: calc(100vh - var(--vp-nav-height));
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
z-index: 90;
|
||||
right: 0;
|
||||
top: 44px;
|
||||
}
|
||||
|
||||
.select-option {
|
||||
position: relative;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
padding: 0px 4px;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
border-radius: 6px;
|
||||
line-height: 32px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-1);
|
||||
white-space: nowrap;
|
||||
transition: background-color .25s,color .25s;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.select-option:hover, .select-option.active {
|
||||
color: var(--vp-c-brand);
|
||||
background-color: var(--vp-c-default-soft);
|
||||
}
|
||||
|
||||
.select-option:active {
|
||||
color: var(--vp-c-brand);
|
||||
background-color: var(--vp-c-bg-elv);
|
||||
}
|
||||
|
||||
.option-text {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.option-text.selected {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 0.75rem;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.check {
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.select-button,
|
||||
.select-options {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
136
docs/.vitepress/theme/components/editors/Sandbox.vue
Normal file
136
docs/.vitepress/theme/components/editors/Sandbox.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<script setup lang="ts">
|
||||
import { getSandpackFiles, getCustomSetupFromProps, parsedBoolean, getSandpackOptions } from 'vitepress-plugin-sandpack';
|
||||
import { Sandpack, type SandpackFiles } from 'sandpack-vue3';
|
||||
import { computed, nextTick, onBeforeMount, onMounted, ref, useSlots, watch } from 'vue';
|
||||
import styles from './styles.css?raw'
|
||||
|
||||
import sandpackTheme from '../../sandpackTheme.json'
|
||||
import { sandboxProps } from 'vitepress-plugin-sandpack';
|
||||
|
||||
const props = defineProps({
|
||||
...sandboxProps,
|
||||
editorHeight: {
|
||||
type: [String, Number],
|
||||
default: undefined
|
||||
},
|
||||
editorWidthPercentage: {
|
||||
type: [String, Number],
|
||||
default: undefined
|
||||
},
|
||||
dependencies: {
|
||||
type: String,
|
||||
default: undefined
|
||||
}
|
||||
});
|
||||
|
||||
const files = ref<SandpackFiles>({});
|
||||
|
||||
const getOpt = (propName: string) => props[propName] ?? props?.options?.[propName];
|
||||
|
||||
const editorVisible = computed(() => parsedBoolean(getOpt('hideEditor')) ? 'none' : 'flex');
|
||||
|
||||
const previewHeight = computed(() => isNaN(Number(getOpt('previewHeight'))) ? undefined : Number(getOpt('previewHeight')));
|
||||
const previewHeightStyle =
|
||||
computed(() => previewHeight.value ? `${previewHeight.value}px` : 'var(--sp-layout-height)');
|
||||
|
||||
const coderHeight = computed(() => isNaN(Number(getOpt('coderHeight'))) ? undefined : Number(getOpt('coderHeight')));
|
||||
const coderHeightStyle =
|
||||
computed(() => coderHeight.value ? `${coderHeight.value}px` : 'var(--sp-layout-height)');
|
||||
|
||||
const slots = useSlots();
|
||||
const isDark = ref(false);
|
||||
|
||||
const resolveFiles = async () => {
|
||||
files.value = {
|
||||
...await getSandpackFiles(props, slots),
|
||||
'styles.css': {
|
||||
code: styles,
|
||||
hidden: true
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
watch(props, resolveFiles);
|
||||
|
||||
onBeforeMount(resolveFiles);
|
||||
|
||||
const dependencies = computed(() => {
|
||||
if (props.dependencies) {
|
||||
return props.dependencies.split(',').reduce((acc, dep) => {
|
||||
const [name, version] = dep.split(':').map(s => s.trim());
|
||||
acc[name] = version || 'latest';
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Sandpack :theme="sandpackTheme" :template="template" :rtl="parsedBoolean(rtl)" :files="files" :options="{
|
||||
...(getSandpackOptions(props) as any),
|
||||
editorWidthPercentage: getOpt('editorWidthPercentage') ? Number(getOpt('editorWidthPercentage')) : undefined,
|
||||
showConsoleButton: false,
|
||||
}" :customSetup='{
|
||||
dependencies: dependencies
|
||||
}' />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.sp-wrapper+* {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.sp-wrapper .sp-layout {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.sp-wrapper .sp-tabs-scrollable-container {
|
||||
border-radius: 8px 8px 0 0;
|
||||
position: relative;
|
||||
|
||||
box-shadow: inset 0 -1px var(--vp-code-tab-divider);
|
||||
margin-bottom: 0px;
|
||||
margin-top: -1px;
|
||||
height: 48px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.sp-wrapper .sp-preview-container {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.sp-wrapper .sp-tabs .sp-tab-button {
|
||||
padding: 0 12px;
|
||||
line-height: 48px;
|
||||
height: 48px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sp-wrapper .sp-tabs .sp-tab-button:after {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
left: 8px;
|
||||
bottom: 0px;
|
||||
z-index: 1;
|
||||
height: 1px;
|
||||
content: '';
|
||||
background-color: transparent;
|
||||
transition: background-color 0.25s;
|
||||
}
|
||||
|
||||
.sp-wrapper .sp-tabs .sp-tab-button[data-active='true'] {
|
||||
color: var(--vp-code-tab-active-text-color);
|
||||
}
|
||||
|
||||
.sp-wrapper .sp-tabs .sp-tab-button[data-active='true']:after {
|
||||
background-color: var(--vp-code-tab-active-bar-color);
|
||||
}
|
||||
|
||||
.sp-wrapper .sp-button {
|
||||
color: var(--sp-colors-clickable);
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
48
docs/.vitepress/theme/components/editors/SnackPlayer.vue
Normal file
48
docs/.vitepress/theme/components/editors/SnackPlayer.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import { useMutationObserver, useScriptTag } from '@vueuse/core';
|
||||
import { useData } from 'vitepress';
|
||||
import { onMounted, useTemplateRef, watchEffect } from 'vue';
|
||||
|
||||
const { isDark } = useData()
|
||||
const el = useTemplateRef('el')
|
||||
|
||||
useScriptTag('https://snack.expo.dev/embed.js')
|
||||
|
||||
watchEffect(() => {
|
||||
console.log(isDark.value);
|
||||
})
|
||||
|
||||
useMutationObserver(el, (mutations) => {
|
||||
const container = el.value;
|
||||
if (mutations[0]) {
|
||||
if ('ExpoSnack' in window) {
|
||||
window?.ExpoSnack?.remove(container);
|
||||
window?.ExpoSnack?.append(container);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
attributes: true,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
const container = el.value;
|
||||
if ('ExpoSnack' in window) {
|
||||
window?.ExpoSnack?.append(container);
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-bind="$attrs" class="snack-player" ref="el" :data-snack-theme="isDark ? 'dark' : 'light'" />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.snack-player {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
width: 100%;
|
||||
height: 635px;
|
||||
margin-bottom: 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
50
docs/.vitepress/theme/components/editors/styles.css
Normal file
50
docs/.vitepress/theme/components/editors/styles.css
Normal file
@@ -0,0 +1,50 @@
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
-webkit-font-smoothing: auto;
|
||||
-moz-font-smoothing: auto;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-smoothing: auto;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-smooth: always;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-touch-callout: none;
|
||||
background: #202127;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
padding: 10px 20px;
|
||||
line-height: 24px;
|
||||
gap: 8px;
|
||||
border-radius: 24px;
|
||||
outline: none;
|
||||
border: none;
|
||||
background: #111;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #f56565;
|
||||
}
|
||||
|
||||
.app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 32px;
|
||||
}
|
||||
88
docs/.vitepress/theme/components/guide/FrameworkSelect.vue
Normal file
88
docs/.vitepress/theme/components/guide/FrameworkSelect.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<script setup lang="tsx">
|
||||
import VPSidebarGroup from 'vitepress/dist/client/theme-default/components/VPSidebarGroup.vue';
|
||||
import sidebar, { guideSidebarTop } from '../../../sidebar';
|
||||
import { useData, useRouter } from 'vitepress';
|
||||
import Select from '../base/Select.vue';
|
||||
import { computed, ref, watch, watchEffect } from 'vue';
|
||||
import { link, route } from '~/.vitepress/data/iconNodes';
|
||||
import { useLocalStorage } from '@vueuse/core';
|
||||
|
||||
const { page } = useData()
|
||||
const router = useRouter()
|
||||
|
||||
const frameworks = [
|
||||
{ name: 'Vanilla', icon: '/framework-logos/js.svg', route: '/guide/lucide' },
|
||||
{ name: 'React', icon: '/framework-logos/react.svg', route: '/guide/react' },
|
||||
{ name: 'Vue', icon: '/framework-logos/vue.svg', route: '/guide/vue' },
|
||||
{ name: 'Svelte', icon: '/framework-logos/svelte.svg', route: '/guide/svelte' },
|
||||
{ name: 'Solid', icon: '/framework-logos/solid.svg', route: '/guide/solid' },
|
||||
{ name: 'Angular', icon: '/framework-logos/angular.svg', route: '/guide/angular' },
|
||||
{ name: 'Preact', icon: '/framework-logos/preact.svg', route: '/guide/preact' },
|
||||
{ name: 'React Native', icon: '/framework-logos/react-native.svg', route: '/guide/react-native' },
|
||||
{ name: 'Astro', icon: '/framework-logos/astro-dark.svg', route: '/guide/astro' },
|
||||
]
|
||||
|
||||
const fallbackFramework = useLocalStorage('lucide-docs-fallback-framework', frameworks[1])
|
||||
|
||||
const selected = computed(() => {
|
||||
const current = frameworks.find(({ route }) => {
|
||||
return router.route.path.split('/').slice(0, 3).join('/') === route
|
||||
})
|
||||
|
||||
return current || fallbackFramework.value
|
||||
})
|
||||
|
||||
function onSelectFramework(item: { name: string, icon: string, route: string }) {
|
||||
fallbackFramework.value = item
|
||||
if (item.route !== router.route.path) {
|
||||
const likeRoute = router.route.path.replace(selected.value.route, item.route);
|
||||
|
||||
const hasRoute = sidebar[item.route]?.some(section =>
|
||||
section?.items?.some(({ link }) => link === likeRoute)
|
||||
);
|
||||
|
||||
if (hasRoute) {
|
||||
router.go(likeRoute)
|
||||
return;
|
||||
}
|
||||
|
||||
router.go(item.route)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VPSidebarGroup :items="guideSidebarTop" v-if="page?.relativePath?.startsWith?.('guide')" />
|
||||
<div class="framework-select" v-if="page?.relativePath?.startsWith?.('guide')">
|
||||
<label for="framework-select">Framework</label>
|
||||
<Select id="framework-select" :items="frameworks" @update:model-value="onSelectFramework" v-model="selected" />
|
||||
</div>
|
||||
<VPSidebarGroup :key="selected.route"
|
||||
v-if="page?.relativePath?.startsWith?.('guide') && !page?.relativePath?.startsWith?.(selected.route.substring(1))"
|
||||
:items="sidebar[selected.route]" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.framework-select {
|
||||
font-size: 12px;
|
||||
transition: border-color 0.5s, background-color 0.5s ease;
|
||||
margin-bottom: 10px;
|
||||
position: sticky;
|
||||
top: -0.5px;
|
||||
z-index: 10;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
padding-top: 10px;
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
label {
|
||||
color: var(--vp-c-text-1);
|
||||
padding: 4px 0;
|
||||
line-height: 24px;
|
||||
font-size: 14px;
|
||||
transition: color 0.25s;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
51
docs/.vitepress/theme/components/home/HomeHeroAfter.vue
Normal file
51
docs/.vitepress/theme/components/home/HomeHeroAfter.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vitepress';
|
||||
import Badge from '../base/Badge.vue';
|
||||
import HomeContainer from './HomeContainer.vue';
|
||||
import { data } from './HomeHeroIconsCard.data'
|
||||
import FakeInput from '../base/FakeInput.vue'
|
||||
import { nextTick, provide } from 'vue'
|
||||
import useSearchShortcut from '../../utils/useSearchShortcut';
|
||||
|
||||
const { go } = useRouter()
|
||||
|
||||
const { shortcutText: kbdSearchShortcut } = useSearchShortcut(() => {
|
||||
go('/icons/?focus')
|
||||
})
|
||||
|
||||
const enableTransitions = () =>
|
||||
'startViewTransition' in document &&
|
||||
window.matchMedia('(prefers-reduced-motion: no-preference)').matches
|
||||
|
||||
|
||||
async function handleClick(event: MouseEvent) {
|
||||
if (!enableTransitions()) {
|
||||
go('/icons/?focus')
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await document.startViewTransition(async () => {
|
||||
await go('/icons/?focus');
|
||||
await nextTick()
|
||||
}).ready
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<FakeInput
|
||||
@click="go('/icons/?focus')"
|
||||
:shortcut="kbdSearchShortcut"
|
||||
class="search-box"
|
||||
>
|
||||
Search {{ data.iconsCount }} icons...
|
||||
</FakeInput>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.search-box {
|
||||
view-transition-name: icons-search-box;
|
||||
width: 100%;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
</style>
|
||||
428
docs/.vitepress/theme/components/home/HomeHeroIconsAnimation.vue
Normal file
428
docs/.vitepress/theme/components/home/HomeHeroIconsAnimation.vue
Normal file
@@ -0,0 +1,428 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, shallowRef, onBeforeUnmount, watchEffect, computed } from 'vue';
|
||||
import { data } from './HomeHeroIconsCard.data'
|
||||
import { useRouter } from 'vitepress';
|
||||
import { random } from 'lodash-es'
|
||||
import FakeInput from '../base/FakeInput.vue'
|
||||
import { motion, Variants, useScroll, useSpring, useTransform } from "motion-v"
|
||||
import LucideIcon from '../base/LucideIcon.vue'
|
||||
import { shrink } from '~/.vitepress/data/iconNodes';
|
||||
|
||||
const emit = defineEmits(['animation-complete'])
|
||||
|
||||
const MotionLucideIcon = motion.create(LucideIcon)
|
||||
|
||||
const COLUMNS = 8;
|
||||
const SIZE = 2;
|
||||
const GAP = 1;
|
||||
|
||||
const { scrollYProgress } = useScroll()
|
||||
const opacity = useTransform(() => (1 - scrollYProgress.get() * 8))
|
||||
|
||||
const icons = ref(data.icons.slice(0, 64).map((icon, index) => {
|
||||
const x = index % COLUMNS;
|
||||
const y = Math.floor(index / COLUMNS);
|
||||
|
||||
if (index === 0) {
|
||||
return {
|
||||
...icon,
|
||||
x: 9999,
|
||||
y: 9999,
|
||||
opacity: 0
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...icon,
|
||||
x: x * (SIZE + GAP) + 0.5,
|
||||
y: y * (SIZE + GAP) + 0.5
|
||||
}
|
||||
}))
|
||||
|
||||
const { go } = useRouter()
|
||||
const intervalTime = shallowRef()
|
||||
const showHandles = ref(true)
|
||||
const scaleDownVariants: Variants = {
|
||||
fullSize: {
|
||||
scale: 1
|
||||
},
|
||||
riseUp: {
|
||||
x: 0.5,
|
||||
y: -0.5,
|
||||
animationName: 'riseUp',
|
||||
scale: 1,
|
||||
transition: {
|
||||
delay: 0.5,
|
||||
duration: 1.5,
|
||||
ease: [0.22, 1, 0.36, 1]
|
||||
}
|
||||
},
|
||||
small: {
|
||||
x: -10.5,
|
||||
y: -10.5,
|
||||
scale: 0.1,
|
||||
animationName: 'small',
|
||||
transition: {
|
||||
delay: 1,
|
||||
duration: 1,
|
||||
ease: [0.22, 1, 0.36, 1]
|
||||
}
|
||||
}
|
||||
}
|
||||
const scaleDownAnimation = ref('fullSize')
|
||||
|
||||
const iconGridAnimation = ref('initial')
|
||||
|
||||
const drawAnimation = ref('visible')
|
||||
const draw: Variants = {
|
||||
hidden: { pathLength: 0, opacity: 0 },
|
||||
visible: {
|
||||
animationName: 'visible',
|
||||
pathLength: 1,
|
||||
opacity: 1,
|
||||
transition: {
|
||||
pathLength: { delay: 2.4, type: "spring", duration: 2.8, bounce: 0 },
|
||||
opacity: { delay: 2.4, duration: 0.1 },
|
||||
},
|
||||
},
|
||||
exit: (path) => ({
|
||||
animationName: 'exit',
|
||||
stroke: path ? 'var(--vp-c-text-1)' : 'var(--vp-c-brand)',
|
||||
pathLength: 1,
|
||||
opacity: 1,
|
||||
transition: {
|
||||
duration: 0.8,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const onAnimationComplete = (item) => {
|
||||
if (item.animationName === 'visible') {
|
||||
drawAnimation.value = 'exit'
|
||||
return
|
||||
}
|
||||
if (item.animationName === 'exit') {
|
||||
showHandles.value = false
|
||||
scaleDownAnimation.value = 'small'
|
||||
}
|
||||
if (item.animationName === 'small') {
|
||||
iconGridAnimation.value = 'showIcons'
|
||||
}
|
||||
if (item.animationName === 'riseUp') {
|
||||
scaleDownAnimation.value = 'small'
|
||||
}
|
||||
if (item.animationName === 'showIcons') {
|
||||
shrinkIconAnimation.value = 'shrinkIcons'
|
||||
}
|
||||
if (item.animationName === 'shrinkIcons') {
|
||||
iconGridAnimation.value = 'initial'
|
||||
|
||||
setTimeout(() => {
|
||||
emit('animation-complete')
|
||||
}, 2800)
|
||||
}
|
||||
}
|
||||
|
||||
const randomIndex = ref(
|
||||
Math.floor(Math.random() * 64)
|
||||
)
|
||||
|
||||
const iconAnimationVariants = {
|
||||
initial: {
|
||||
animationName: 'end',
|
||||
opacity: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
transition: { duration: 1, delay: 1, ease: 'easeInOut' }
|
||||
},
|
||||
showIcons: (index) => ({
|
||||
animationName: 'showIcons',
|
||||
opacity: [0, 1, 1],
|
||||
x: [0.5, 0, 0],
|
||||
y: [-0.5, 0, 0],
|
||||
strokeWidth: randomIndex.value === index ? [0, 2, 2] : undefined,
|
||||
transition: { delay: index * 0.023, duration: 1.6, ease: 'easeInOut' }
|
||||
}),
|
||||
}
|
||||
|
||||
const shrinkIconAnimation = ref('initial')
|
||||
|
||||
const shrinkIconVariants = {
|
||||
initial: { strokeWidth: 2 },
|
||||
shrinkIcons: (index) => ({
|
||||
animationName: 'shrinkIcons',
|
||||
opacity: 1,
|
||||
strokeWidth: 0,
|
||||
transition: { delay: 1.8, duration: 1.5, ease: 'easeInOut' }
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="home-hero-animation-container">
|
||||
<div class="home-hero-animation">
|
||||
<motion.svg xmlns="http://www.w3.org/2000/svg" viewBox="-12 -12 48 48" fill="none" overflow="auto"
|
||||
stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="hero-background"
|
||||
:style="{ opacity }">
|
||||
|
||||
<g class="svg-preview-grid-group" stroke-linecap="butt" stroke-width="0.1" stroke="#777"
|
||||
mask="url(#svg-preview-bounding-box-mask)" stroke-opacity="0.3">
|
||||
<path
|
||||
stroke-dasharray="0 0.1 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0 0.15"
|
||||
stroke-width="0.1"
|
||||
d="M1 0.1v23.8M2 0.1v23.8M4 0.1v23.8M5 0.1v23.8M7 0.1v23.8M8 0.1v23.8M10 0.1v23.8M11 0.1v23.8M13 0.1v23.8M14 0.1v23.8M16 0.1v23.8M17 0.1v23.8M19 0.1v23.8M20 0.1v23.8M22 0.1v23.8M23 0.1v23.8M0.1 1h23.8M0.1 2h23.8M0.1 4h23.8M0.1 5h23.8M0.1 7h23.8M0.1 8h23.8M0.1 10h23.8M0.1 11h23.8M0.1 13h23.8M0.1 14h23.8M0.1 16h23.8M0.1 17h23.8M0.1 19h23.8M0.1 20h23.8M0.1 22h23.8M0.1 23h23.8">
|
||||
</path>
|
||||
<path
|
||||
d="M3 0.1v23.8M6 0.1v23.8M9 0.1v23.8M12 0.1v23.8M15 0.1v23.8M18 0.1v23.8M21 0.1v23.8M0.1 3h23.8M0.1 6h23.8M0.1 9h23.8M0.1 12h23.8M0.1 15h23.8M0.1 18h23.8M0.1 21h23.8">
|
||||
</path>
|
||||
</g>
|
||||
<!-- <rect fill="red" x="0" y="0" width="24" height="24" fill-opacity="0.1" stroke="none" /> -->
|
||||
<motion.g initial="initial" :variants="shrinkIconVariants" :animate="shrinkIconAnimation"
|
||||
@animation-complete="onAnimationComplete">
|
||||
<MotionLucideIcon v-for="(icon, index) in icons" size="2" class="animated-icon" initial="initial"
|
||||
:key="icon.name" :variants="iconAnimationVariants" :animate="iconGridAnimation" :custom="index"
|
||||
strokeWidth="inherit" v-bind="icon" @animation-complete="onAnimationComplete" />
|
||||
<motion.g class="svg-preview-colored-path-group" :variants="scaleDownVariants" :animate="scaleDownAnimation"
|
||||
initial="hidden" @animation-complete="onAnimationComplete">
|
||||
<motion.path
|
||||
d="M14 12C14 9.79086 12.2091 8 10 8C7.79086 8 6 9.79086 6 12C6 16.4183 9.58172 20 14 20C18.4183 20 22 16.4183 22 12C22 8.446 20.455 5.25285 18 3.05557"
|
||||
:style="{ stroke: 'var(--vp-c-gray-1)' }" :animate="drawAnimation" initial="hidden" :variants="draw"
|
||||
:custom="1" @animation-complete="onAnimationComplete" />
|
||||
<motion.path
|
||||
d="M10 12C10 14.2091 11.7909 16 14 16C16.2091 16 18 14.2091 18 12C18 7.58172 14.4183 4 10 4C5.58172 4 2 7.58172 2 12C2 15.5841 3.57127 18.8012 6.06253 21"
|
||||
:style="{ stroke: 'var(--vp-c-gray-1)' }" :animate="drawAnimation" initial="hidden" :variants="draw"
|
||||
:custom="0" />
|
||||
</motion.g>
|
||||
</motion.g>
|
||||
<motion.g class="svg-preview-control-path-marker-group" stroke="#fff" stroke-width="0.125"
|
||||
:initial="{ opacity: 1 }" :animate="showHandles ? { opacity: 1 } : { opacity: 0 }"
|
||||
:transition="{ delay: 0, duration: 0.2 }">
|
||||
<motion.path
|
||||
d="M14 12C14 9.79086 12.2091 8 10 8C7.79086 8 6 9.79086 6 12C6 16.4183 9.58172 20 14 20C18.4183 20 22 16.4183 22 12C22 8.446 20.455 5.25285 18 3.05557"
|
||||
:initial="{ opacity: 0 }" :animate="{ opacity: 1 }" :transition="{ delay: 1.6, duration: 1.5 }" />
|
||||
<motion.path
|
||||
d="M10 12C10 14.2091 11.7909 16 14 16C16.2091 16 18 14.2091 18 12C18 7.58172 14.4183 4 10 4C5.58172 4 2 7.58172 2 12C2 15.5841 3.57127 18.8012 6.06253 21"
|
||||
:initial="{ opacity: 0 }" :animate="{ opacity: 1 }" :transition="{ delay: 1.6, duration: 1.5 }" />
|
||||
<motion.g :initial="{ opacity: 0 }" :animate="{ opacity: 1 }" :transition="{ delay: 0.2, duration: 0.3 }">
|
||||
<path
|
||||
d="M14 12h.01M10 8h.01M10 8h.01M6 12h.01M6 12h.01M14 20h.01M14 20h.01M22 12h.01M22 12h.01M18 3.05557h.01M10 12h.01M14 16h.01M14 16h.01M18 12h.01M18 12h.01M10 4h.01M10 4h.01M2 12h.01M2 12h.01M6.06253 21h.01">
|
||||
</path>
|
||||
</motion.g>
|
||||
|
||||
<motion.circle :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0, duration: 0.8 }" cx="14" cy="12" r="0.5" />
|
||||
<motion.circle :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0, duration: 0.8 }" cx="14" cy="12" r="0.5" />
|
||||
<motion.circle :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0, duration: 0.8 }" cx="18" cy="3.05557" r="0.5" />
|
||||
<motion.circle :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0, duration: 0.8 }" cx="10" cy="12" r="0.5" />
|
||||
<motion.circle :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0, duration: 0.8 }" cx="6.06253" cy="21" r="0.5" />
|
||||
</motion.g>
|
||||
<motion.g class="svg-preview-handles-group" stroke-width="0.12" stroke="#FFF" stroke-opacity="0.3"
|
||||
:initial="{ opacity: 1 }" :animate="showHandles ? { opacity: 1 } : { opacity: 0 }"
|
||||
:transition="{ delay: 0, duration: 0.6 }">
|
||||
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0.2, duration: 0.3 }">
|
||||
<path d="M14 12 14 9.79086"></path>
|
||||
<circle cy="9.79086" cx="14" r="0.25"></circle>
|
||||
</motion.g>
|
||||
|
||||
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0.4, duration: 0.3 }">
|
||||
<path d="M10 8 12.2091 8"></path>
|
||||
<circle cy="8" cx="12.2091" r="0.25"></circle>
|
||||
<path d="M10 8 7.79086 8"></path>
|
||||
<circle cy="8" cx="7.79086" r="0.25"></circle>
|
||||
</motion.g>
|
||||
|
||||
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0.6, duration: 0.3 }">
|
||||
<path d="M6 12 6 9.79086"></path>
|
||||
<circle cy="9.79086" cx="6" r="0.25"></circle>
|
||||
<path d="M6 12 6 16.4183"></path>
|
||||
<circle cy="16.4183" cx="6" r="0.25"></circle>
|
||||
</motion.g>
|
||||
|
||||
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0.8, duration: 0.3 }">
|
||||
<path d="M14 20 9.58172 20"></path>
|
||||
<circle cy="20" cx="9.58172" r="0.25"></circle>
|
||||
<path d="M14 20 18.4183 20"></path>
|
||||
<circle cy="20" cx="18.4183" r="0.25"></circle>
|
||||
</motion.g>
|
||||
|
||||
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 1, duration: 0.3 }">
|
||||
<path d="M22 12 22 16.4183"></path>
|
||||
<circle cy="16.4183" cx="22" r="0.25"></circle>
|
||||
<path d="M22 12 22 8.446"></path>
|
||||
<circle cy="8.446" cx="22" r="0.25"></circle>
|
||||
</motion.g>
|
||||
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 1.2, duration: 0.3 }">
|
||||
<path d="M18 3.05557 20.455 5.25285"></path>
|
||||
<circle cy="5.25285" cx="20.455" r="0.25"></circle>
|
||||
</motion.g>
|
||||
|
||||
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0.2, duration: 0.3 }">
|
||||
<path d="M10 12 10 14.2091"></path>
|
||||
<circle cy="14.2091" cx="10" r="0.25"></circle>
|
||||
</motion.g>
|
||||
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0.4, duration: 0.3 }">
|
||||
<path d="M14 16 11.7909 16"></path>
|
||||
<circle cy="16" cx="11.7909" r="0.25"></circle>
|
||||
<path d="M14 16 16.2091 16"></path>
|
||||
<circle cy="16" cx="16.2091" r="0.25"></circle>
|
||||
</motion.g>
|
||||
|
||||
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0.6, duration: 0.3 }">
|
||||
<path d="M18 12 18 14.2091"></path>
|
||||
<circle cy="14.2091" cx="18" r="0.25"></circle>
|
||||
<path d="M18 12 18 7.58172"></path>
|
||||
<circle cy="7.58172" cx="18" r="0.25"></circle>
|
||||
</motion.g>
|
||||
|
||||
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0.8, duration: 0.3 }">
|
||||
<path d="M10 4 14.4183 4"></path>
|
||||
<circle cy="4" cx="14.4183" r="0.25"></circle>
|
||||
<path d="M10 4 5.58172 4"></path>
|
||||
<circle cy="4" cx="5.58172" r="0.25"></circle>
|
||||
</motion.g>
|
||||
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 1, duration: 0.3 }">
|
||||
<path d="M2 12 2 7.58172"></path>
|
||||
<circle cy="7.58172" cx="2" r="0.25"></circle>
|
||||
<path d="M2 12 2 15.5841"></path>
|
||||
<circle cy="15.5841" cx="2" r="0.25"></circle>
|
||||
</motion.g>
|
||||
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 1.2, duration: 0.3 }">
|
||||
<path d="M6.06253 21 3.57127 18.8012"></path>
|
||||
<circle cy="18.8012" cx="3.57127" r="0.25"></circle>
|
||||
</motion.g>
|
||||
</motion.g>
|
||||
</motion.svg>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.home-hero-animation-container {
|
||||
margin: -48px -24px 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.home-hero-animation {
|
||||
height: 250px;
|
||||
width: 396px;
|
||||
overflow: hidden;
|
||||
margin: auto;
|
||||
margin-left: calc(((396px - 100vw) / 2)* -1);
|
||||
}
|
||||
|
||||
@media (min-width: 396px) {
|
||||
.home-hero-animation {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-background {
|
||||
transform: rotateX(-51deg) rotateZ(-43deg);
|
||||
transform-style: preserve-3d;
|
||||
will-change: transform, opacity;
|
||||
position: relative;
|
||||
top: -155px;
|
||||
left: -82px;
|
||||
width: 560px;
|
||||
height: 560px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.hero-background {
|
||||
width: 680px;
|
||||
height: 680px;
|
||||
|
||||
left: -100px;
|
||||
top: -188px;
|
||||
}
|
||||
|
||||
.home-hero-animation {
|
||||
height: 305px;
|
||||
width: 480px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.hero-background {
|
||||
width: 760px;
|
||||
height: 760px;
|
||||
|
||||
left: -110px;
|
||||
top: -200px;
|
||||
}
|
||||
|
||||
.home-hero-animation {
|
||||
height: 360px;
|
||||
width: 540px;
|
||||
}
|
||||
|
||||
.home-hero-animation-container {
|
||||
margin-top: -60px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.hero-background {
|
||||
top: -20vw;
|
||||
right: 20vw;
|
||||
width: 80vw;
|
||||
height: 80vw;
|
||||
}
|
||||
|
||||
.home-hero-animation {
|
||||
height: 415px;
|
||||
width: 620px;
|
||||
}
|
||||
|
||||
.home-hero-animation-container {
|
||||
margin: -48px -48px 0 -64px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1160px) {
|
||||
.home-hero-animation-container {
|
||||
margin-right: -64px;
|
||||
margin-bottom: -180px;
|
||||
}
|
||||
|
||||
.home-hero-animation {
|
||||
width: auto;
|
||||
height: calc(((1152px/2)));
|
||||
top: -20px;
|
||||
}
|
||||
|
||||
.hero-background {
|
||||
top: -20vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.home-hero-animation-container {
|
||||
margin-right: calc(((((100vw - 1152px) / 2)) * -1) + 24px);
|
||||
margin-left: -128px
|
||||
}
|
||||
|
||||
.hero-background {
|
||||
width: 1024px;
|
||||
height: 1024px;
|
||||
top: -280px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,162 +1,13 @@
|
||||
<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'
|
||||
import useSearchShortcut from '../../utils/useSearchShortcut'
|
||||
|
||||
const { go } = useRouter()
|
||||
const intervalTime = shallowRef()
|
||||
|
||||
const { shortcutText: kbdSearchShortcut } = useSearchShortcut(() => {
|
||||
go('/icons/?focus')
|
||||
})
|
||||
|
||||
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[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)
|
||||
})
|
||||
import { ref } from 'vue';
|
||||
import HomeHeroIconsAnimation from './HomeHeroIconsAnimation.vue'
|
||||
|
||||
const animationRun = ref(1)
|
||||
</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')"
|
||||
:shortcut="kbdSearchShortcut"
|
||||
class="search-box"
|
||||
>
|
||||
Search {{ data.iconsCount }} icons...
|
||||
</FakeInput>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<HomeHeroIconsAnimation
|
||||
:key="animationRun"
|
||||
@animation-complete="animationRun++"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.card-wrapper {
|
||||
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%;
|
||||
max-height: 220px;
|
||||
max-width: 560px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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>
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import Badge from '../base/Badge.vue';
|
||||
import HomeContainer from './HomeContainer.vue';
|
||||
import { data } from './HomeHeroBefore.data'
|
||||
import { data } from './HomeHeroInfoBefore.data'
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<HomeContainer class="container">
|
||||
<Badge
|
||||
:href="`https://github.com/lucide-icons/lucide/releases/tag/${data.version}`"
|
||||
>v{{ data.version }}</Badge>
|
||||
</HomeContainer>
|
||||
<Badge :href="`https://github.com/lucide-icons/lucide/releases/tag/${data.version}`">v{{ data.version }}</Badge>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
margin-block: 0;;
|
||||
margin-block: 0;
|
||||
margin-top: 37px;
|
||||
margin-bottom: -96px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -34,9 +31,9 @@ import { data } from './HomeHeroBefore.data'
|
||||
.container {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -13,25 +13,25 @@ export default {
|
||||
label: 'Lucide documentation for React',
|
||||
},
|
||||
{
|
||||
name: 'lucide-vue-next',
|
||||
name: 'lucide-vue',
|
||||
logo: '/framework-logos/vue.svg',
|
||||
label: 'Lucide documentation for Vue 3',
|
||||
label: 'Lucide documentation for Vue',
|
||||
},
|
||||
{
|
||||
name: 'lucide-svelte',
|
||||
logo: '/framework-logos/svelte.svg',
|
||||
label: 'Lucide documentation for Svelte',
|
||||
},
|
||||
{
|
||||
name: 'lucide-solid',
|
||||
logo: '/framework-logos/solid.svg',
|
||||
label: 'Lucide documentation for Solid',
|
||||
},
|
||||
{
|
||||
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',
|
||||
@@ -48,6 +48,11 @@ export default {
|
||||
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',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -2,48 +2,45 @@
|
||||
import { useData } from 'vitepress';
|
||||
import { useSessionStorage } from '@vueuse/core';
|
||||
import IconButton from '../base/IconButton.vue';
|
||||
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue';
|
||||
import { x } from '../../../data/iconNodes';
|
||||
import Icon from 'lucide-vue-next/src/Icon';
|
||||
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue'
|
||||
import { x } from '../../../data/iconNodes'
|
||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
const { theme } = useData();
|
||||
const showAd = useSessionStorage('show-carbon-ads', true);
|
||||
const carbonLoaded = ref(true);
|
||||
const { theme } = useData()
|
||||
const showAd = useSessionStorage('show-carbon-ads', true)
|
||||
const carbonLoaded = ref(true)
|
||||
|
||||
defineProps<{
|
||||
drawerOpen: boolean;
|
||||
}>();
|
||||
drawerOpen: boolean
|
||||
}>()
|
||||
|
||||
const CloseIcon = createLucideIcon('Close', x)
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
if (window?._carbonads == null) {
|
||||
carbonLoaded.value = false;
|
||||
carbonLoaded.value = false
|
||||
}
|
||||
}, 5000);
|
||||
});
|
||||
}, 5000)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'drawer-open': drawerOpen,
|
||||
'hide-ad': !(showAd && carbonLoaded),
|
||||
'hide-ad': !(showAd && carbonLoaded)
|
||||
}"
|
||||
class="floating-ad"
|
||||
v-if="theme.carbonAds"
|
||||
>
|
||||
<IconButton
|
||||
@click="showAd = false"
|
||||
class="hide-button"
|
||||
>
|
||||
<Icon
|
||||
:iconNode="x"
|
||||
:size="20"
|
||||
absoluteStrokeWidth
|
||||
/>
|
||||
<IconButton @click="showAd = false" class="hide-button">
|
||||
<component :is="CloseIcon" :size="20" absoluteStrokeWidth />
|
||||
</IconButton>
|
||||
<VPDocAsideCarbonAds :carbon-ads="theme.carbonAds" />
|
||||
<VPDocAsideCarbonAds
|
||||
:carbon-ads="theme.carbonAds"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -54,9 +51,7 @@ onMounted(() => {
|
||||
bottom: 32px;
|
||||
width: 224px;
|
||||
right: 32px;
|
||||
transition:
|
||||
opacity 0.5s,
|
||||
transform 0.25s ease;
|
||||
transition: opacity 0.5s, transform 0.25s ease;
|
||||
}
|
||||
|
||||
.floating-ad.drawer-open {
|
||||
@@ -72,11 +67,8 @@ onMounted(() => {
|
||||
transform: translateY(-288px) translateX(224px);
|
||||
}
|
||||
|
||||
.floating-ad.drawer-open,
|
||||
.floating-ad.hide-ad {
|
||||
transition:
|
||||
opacity 0.25s,
|
||||
transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
||||
.floating-ad.drawer-open, .floating-ad.hide-ad {
|
||||
transition: opacity 0.25s, transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
|
||||
@@ -1,68 +1,70 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import ButtonMenu from '../base/ButtonMenu.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 downloadText = 'Download!'
|
||||
const copiedText = 'Copied!'
|
||||
const confettiText = ref(copiedText)
|
||||
const props = defineProps<{
|
||||
name: string;
|
||||
popoverPosition?: 'top' | 'bottom';
|
||||
}>();
|
||||
name: string
|
||||
popoverPosition?: 'top' | 'bottom'
|
||||
}>()
|
||||
|
||||
const { size } = useIconStyleContext();
|
||||
const { size } = useIconStyleContext()
|
||||
|
||||
const { animate, confetti } = useConfetti();
|
||||
const { animate, confetti } = useConfetti()
|
||||
|
||||
function copySVG() {
|
||||
confettiText.value = copiedText;
|
||||
const svgString = getSVGIcon();
|
||||
confettiText.value = copiedText
|
||||
const svgString = getSVGIcon()
|
||||
|
||||
navigator.clipboard.writeText(svgString);
|
||||
navigator.clipboard.writeText(svgString)
|
||||
|
||||
confetti();
|
||||
confetti()
|
||||
}
|
||||
|
||||
function copyDataUrl() {
|
||||
confettiText.value = copiedText;
|
||||
const svgString = getSVGIcon();
|
||||
confettiText.value = copiedText
|
||||
const svgString = getSVGIcon()
|
||||
|
||||
// Create SVG data url
|
||||
const dataUrl = `data:image/svg+xml;base64,${btoa(svgString)}`;
|
||||
navigator.clipboard.writeText(dataUrl);
|
||||
const dataUrl = `data:image/svg+xml;base64,${btoa(svgString)}`
|
||||
navigator.clipboard.writeText(dataUrl)
|
||||
|
||||
confetti();
|
||||
confetti()
|
||||
}
|
||||
|
||||
function downloadSVG() {
|
||||
confettiText.value = downloadText;
|
||||
const svgString = getSVGIcon();
|
||||
confettiText.value = downloadText
|
||||
const svgString = getSVGIcon()
|
||||
|
||||
downloadData(`${props.name}.svg`, `data:image/svg+xml;base64,${btoa(svgString)}`);
|
||||
confetti();
|
||||
downloadData(`${props.name}.svg`, `data:image/svg+xml;base64,${btoa(svgString)}`)
|
||||
confetti()
|
||||
}
|
||||
|
||||
function downloadPNG() {
|
||||
confettiText.value = downloadText;
|
||||
const svgString = getSVGIcon();
|
||||
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 ctx = canvas.getContext("2d");
|
||||
|
||||
const image = new Image();
|
||||
image.src = `data:image/svg+xml;base64,${btoa(svgString)}`;
|
||||
image.onload = function () {
|
||||
image.onload = function() {
|
||||
ctx.drawImage(image, 0, 0);
|
||||
downloadData(`${props.name}.png`, canvas.toDataURL('image/png'));
|
||||
confetti();
|
||||
};
|
||||
downloadData(`${props.name}.png`, canvas.toDataURL('image/png'))
|
||||
confetti()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -73,10 +75,10 @@ function downloadPNG() {
|
||||
: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 },
|
||||
{ text: 'Copy SVG' , onClick: copySVG },
|
||||
{ text: 'Copy Data URL' , onClick: copyDataUrl },
|
||||
{ text: 'Download SVG' , onClick: downloadSVG },
|
||||
{ text: 'Download PNG' , onClick: downloadPNG },
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,44 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, useSlots } from 'vue';
|
||||
import { copy } from '../../../data/iconNodes';
|
||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
||||
import { copy } from '../../../data/iconNodes'
|
||||
import useConfetti from '../../composables/useConfetti';
|
||||
import Icon from 'lucide-vue-next/src/Icon';
|
||||
const { animate, confetti } = useConfetti();
|
||||
const slots = useSlots();
|
||||
const { animate, confetti } = useConfetti()
|
||||
const slots = useSlots()
|
||||
|
||||
const copiedText = computed(() => slots.default?.()[0].children);
|
||||
const copiedText = computed(() => slots.default?.()[0].children)
|
||||
|
||||
function copyText() {
|
||||
navigator.clipboard.writeText(copiedText.value);
|
||||
navigator.clipboard.writeText(copiedText.value)
|
||||
|
||||
confetti();
|
||||
confetti()
|
||||
}
|
||||
|
||||
const Copy = createLucideIcon('ChevronUp', copy)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1
|
||||
class="icon-name confetti-button"
|
||||
:class="{ animate }"
|
||||
:class="{animate}"
|
||||
data-confetti-text="Copied!"
|
||||
@click="copyText"
|
||||
>
|
||||
<slot />
|
||||
<Icon
|
||||
:iconNode="copy"
|
||||
:size="20"
|
||||
class="copy-icon"
|
||||
/>
|
||||
<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 0.15s;
|
||||
transition: background ease-in .15s;;
|
||||
padding: 2px 8px;
|
||||
border-radius: 8px;
|
||||
width: auto;
|
||||
@@ -51,7 +48,7 @@ function copyText() {
|
||||
}
|
||||
|
||||
.icon-name:hover .copy-icon {
|
||||
opacity: 0.9;
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
.icon-name:before,
|
||||
@@ -68,10 +65,10 @@ function copyText() {
|
||||
opacity: 0;
|
||||
margin-left: 12px;
|
||||
margin-top: 6px;
|
||||
transition: ease 0.3s opacity;
|
||||
transition:ease .3s opacity;
|
||||
}
|
||||
|
||||
.icon-name:hover .copy-icon:hover {
|
||||
opacity: 0.6;
|
||||
opacity: .6;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -29,7 +29,12 @@ const props = defineProps<{
|
||||
|
||||
const iconComponent = computed(() => {
|
||||
if (!props.name || !props.iconNode) return null;
|
||||
return createLucideIcon(props.name, props.iconNode);
|
||||
try {
|
||||
return createLucideIcon(props.name, props.iconNode);
|
||||
} catch (error) {
|
||||
console.warn(`Icon ${props.name} not found, using fallback`);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const CalendarIcon = createLucideIcon('calendar', Calendar.iconNode);
|
||||
@@ -61,7 +66,7 @@ const prettyName = props.name
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="showcase">
|
||||
<section class="showcase" v-if="iconComponent">
|
||||
<h2 class="title">See this icon in action</h2>
|
||||
<div class="showcase-grid">
|
||||
<div class="showcase-item column">
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, defineAsyncComponent, onMounted } from 'vue';
|
||||
import { ref, computed, defineAsyncComponent, onMounted, watch, watchEffect } 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 useSearchShortcut from '../../utils/useSearchShortcut';
|
||||
import StickyBar from './StickyBar.vue';
|
||||
import IconsCategory, { CategoryRow } from './IconsCategory.vue';
|
||||
import IconsCategory from './IconsCategory.vue';
|
||||
import useFetchTags from '../../composables/useFetchTags';
|
||||
import useFetchCategories from '../../composables/useFetchCategories';
|
||||
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
||||
import chunkArray from '../../utils/chunkArray';
|
||||
import { CategoryRow } from './IconsCategory.vue';
|
||||
import useScrollToCategory from '../../composables/useScrollToCategory';
|
||||
import CarbonAdOverlay from './CarbonAdOverlay.vue';
|
||||
import useSearchPlaceholder from '../../utils/useSearchPlaceholder.ts';
|
||||
|
||||
const ICON_SIZE = 56;
|
||||
const ICON_GRID_GAP = 8;
|
||||
@@ -40,10 +40,10 @@ const { execute: fetchTags, data: tags } = useFetchTags();
|
||||
const { execute: fetchCategories, data: categoriesMap } = useFetchCategories();
|
||||
|
||||
const overviewEl = ref<HTMLElement | null>(null);
|
||||
const { width: containerWidth } = useElementSize(overviewEl);
|
||||
const { width: containerWidth } = useElementSize(overviewEl)
|
||||
|
||||
const columnSize = computed(() => {
|
||||
return Math.floor(containerWidth.value / (ICON_SIZE + ICON_GRID_GAP));
|
||||
return Math.floor((containerWidth.value) / ((ICON_SIZE + ICON_GRID_GAP)));
|
||||
});
|
||||
|
||||
const mappedIcons = computed(() => {
|
||||
@@ -71,27 +71,26 @@ const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
|
||||
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 = icon?.externalLibrary
|
||||
? icon.categories
|
||||
: props.iconCategories[icon.name];
|
||||
return props.categories
|
||||
.map(({ name, title }) => {
|
||||
const categoryIcons = props.icons.filter((icon) => {
|
||||
const iconCategories = icon?.externalLibrary ? icon.categories : props.iconCategories[icon.name]
|
||||
|
||||
return iconCategories?.includes(name);
|
||||
});
|
||||
return iconCategories?.includes(name);
|
||||
});
|
||||
|
||||
const searchedCategoryIcons = isSearching
|
||||
? categoryIcons.filter((icon) =>
|
||||
searchResults.value.some((item) => item?.name === icon?.name),
|
||||
)
|
||||
: categoryIcons;
|
||||
const searchedCategoryIcons = isSearching
|
||||
? categoryIcons.filter((icon) =>
|
||||
searchResults.value.some((item) => item?.name === icon?.name)
|
||||
)
|
||||
: categoryIcons;
|
||||
|
||||
return {
|
||||
title,
|
||||
name,
|
||||
icons: searchedCategoryIcons,
|
||||
};
|
||||
});
|
||||
return {
|
||||
title,
|
||||
name,
|
||||
icons: searchedCategoryIcons,
|
||||
};
|
||||
})
|
||||
});
|
||||
|
||||
const categoriesList = computed(() => {
|
||||
@@ -108,24 +107,26 @@ const categoriesList = computed(() => {
|
||||
return acc;
|
||||
}, []);
|
||||
});
|
||||
const searchPlaceholder = useSearchPlaceholder(searchQuery, searchResults);
|
||||
|
||||
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(categoriesList, {
|
||||
itemHeight: ICON_SIZE + ICON_GRID_GAP,
|
||||
overscan: 10,
|
||||
});
|
||||
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
|
||||
categoriesList,
|
||||
{
|
||||
itemHeight: ICON_SIZE + ICON_GRID_GAP,
|
||||
overscan: 10
|
||||
},
|
||||
)
|
||||
|
||||
useScrollToCategory({
|
||||
categories,
|
||||
categoriesList,
|
||||
scrollTo,
|
||||
searchQueryDebounced,
|
||||
});
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
containerProps.ref.value = document.documentElement;
|
||||
useEventListener(window, 'scroll', containerProps.onScroll);
|
||||
});
|
||||
useEventListener(window, 'scroll', containerProps.onScroll)
|
||||
})
|
||||
|
||||
function onFocusSearchInput() {
|
||||
if (tags.value == null) {
|
||||
@@ -144,13 +145,16 @@ function handleCloseDrawer() {
|
||||
|
||||
window.history.pushState({}, '', '/icons/categories');
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
|
||||
console.log(props.icons.find((icon) => icon.name === 'burger'));
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="overviewEl"
|
||||
class="overview-container"
|
||||
>
|
||||
<div ref="overviewEl" class="overview-container">
|
||||
<StickyBar class="category-search">
|
||||
<InputSearch
|
||||
:placeholder="`Search ${icons.length} icons ...`"
|
||||
@@ -162,9 +166,8 @@ function handleCloseDrawer() {
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="searchPlaceholder.isNoResults"
|
||||
:searchQuery="searchPlaceholder.query"
|
||||
:isBrandSearch="searchPlaceholder.isBrand"
|
||||
v-if="categories.length === 0"
|
||||
:searchQuery="searchQuery"
|
||||
@clear="searchQuery = ''"
|
||||
/>
|
||||
<div v-bind="wrapperProps">
|
||||
@@ -205,4 +208,8 @@ function handleCloseDrawer() {
|
||||
.icons {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.overview-container {
|
||||
padding-bottom: 288px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, defineAsyncComponent, onMounted, watch } from 'vue';
|
||||
import { ref, computed, defineAsyncComponent, onMounted, onBeforeUnmount, watch } from 'vue';
|
||||
import type { IconEntity } from '../../types';
|
||||
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
||||
import { useRoute } from 'vitepress';
|
||||
@@ -13,7 +13,6 @@ import useFetchTags from '../../composables/useFetchTags';
|
||||
import useFetchCategories from '../../composables/useFetchCategories';
|
||||
import chunkArray from '../../utils/chunkArray';
|
||||
import CarbonAdOverlay from './CarbonAdOverlay.vue';
|
||||
import useSearchPlaceholder from '../../utils/useSearchPlaceholder.ts';
|
||||
|
||||
const ICON_SIZE = 56;
|
||||
const ICON_GRID_GAP = 8;
|
||||
@@ -37,10 +36,10 @@ const { execute: fetchTags, data: tags } = useFetchTags();
|
||||
const { execute: fetchCategories, data: categories } = useFetchCategories();
|
||||
|
||||
const overviewEl = ref<HTMLElement | null>(null);
|
||||
const { width: containerWidth } = useElementSize(overviewEl);
|
||||
const { width: containerWidth } = useElementSize(overviewEl)
|
||||
|
||||
const columnSize = computed(() => {
|
||||
return Math.floor(containerWidth.value / (ICON_SIZE + ICON_GRID_GAP));
|
||||
return Math.floor((containerWidth.value) / ((ICON_SIZE + ICON_GRID_GAP)));
|
||||
});
|
||||
|
||||
const mappedIcons = computed(() => {
|
||||
@@ -72,27 +71,29 @@ const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
|
||||
{ name: 'tags', weight: 2 },
|
||||
{ name: 'categories', weight: 1 },
|
||||
]);
|
||||
const searchPlaceholder = useSearchPlaceholder(searchQuery, searchResults);
|
||||
|
||||
const chunkedIcons = computed(() => {
|
||||
return chunkArray(searchResults.value, columnSize.value);
|
||||
});
|
||||
|
||||
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(chunkedIcons, {
|
||||
itemHeight: ICON_SIZE + ICON_GRID_GAP,
|
||||
overscan: 10,
|
||||
});
|
||||
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
|
||||
chunkedIcons,
|
||||
{
|
||||
itemHeight: ICON_SIZE + ICON_GRID_GAP,
|
||||
overscan: 10
|
||||
},
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
containerProps.ref.value = document.documentElement;
|
||||
useEventListener(window, 'scroll', containerProps.onScroll);
|
||||
useEventListener(window, 'scroll', containerProps.onScroll)
|
||||
|
||||
// Check if we should focus the search input from URL parameter
|
||||
const route = useRoute();
|
||||
const route = useRoute()
|
||||
if (route.data?.relativePath && window.location.search.includes('focus')) {
|
||||
searchInput.value?.focus();
|
||||
searchInput.value?.focus()
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
function setActiveIconName(name: string) {
|
||||
activeIconName.value = name;
|
||||
@@ -112,8 +113,8 @@ const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
||||
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
|
||||
|
||||
watch(searchQueryDebounced, () => {
|
||||
scrollTo(0);
|
||||
});
|
||||
scrollTo(0)
|
||||
})
|
||||
|
||||
function handleCloseDrawer() {
|
||||
setActiveIconName('');
|
||||
@@ -123,10 +124,7 @@ function handleCloseDrawer() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="overviewEl"
|
||||
class="overview-container"
|
||||
>
|
||||
<div ref="overviewEl" class="overview-container">
|
||||
<StickyBar>
|
||||
<InputSearch
|
||||
:placeholder="`Search ${icons.length} icons ...`"
|
||||
@@ -138,9 +136,8 @@ function handleCloseDrawer() {
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="searchPlaceholder.isNoResults"
|
||||
:searchQuery="searchPlaceholder.query"
|
||||
:isBrandSearch="searchPlaceholder.isBrand"
|
||||
v-if="searchResults.length === 0 && searchQuery !== ''"
|
||||
:searchQuery="searchQuery"
|
||||
@clear="searchQuery = ''"
|
||||
/>
|
||||
<IconGrid
|
||||
@@ -185,5 +182,10 @@ function handleCloseDrawer() {
|
||||
|
||||
.input-wrapper {
|
||||
width: 100%;
|
||||
view-transition-name: icons-search-box;
|
||||
}
|
||||
|
||||
.overview-container {
|
||||
padding-bottom: 288px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,218 +1,56 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed, markRaw, shallowReadonly, watch } from 'vue';
|
||||
import {
|
||||
bird,
|
||||
squirrel,
|
||||
rabbit,
|
||||
ghost,
|
||||
castle,
|
||||
drama,
|
||||
dog,
|
||||
cat,
|
||||
wandSparkles,
|
||||
save,
|
||||
snowflake,
|
||||
cake,
|
||||
fish,
|
||||
turtle,
|
||||
rat,
|
||||
worm,
|
||||
testTubeDiagonal,
|
||||
sword,
|
||||
} 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';
|
||||
import { IconNode } from '../../types';
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { bird, squirrel, rabbit } 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'
|
||||
import { IconNode } from '../../types'
|
||||
|
||||
const { searchQuery, isBrandSearch } = defineProps<{
|
||||
searchQuery: string;
|
||||
isBrandSearch: boolean;
|
||||
}>();
|
||||
defineProps<{
|
||||
searchQuery: string
|
||||
}>()
|
||||
|
||||
defineEmits(['clear']);
|
||||
defineEmits(['clear'])
|
||||
|
||||
interface Placeholder {
|
||||
title: string;
|
||||
message: string;
|
||||
icon: IconNode;
|
||||
finePrint?: string;
|
||||
}
|
||||
|
||||
const brandPlaceholders: Placeholder[] = shallowReadonly([
|
||||
{
|
||||
title: 'Boooo! What a scary brand logo!',
|
||||
message:
|
||||
'[name] and its friends often haunt this search box, but you won’t ever find them here.',
|
||||
icon: markRaw(ghost),
|
||||
},
|
||||
{
|
||||
title: 'Thank You Mario!',
|
||||
message: 'But [name] is in another castle!',
|
||||
icon: markRaw(castle),
|
||||
},
|
||||
{
|
||||
title: '[name] did audition for our icon set',
|
||||
message: '...but didn’t make the callback.',
|
||||
icon: markRaw(drama),
|
||||
},
|
||||
{
|
||||
title: 'Such Search. Very [name].',
|
||||
message: 'Much not here. So Wow.',
|
||||
icon: markRaw(dog),
|
||||
},
|
||||
{
|
||||
title: 'I Can Has [name]?',
|
||||
message: 'No [name] for you in here.',
|
||||
icon: markRaw(cat),
|
||||
},
|
||||
{
|
||||
title: 'Loading [name]...',
|
||||
message: 'Fatal error: our cartridge contains only open-source pixels.',
|
||||
icon: markRaw(save),
|
||||
},
|
||||
{
|
||||
title: '[name] Shall Not Pass',
|
||||
message: 'Do not look to its coming at first light of any day.',
|
||||
icon: markRaw(wandSparkles),
|
||||
},
|
||||
{
|
||||
title: 'Winter is coming',
|
||||
message: 'But [name] sure isn’t.',
|
||||
icon: markRaw(snowflake),
|
||||
},
|
||||
{
|
||||
title: 'The cake is a lie',
|
||||
message: 'And so is the promise of an icon for [name] at Lucide.',
|
||||
icon: markRaw(cake),
|
||||
},
|
||||
{
|
||||
title: 'It’s not a bug',
|
||||
message: 'Having no [name] icon around is a feature.',
|
||||
icon: markRaw(worm),
|
||||
},
|
||||
{
|
||||
title: 'The lab exploded',
|
||||
message: 'We tried mixing [name] with open-source icons.',
|
||||
icon: markRaw(testTubeDiagonal),
|
||||
},
|
||||
{
|
||||
title: 'It’s Dangerous to Go Alone',
|
||||
message: 'Take this icon instead — it’s not [name], but it might help.',
|
||||
icon: markRaw(sword),
|
||||
},
|
||||
]);
|
||||
|
||||
const notFoundPlaceholders: Omit<Placeholder, 'title'>[] = shallowReadonly([
|
||||
{
|
||||
message: 'We’ve looked for this icon for a bird’s eye view, but could not find it.',
|
||||
icon: markRaw(bird),
|
||||
},
|
||||
{
|
||||
message: 'We checked every tree. Only acorns, no results.',
|
||||
icon: markRaw(squirrel),
|
||||
},
|
||||
{
|
||||
message: 'You’ve gone too deep into the rabbit hole — this icon doesn’t exist.',
|
||||
icon: markRaw(rabbit),
|
||||
},
|
||||
{
|
||||
message: 'This icon seems to have slipped through the net.',
|
||||
icon: markRaw(fish),
|
||||
},
|
||||
{
|
||||
message: 'This icon might exist in the future… but it hasn’t arrived yet.',
|
||||
icon: markRaw(turtle),
|
||||
},
|
||||
{
|
||||
message: 'Rats! This icon seems to have slipped through the cracks.',
|
||||
icon: markRaw(rat),
|
||||
},
|
||||
]);
|
||||
|
||||
function randomItem<T>(arr: T[]): T {
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
}
|
||||
|
||||
const placeholderIcon = ref<HTMLElement>();
|
||||
const placeholder = ref<Placeholder>();
|
||||
|
||||
watch(
|
||||
() => isBrandSearch,
|
||||
() => {
|
||||
placeholder.value = isBrandSearch
|
||||
? {
|
||||
...randomItem(brandPlaceholders),
|
||||
finePrint:
|
||||
'Lucide does not accept brand logos, and we do not plan to add them in the future. This is due to a combination of legal restrictions, design consistency concerns, and practical maintenance reasons.',
|
||||
}
|
||||
: {
|
||||
title: `No results for “[name]”`,
|
||||
finePrint:
|
||||
'This icon doesn’t seem to exist… yet. Try searching similar terms, browsing existing requests, or opening a new one.',
|
||||
...randomItem(notFoundPlaceholders),
|
||||
};
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
const iconComponent = computed(() => createLucideIcon('placeholder', placeholder.value.icon));
|
||||
const flip = ref(false);
|
||||
const animalIcon = ref<HTMLElement>()
|
||||
const randomAnimal = computed<IconNode>(() => {
|
||||
return Math.random() > 0.5 ? squirrel : Math.random() > 0.5 ? rabbit : bird
|
||||
})
|
||||
const animalComponent = computed(() => createLucideIcon('animal', randomAnimal.value))
|
||||
const flip = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
useEventListener(document, 'mousemove', (mouseEvent) => {
|
||||
const { width, x } = placeholderIcon.value.getBoundingClientRect();
|
||||
const {width, height, x, y} = animalIcon.value.getBoundingClientRect()
|
||||
|
||||
const centerX = width / 2 + x;
|
||||
const centerX = (width / 2) + x
|
||||
|
||||
flip.value = mouseEvent.x < centerX
|
||||
})
|
||||
})
|
||||
|
||||
flip.value = !isBrandSearch && mouseEvent.x < centerX;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="no-results">
|
||||
<component
|
||||
:is="iconComponent"
|
||||
class="placeholder-icon"
|
||||
ref="placeholderIcon"
|
||||
:is="animalComponent"
|
||||
class="animal-icon"
|
||||
ref="animalIcon"
|
||||
:class="{ flip }"
|
||||
:strokeWidth="1"
|
||||
/>
|
||||
<h2 class="no-results-text">{{ placeholder.title.replace('[name]', searchQuery) }}</h2>
|
||||
<p class="no-results-message">
|
||||
{{ placeholder.message.replace('[name]', searchQuery) }}
|
||||
</p>
|
||||
<div class="divider"></div>
|
||||
<p
|
||||
v-if="placeholder.finePrint"
|
||||
class="no-results-fine-print"
|
||||
>
|
||||
{{ placeholder.finePrint }}
|
||||
</p>
|
||||
<h2 class="no-results-text">
|
||||
No icons found for '{{ searchQuery }}'
|
||||
</h2>
|
||||
<VPButton
|
||||
v-if="isBrandSearch"
|
||||
text="Head over to Simple Icons"
|
||||
theme="brand"
|
||||
:href="`https://simpleicons.org/?q=${searchQuery}`"
|
||||
target="_blank"
|
||||
/>
|
||||
<VPButton
|
||||
v-else
|
||||
text="Clear search & try again"
|
||||
theme="brand"
|
||||
text="Clear your search and try again"
|
||||
theme="alt"
|
||||
@click="$emit('clear')"
|
||||
/>
|
||||
<span class="text-divider">or</span>
|
||||
<VPButton
|
||||
v-if="isBrandSearch"
|
||||
text="Read our statement on brand logos"
|
||||
theme="alt"
|
||||
href="https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md"
|
||||
target="_blank"
|
||||
/>
|
||||
<VPButton
|
||||
v-else
|
||||
text="Search GitHub issues"
|
||||
text="Search on Github issues"
|
||||
theme="alt"
|
||||
:href="`https://github.com/lucide-icons/lucide/issues?q=is%3Aopen+${searchQuery}`"
|
||||
target="_blank"
|
||||
@@ -225,38 +63,33 @@ onMounted(() => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding-block: 48px;
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
color: var(--vp-c-text-1);
|
||||
.animal-icon {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
color: var(--vp-c-neutral);
|
||||
opacity: 0.8;
|
||||
margin-top: 72px;
|
||||
}
|
||||
|
||||
.placeholder-icon.flip {
|
||||
.animal-icon.flip {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.animal-icon {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
}
|
||||
}
|
||||
|
||||
.no-results-text {
|
||||
line-height: 1.35;
|
||||
line-height: 40px;
|
||||
font-size: 24px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 8px;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.no-results-message {
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.no-results-fine-print {
|
||||
max-inline-size: 60ch;
|
||||
font-size: 14px;
|
||||
margin-bottom: 32px;
|
||||
color: var(--vp-c-text-2);
|
||||
text-wrap: balance;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-divider {
|
||||
@@ -264,10 +97,4 @@ onMounted(() => {
|
||||
font-size: 16px;
|
||||
color: var(--vp-c-neutral);
|
||||
}
|
||||
.divider {
|
||||
margin: 24px auto 18px;
|
||||
width: 64px;
|
||||
height: 1px;
|
||||
background-color: var(--vp-c-divider);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,72 +1,75 @@
|
||||
<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';
|
||||
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>;
|
||||
}>();
|
||||
rootEl?: Ref<HTMLElement>
|
||||
}>()
|
||||
|
||||
const { color, strokeWidth, size, absoluteStrokeWidth } = useIconStyleContext();
|
||||
const documentRef = shallowRef<HTMLElement | undefined>(
|
||||
typeof document !== 'undefined' ? document?.documentElement : undefined,
|
||||
);
|
||||
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 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}`,
|
||||
},
|
||||
);
|
||||
initialValue: `${STYLE_DEFAULTS.strokeWidth}`
|
||||
}
|
||||
)
|
||||
|
||||
const sizeCssVar = useCssVar('--customize-size', props.rootEl?.value ?? documentRef.value, {
|
||||
initialValue: `${STYLE_DEFAULTS.size}`,
|
||||
});
|
||||
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' });
|
||||
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;
|
||||
absoluteStrokeWidth.value = STYLE_DEFAULTS.absoluteStrokeWidth;
|
||||
function resetStyle () {
|
||||
color.value = STYLE_DEFAULTS.color
|
||||
strokeWidth.value = STYLE_DEFAULTS.strokeWidth
|
||||
size.value = STYLE_DEFAULTS.size
|
||||
absoluteStrokeWidth.value = STYLE_DEFAULTS.absoluteStrokeWidth
|
||||
}
|
||||
|
||||
watch(absoluteStrokeWidth, (enabled) => {
|
||||
const htmlEl = document.documentElement;
|
||||
const htmlEl = document.documentElement
|
||||
|
||||
htmlEl.classList.toggle('absolute-stroke-width', enabled);
|
||||
});
|
||||
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 ||
|
||||
absoluteStrokeWidth.value !== STYLE_DEFAULTS.absoluteStrokeWidth
|
||||
);
|
||||
});
|
||||
return color.value !== STYLE_DEFAULTS.color
|
||||
|| strokeWidth.value !== STYLE_DEFAULTS.strokeWidth
|
||||
|| size.value !== STYLE_DEFAULTS.size
|
||||
|| absoluteStrokeWidth.value !== STYLE_DEFAULTS.absoluteStrokeWidth
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="customizer-card"
|
||||
:class="{ customized: customizingActive }"
|
||||
>
|
||||
<div class="customizer-card" :class="{ customized: customizingActive }">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title">Customizer</h2>
|
||||
<h2 class="card-title">
|
||||
Customizer
|
||||
</h2>
|
||||
<ResetButton @click="resetStyle"></ResetButton>
|
||||
</div>
|
||||
<InputField
|
||||
@@ -74,11 +77,7 @@ const customizingActive = computed(() => {
|
||||
label="Color"
|
||||
>
|
||||
<template #display>
|
||||
<ColorPicker
|
||||
v-model="color"
|
||||
id="icon-color"
|
||||
class="color-picker"
|
||||
/>
|
||||
<ColorPicker v-model="color" id="icon-color" class="color-picker"/>
|
||||
</template>
|
||||
</InputField>
|
||||
|
||||
@@ -118,7 +117,7 @@ const customizingActive = computed(() => {
|
||||
|
||||
<InputField
|
||||
id="absolute-stroke-width"
|
||||
label="Absolute stroke width"
|
||||
label="Absolute Stroke width"
|
||||
>
|
||||
<Switch
|
||||
id="absolute-stroke-width"
|
||||
@@ -144,7 +143,6 @@ const customizingActive = computed(() => {
|
||||
font-size: 16px;
|
||||
/* margin-bottom: 12px; */
|
||||
}
|
||||
|
||||
.customizer-card {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 12px 24px 24px;
|
||||
@@ -153,7 +151,7 @@ const customizingActive = computed(() => {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
border: 1px solid transparent;
|
||||
transition: border-color 0.4s ease-in-out;
|
||||
transition: border-color .4s ease-in-out;
|
||||
}
|
||||
|
||||
.customizer-card.customized {
|
||||
|
||||
379
docs/.vitepress/theme/components/overrides/Hero.vue
Normal file
379
docs/.vitepress/theme/components/overrides/Hero.vue
Normal file
@@ -0,0 +1,379 @@
|
||||
<script setup lang="ts">
|
||||
import VPHero from 'vitepress/dist/client/theme-default/components/VPHero.vue'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||
import FakeInput from '../base/FakeInput.vue';
|
||||
import { useRouter } from 'vitepress';
|
||||
import { data } from '../home/HomeHeroIconsCard.data'
|
||||
import { useScroll } from '@vueuse/core';
|
||||
import { computed } from 'vue';
|
||||
const { go } = useRouter()
|
||||
|
||||
const { frontmatter: fm } = useData()
|
||||
const { x, y, isScrolling, arrivedState, directions } = useScroll(window)
|
||||
|
||||
const opacity = computed(() => {
|
||||
if (y.value < 0) return 1
|
||||
if (y.value > 300) return 0
|
||||
return 1 - (y.value / 300)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VPHero v-if="fm.hero" class="VPHomeHero" :name="fm.hero.name" :text="fm.hero.text" :tagline="fm.hero.tagline"
|
||||
:image="undefined" :actions="fm.hero.actions">
|
||||
<template #home-hero-image></template>
|
||||
<template #home-hero-actions-after>
|
||||
<FakeInput @click="go('/icons/?focus')" class="search-box">
|
||||
Search {{ data.iconsCount }} icons...
|
||||
</FakeInput>
|
||||
</template>
|
||||
|
||||
</VPHero>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-12 -12 48 48" fill="none" overflow="auto"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="hero-background" :style="{ opacity: opacity }">
|
||||
<g class="svg-preview-grid-group" stroke-linecap="butt" stroke-width="0.1" stroke="#777"
|
||||
stroke-opacity="0.3">
|
||||
<!-- <rect class="svg-preview-grid-rect" width="23.9" height="23.9" x="0.05" y="0.05" rx="1"></rect> -->
|
||||
<path
|
||||
stroke-dasharray="0 0.1 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0 0.15"
|
||||
stroke-width="0.1"
|
||||
d="M1 0.1v23.8M2 0.1v23.8M4 0.1v23.8M5 0.1v23.8M7 0.1v23.8M8 0.1v23.8M10 0.1v23.8M11 0.1v23.8M13 0.1v23.8M14 0.1v23.8M16 0.1v23.8M17 0.1v23.8M19 0.1v23.8M20 0.1v23.8M22 0.1v23.8M23 0.1v23.8M0.1 1h23.8M0.1 2h23.8M0.1 4h23.8M0.1 5h23.8M0.1 7h23.8M0.1 8h23.8M0.1 10h23.8M0.1 11h23.8M0.1 13h23.8M0.1 14h23.8M0.1 16h23.8M0.1 17h23.8M0.1 19h23.8M0.1 20h23.8M0.1 22h23.8M0.1 23h23.8">
|
||||
</path>
|
||||
<path
|
||||
d="M3 0.1v23.8M6 0.1v23.8M9 0.1v23.8M12 0.1v23.8M15 0.1v23.8M18 0.1v23.8M21 0.1v23.8M0.1 3h23.8M0.1 6h23.8M0.1 9h23.8M0.1 12h23.8M0.1 15h23.8M0.1 18h23.8M0.1 21h23.8">
|
||||
</path>
|
||||
</g>
|
||||
<g class="svg-preview-shadow-mask-group" stroke-width="4" stroke="#777" stroke-opacity="0.15">
|
||||
<mask id="svg-preview-shadow-mask-0" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
|
||||
<path d="M4.9 16.1h.01M4.9 1.9h.01"></path>
|
||||
</mask>
|
||||
<mask id="svg-preview-shadow-mask-1" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
|
||||
<path d="M7.8 4.7h.01M7 12.2h.01"></path>
|
||||
</mask>
|
||||
<mask id="svg-preview-shadow-mask-2" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
|
||||
<path d="M12 7h.01M14 9h.01M12 11h.01M10 9h.01"></path>
|
||||
</mask>
|
||||
<mask id="svg-preview-shadow-mask-3" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
|
||||
<path d="M16.2 4.8h.01M17 12.27h.01"></path>
|
||||
</mask>
|
||||
<mask id="svg-preview-shadow-mask-4" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
|
||||
<path d="M19.1 1.9h.01M19.1 16h.01"></path>
|
||||
</mask>
|
||||
<mask id="svg-preview-shadow-mask-5" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
|
||||
<path d="M9.5 18h.01M14.5 18h.01"></path>
|
||||
</mask>
|
||||
<mask id="svg-preview-shadow-mask-6" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
|
||||
<path d="M8 22h.01M12 11h.01M16 22h.01"></path>
|
||||
</mask>
|
||||
</g>
|
||||
<!-- <g class="svg-preview-shadow-group" stroke-width="4" stroke="#777" stroke-opacity="0.15">
|
||||
<path mask="url(#svg-preview-shadow-mask-0)" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
|
||||
<path mask="url(#svg-preview-shadow-mask-1)" d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2"></path>
|
||||
<path mask="url(#svg-preview-shadow-mask-2)" d="M 12 7 A2 2 0 0 1 14 9"></path>
|
||||
<path mask="url(#svg-preview-shadow-mask-2)" d="M 14 9 A2 2 0 0 1 12 11"></path>
|
||||
<path mask="url(#svg-preview-shadow-mask-2)" d="M 12 11 A2 2 0 0 1 10 9"></path>
|
||||
<path mask="url(#svg-preview-shadow-mask-2)" d="M 10 9 A2 2 0 0 1 12 7"></path>
|
||||
<path mask="url(#svg-preview-shadow-mask-3)" d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27"></path>
|
||||
<path mask="url(#svg-preview-shadow-mask-4)" d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16"></path>
|
||||
<path mask="url(#svg-preview-shadow-mask-5)" d="M 9.5 18 L 14.5 18"></path>
|
||||
<path mask="url(#svg-preview-shadow-mask-6)" d="M 8 22 L 12 11"></path>
|
||||
<path mask="url(#svg-preview-shadow-mask-6)" d="M 12 11 L 16 22"></path>
|
||||
<path
|
||||
d="M4.9 16.1h.01M4.9 1.9h.01M7.8 4.7h.01M7 12.2h.01M12 7h.01M14 9h.01M12 11h.01M10 9h.01M16.2 4.8h.01M17 12.27h.01M19.1 1.9h.01M19.1 16h.01M9.5 18h.01M14.5 18h.01M8 22h.01M16 22h.01">
|
||||
</path>
|
||||
</g> -->
|
||||
<g>
|
||||
<defs xmlns="http://www.w3.org/2000/svg">
|
||||
<pattern id="backdrop-pattern-:R4:" width=".1" height=".1" patternUnits="userSpaceOnUse"
|
||||
patternTransform="rotate(45 50 50)">
|
||||
<line stroke="red" stroke-width="0.1" y2="1"></line>
|
||||
<line stroke="red" stroke-width="0.1" y2="1"></line>
|
||||
</pattern>
|
||||
</defs>
|
||||
<g stroke-width="4">
|
||||
<mask id="svg-preview-backdrop-mask-:R4:-0" maskUnits="userSpaceOnUse">
|
||||
<path stroke="white" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
|
||||
</mask>
|
||||
<path d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
|
||||
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-0)"></path>
|
||||
</g>
|
||||
<g stroke-width="4">
|
||||
<mask id="svg-preview-backdrop-mask-:R4:-0" maskUnits="userSpaceOnUse">
|
||||
<path stroke="white" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
|
||||
</mask>
|
||||
<path
|
||||
d="M 12 7 A2 2 0 0 1 14 9 M 14 9 A2 2 0 0 1 12 11 M 12 11 A2 2 0 0 1 10 9 M 10 9 A2 2 0 0 1 12 7 M 8 22 L 12 11 M 12 11 L 16 22 M 9.5 18 L 14.5 18"
|
||||
stroke="url(#backdrop-pattern-:R4:)" stroke-width="4" stroke-opacity="0.75"
|
||||
mask="url(#svg-preview-backdrop-mask-:R4:-0)"></path>
|
||||
</g>
|
||||
<g stroke-width="4">
|
||||
<mask id="svg-preview-backdrop-mask-:R4:-1" maskUnits="userSpaceOnUse">
|
||||
<path stroke="white" d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2"></path>
|
||||
</mask>
|
||||
<path
|
||||
d="M 12 7 A2 2 0 0 1 14 9 M 14 9 A2 2 0 0 1 12 11 M 12 11 A2 2 0 0 1 10 9 M 10 9 A2 2 0 0 1 12 7 M 8 22 L 12 11 M 12 11 L 16 22 M 9.5 18 L 14.5 18"
|
||||
stroke="url(#backdrop-pattern-:R4:)" stroke-width="4" stroke-opacity="0.75"
|
||||
mask="url(#svg-preview-backdrop-mask-:R4:-1)"></path>
|
||||
</g>
|
||||
<g stroke-width="4">
|
||||
<mask id="svg-preview-backdrop-mask-:R4:-0" maskUnits="userSpaceOnUse">
|
||||
<path stroke="white" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
|
||||
</mask>
|
||||
<path d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
|
||||
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-0)"></path>
|
||||
</g>
|
||||
<g stroke-width="4">
|
||||
<mask id="svg-preview-backdrop-mask-:R4:-1" maskUnits="userSpaceOnUse">
|
||||
<path stroke="white" d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2"></path>
|
||||
</mask>
|
||||
<path d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
|
||||
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-1)"></path>
|
||||
</g>
|
||||
<g stroke-width="4">
|
||||
<mask id="svg-preview-backdrop-mask-:R4:-2" maskUnits="userSpaceOnUse">
|
||||
<path stroke="white"
|
||||
d="M 12 7 A2 2 0 0 1 14 9 M 14 9 A2 2 0 0 1 12 11 M 12 11 A2 2 0 0 1 10 9 M 10 9 A2 2 0 0 1 12 7 M 8 22 L 12 11 M 12 11 L 16 22 M 9.5 18 L 14.5 18">
|
||||
</path>
|
||||
</mask>
|
||||
<path d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
|
||||
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-2)"></path>
|
||||
</g>
|
||||
<g stroke-width="4">
|
||||
<mask id="svg-preview-backdrop-mask-:R4:-0" maskUnits="userSpaceOnUse">
|
||||
<path stroke="white" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
|
||||
</mask>
|
||||
<path d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
|
||||
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-0)"></path>
|
||||
</g>
|
||||
<g stroke-width="4">
|
||||
<mask id="svg-preview-backdrop-mask-:R4:-1" maskUnits="userSpaceOnUse">
|
||||
<path stroke="white" d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2"></path>
|
||||
</mask>
|
||||
<path d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
|
||||
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-1)"></path>
|
||||
</g>
|
||||
<g stroke-width="4">
|
||||
<mask id="svg-preview-backdrop-mask-:R4:-2" maskUnits="userSpaceOnUse">
|
||||
<path stroke="white"
|
||||
d="M 12 7 A2 2 0 0 1 14 9 M 14 9 A2 2 0 0 1 12 11 M 12 11 A2 2 0 0 1 10 9 M 10 9 A2 2 0 0 1 12 7 M 8 22 L 12 11 M 12 11 L 16 22 M 9.5 18 L 14.5 18">
|
||||
</path>
|
||||
</mask>
|
||||
<path d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
|
||||
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-2)"></path>
|
||||
</g>
|
||||
<g stroke-width="4">
|
||||
<mask id="svg-preview-backdrop-mask-:R4:-3" maskUnits="userSpaceOnUse">
|
||||
<path stroke="white" d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27"></path>
|
||||
</mask>
|
||||
<path d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
|
||||
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-3)"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g class="svg-preview-handles-group" stroke-width="0.12" stroke="#777" stroke-opacity="0.6">
|
||||
<path d="M4.9 16.1 1 12.2"></path>
|
||||
<circle cy="12.2" cx="1" r="0.25"></circle>
|
||||
<path d="M4.9 1.9 1 5.8"></path>
|
||||
<circle cy="5.8" cx="1" r="0.25"></circle>
|
||||
<path d="M16.2 4.8 18.2 6.8"></path>
|
||||
<circle cy="6.8" cx="18.2" r="0.25"></circle>
|
||||
<path d="M17 12.27 18.46 9.91"></path>
|
||||
<circle cy="9.91" cx="18.46" r="0.25"></circle>
|
||||
</g>
|
||||
<g class="svg-preview-colored-path-group">
|
||||
<path d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9" stroke="##dfdfd6"></path>
|
||||
<path d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2" stroke="##dfdfd6"></path>
|
||||
<path d="M 12 7 A2 2 0 0 1 14 9" stroke="##dfdfd6"></path>
|
||||
<path d="M 14 9 A2 2 0 0 1 12 11" stroke="##dfdfd6"></path>
|
||||
<path d="M 12 11 A2 2 0 0 1 10 9" stroke="##dfdfd6"></path>
|
||||
<path d="M 10 9 A2 2 0 0 1 12 7" stroke="##dfdfd6"></path>
|
||||
<path d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27" stroke="##dfdfd6"></path>
|
||||
<path d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16" stroke="##dfdfd6"></path>
|
||||
<path d="M 9.5 18 L 14.5 18" stroke="##dfdfd6"></path>
|
||||
<path d="M 8 22 L 12 11" stroke="##dfdfd6"></path>
|
||||
<path d="M 12 11 L 16 22" stroke="##dfdfd6"></path>
|
||||
</g>
|
||||
<g class="svg-preview-radii-group" stroke-width="0.12" stroke-dasharray="0 0.25 0.25" stroke="#777"
|
||||
stroke-opacity="0.3">
|
||||
<circle cx="9.518750780437157" cy="16.261333416579962" r="0.25"></circle>
|
||||
<circle cx="11.118750780437157" cy="1.261333416579964" r="0.25"></circle>
|
||||
<path
|
||||
d="M9.518750780437157 16.261333416579962L4.481249219562843 8.138666583420036L11.118750780437157 1.261333416579964">
|
||||
</path>
|
||||
<circle cx="4.481249219562843" cy="8.138666583420036" r="0.25"></circle>
|
||||
<path d="M7 12.2L12.217985863765243 8.963918492134958L7.8 4.7"></path>
|
||||
<circle cy="8.963918492134958" cx="12.217985863765243" r="0.25" stroke-dasharray="0" stroke="red"></circle>
|
||||
<circle cy="8.963918492134958" cx="12.217985863765243" r="6.14" stroke="red"></circle>
|
||||
<circle cy="9" cx="12" r="0.25" stroke-dasharray="0"></circle>
|
||||
<circle cy="9" cx="12" r="2"></circle>
|
||||
<circle cy="9" cx="12" r="0.25" stroke-dasharray="0"></circle>
|
||||
<circle cy="9" cx="12" r="2"></circle>
|
||||
<circle cy="9" cx="12" r="0.25" stroke-dasharray="0"></circle>
|
||||
<circle cy="9" cx="12" r="2"></circle>
|
||||
<circle cy="9" cx="12" r="0.25" stroke-dasharray="0"></circle>
|
||||
<circle cy="9" cx="12" r="2"></circle>
|
||||
<circle cx="12.035530040810755" cy="23.05" r="0.25"></circle>
|
||||
<circle cx="12.035530040810755" cy="-5.1499999999999995" r="0.25"></circle>
|
||||
<path d="M12.035530040810755 23.05L26.164469959189248 8.95L12.035530040810755 -5.1499999999999995"></path>
|
||||
<circle cx="26.164469959189248" cy="8.95" r="0.25"></circle>
|
||||
<path d="M19.1 16L12.064440320770494 8.95L19.1 1.9"></path>
|
||||
<circle cy="8.95" cx="12.064440320770494" r="0.25" stroke-dasharray="0" stroke="red"></circle>
|
||||
<circle cy="8.95" cx="12.064440320770494" r="9.96" stroke="red"></circle>
|
||||
</g>
|
||||
<g class="svg-preview-control-path-marker-mask-group" stroke-width="1" stroke="#000">
|
||||
<mask id="svg-preview-control-path-marker-mask-0" maskUnits="userSpaceOnUse">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
|
||||
<path d="M4.9 16.1h.01"></path>
|
||||
<path d="M4.9 1.9h.01"></path>
|
||||
</mask>
|
||||
<mask id="svg-preview-control-path-marker-mask-1" maskUnits="userSpaceOnUse">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
|
||||
<path d="M7.8 4.7h.01"></path>
|
||||
<path d="M7 12.2h.01"></path>
|
||||
</mask>
|
||||
<mask id="svg-preview-control-path-marker-mask-6" maskUnits="userSpaceOnUse">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
|
||||
<path d="M16.2 4.8h.01"></path>
|
||||
<path d="M17 12.27h.01"></path>
|
||||
</mask>
|
||||
<mask id="svg-preview-control-path-marker-mask-7" maskUnits="userSpaceOnUse">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
|
||||
<path d="M19.1 1.9h.01"></path>
|
||||
<path d="M19.1 16h.01"></path>
|
||||
</mask>
|
||||
<mask id="svg-preview-control-path-marker-mask-8" maskUnits="userSpaceOnUse">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
|
||||
<path d="M9.5 18h.01"></path>
|
||||
<path d="M14.5 18h.01"></path>
|
||||
</mask>
|
||||
<mask id="svg-preview-control-path-marker-mask-9" maskUnits="userSpaceOnUse">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
|
||||
<path d="M8 22h.01"></path>
|
||||
<path d="M12 11h.01"></path>
|
||||
</mask>
|
||||
<mask id="svg-preview-control-path-marker-mask-10" maskUnits="userSpaceOnUse">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
|
||||
<path d="M12 11h.01"></path>
|
||||
<path d="M16 22h.01"></path>
|
||||
</mask>
|
||||
</g>
|
||||
<g class="svg-preview-control-path-group" stroke="#fff" stroke-width="0.125">
|
||||
<path mask="url(#svg-preview-control-path-marker-mask-0)" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
|
||||
<path mask="url(#svg-preview-control-path-marker-mask-1)" d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2"></path>
|
||||
<path d="M 12 7 A2 2 0 0 1 14 9"></path>
|
||||
<path d="M 14 9 A2 2 0 0 1 12 11"></path>
|
||||
<path d="M 12 11 A2 2 0 0 1 10 9"></path>
|
||||
<path d="M 10 9 A2 2 0 0 1 12 7"></path>
|
||||
<path mask="url(#svg-preview-control-path-marker-mask-6)" d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27"></path>
|
||||
<path mask="url(#svg-preview-control-path-marker-mask-7)" d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16"></path>
|
||||
<path mask="url(#svg-preview-control-path-marker-mask-8)" d="M 9.5 18 L 14.5 18"></path>
|
||||
<path mask="url(#svg-preview-control-path-marker-mask-9)" d="M 8 22 L 12 11"></path>
|
||||
<path mask="url(#svg-preview-control-path-marker-mask-10)" d="M 12 11 L 16 22"></path>
|
||||
</g>
|
||||
<g class="svg-preview-control-path-marker-group" stroke="#fff" stroke-width="0.125">
|
||||
<path
|
||||
d="M4.9 16.1h.01M4.9 1.9h.01M7.8 4.7h.01M7 12.2h.01M16.2 4.8h.01M17 12.27h.01M19.1 1.9h.01M19.1 16h.01M9.5 18h.01M14.5 18h.01M8 22h.01M12 11h.01M12 11h.01M16 22h.01">
|
||||
</path>
|
||||
<circle cx="4.9" cy="16.1" r="0.5"></circle>
|
||||
<circle cx="4.9" cy="1.9" r="0.5"></circle>
|
||||
<circle cx="7.8" cy="4.7" r="0.5"></circle>
|
||||
<circle cx="7" cy="12.2" r="0.5"></circle>
|
||||
<circle cx="16.2" cy="4.8" r="0.5"></circle>
|
||||
<circle cx="17" cy="12.27" r="0.5"></circle>
|
||||
<circle cx="19.1" cy="1.9" r="0.5"></circle>
|
||||
<circle cx="19.1" cy="16" r="0.5"></circle>
|
||||
<circle cx="9.5" cy="18" r="0.5"></circle>
|
||||
<circle cx="14.5" cy="18" r="0.5"></circle>
|
||||
<circle cx="8" cy="22" r="0.5"></circle>
|
||||
<circle cx="16" cy="22" r="0.5"></circle>
|
||||
</g>
|
||||
<g class="svg-preview-handles-group" stroke-width="0.12" style="stroke: var(--vp-c-brand)" stroke-opacity="0.3">
|
||||
<path d="M4.9 16.1 1 12.2"></path>
|
||||
<circle cy="12.2" cx="1" r="0.25"></circle>
|
||||
<path d="M4.9 1.9 1 5.8"></path>
|
||||
<circle cy="5.8" cx="1" r="0.25"></circle>
|
||||
<path d="M16.2 4.8 18.2 6.8"></path>
|
||||
<circle cy="6.8" cx="18.2" r="0.25"></circle>
|
||||
<path d="M17 12.27 18.46 9.91"></path>
|
||||
<circle cy="9.91" cx="18.46" r="0.25"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
overflow: hidden;
|
||||
height: 60vh;
|
||||
display: flex;
|
||||
position: relative;
|
||||
/* align-items: center;
|
||||
justify-content: center; */
|
||||
}
|
||||
|
||||
.hero-background {
|
||||
transform: rotateX(-51deg) rotateZ(-43deg);
|
||||
transform-style: preserve-3d;
|
||||
position: fixed;
|
||||
top: -240px;
|
||||
right: -320px;
|
||||
width: 112vw;
|
||||
height: 112vh;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 3.2rem;
|
||||
line-height: 1.2;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
/* .VPHomeHero .image {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.VPHomeHero .container {
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.VPHomeHero .main {
|
||||
text-align: center;
|
||||
} */
|
||||
|
||||
/* .VPHomeHero .container .actions {
|
||||
justify-content: center;
|
||||
} */
|
||||
/*
|
||||
@media (min-width: 960px) {
|
||||
.VPHomeHero :deep(.actions) {
|
||||
justify-content: center;
|
||||
}
|
||||
} */
|
||||
|
||||
@media screen and (prefers-color-scheme: light) {
|
||||
.svg-preview-grid-rect { fill: none }
|
||||
}
|
||||
@media screen and (prefers-color-scheme: dark) {
|
||||
.svg-preview-grid-rect { fill: none }
|
||||
.svg
|
||||
.svg-preview-grid-group,
|
||||
.svg-preview-radii-group,
|
||||
.svg-preview-shadow-mask-group,
|
||||
.svg-preview-shadow-group {
|
||||
stroke: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.search-box {
|
||||
/* width: calc(100vw - 272px); */
|
||||
width: 100%;
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
||||
248
docs/.vitepress/theme/components/overrides/HeroMatrix.vue
Normal file
248
docs/.vitepress/theme/components/overrides/HeroMatrix.vue
Normal file
@@ -0,0 +1,248 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount, type Component } from 'vue';
|
||||
import {
|
||||
heart,
|
||||
star,
|
||||
zap,
|
||||
code,
|
||||
feather,
|
||||
cloud,
|
||||
sun,
|
||||
moon,
|
||||
camera,
|
||||
music,
|
||||
video,
|
||||
globe,
|
||||
layers,
|
||||
package as packageIcon,
|
||||
compass,
|
||||
command,
|
||||
terminal,
|
||||
database,
|
||||
server,
|
||||
cpu,
|
||||
lock,
|
||||
key,
|
||||
shield,
|
||||
wifi,
|
||||
download,
|
||||
upload,
|
||||
search,
|
||||
settings,
|
||||
users,
|
||||
mail,
|
||||
bell,
|
||||
calendar,
|
||||
clock,
|
||||
gitBranch,
|
||||
funnel,
|
||||
bookmark,
|
||||
tag,
|
||||
sparkles,
|
||||
} from '../../../data/iconNodes';
|
||||
import createElement from 'lucide/src/createElement';
|
||||
import { IconNode } from 'lucide';
|
||||
|
||||
const icons: Component[] = [
|
||||
heart, star, zap, code, feather, cloud, sun, moon, camera, music,
|
||||
video, globe, layers, packageIcon, compass, command, terminal, database,
|
||||
server, cpu, lock, key, shield, wifi, download, upload, search,
|
||||
settings, users, mail, bell, calendar, clock, gitBranch, funnel,
|
||||
bookmark, tag, sparkles,
|
||||
];
|
||||
|
||||
const svgs = icons.map(icon => {
|
||||
const element = createElement(icon as IconNode, {
|
||||
stroke: 'white',
|
||||
opacity: '0.2',
|
||||
})
|
||||
|
||||
return element.outerHTML;
|
||||
});
|
||||
|
||||
const highlightedSvgs = icons.map(icon => {
|
||||
const element = createElement(icon as IconNode, {
|
||||
stroke: 'white',
|
||||
})
|
||||
|
||||
return element.outerHTML;
|
||||
});
|
||||
|
||||
const images = svgs.map(svg => {
|
||||
const img = new Image();
|
||||
img.src = `data:image/svg+xml;base64,${btoa(svg)}`;
|
||||
return img;
|
||||
});
|
||||
|
||||
const highlightedImages = highlightedSvgs.map(svg => {
|
||||
const img = new Image();
|
||||
img.src = `data:image/svg+xml;base64,${btoa(svg)}`;
|
||||
return img;
|
||||
});
|
||||
|
||||
const canvas = ref<HTMLCanvasElement | null>(null);
|
||||
|
||||
// Setting up the columns
|
||||
const fontSize = 16;
|
||||
const gap = 40;
|
||||
const gapY = 8;
|
||||
const intervalTime = ref<number | null>(null);
|
||||
|
||||
// Store individual drops with their positions
|
||||
let individualDrops: Array<{x: number, y: number, active: boolean}> = [];
|
||||
|
||||
// Random integer between min and max
|
||||
function randInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!canvas.value) return;
|
||||
|
||||
const ctx = canvas.value.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
const width = window.innerWidth;
|
||||
const height = window.innerHeight * 0.6;
|
||||
canvas.value.width = width;
|
||||
canvas.value.height = height;
|
||||
|
||||
let columns = Math.floor(width / 120);
|
||||
|
||||
var rows = Math.floor(canvas.value.height / fontSize);
|
||||
var midStart = Math.floor(rows * 0.25);
|
||||
var midEnd = Math.floor(rows * 0.85);
|
||||
|
||||
let drops = Array.from({ length: columns }, () => randInt(midStart, midEnd));
|
||||
let fps, fpsInterval, startTime, now, then, elapsed;
|
||||
|
||||
// Add click event listener
|
||||
function handleCanvasClick(event: MouseEvent) {
|
||||
const rect = canvas.value?.getBoundingClientRect();
|
||||
if (!rect) return;
|
||||
|
||||
const x = event.clientX - rect.left;
|
||||
const y = event.clientY - rect.top;
|
||||
|
||||
individualDrops.push({
|
||||
x: x,
|
||||
y: y,
|
||||
active: true
|
||||
});
|
||||
}
|
||||
|
||||
canvas.value.addEventListener('click', handleCanvasClick);
|
||||
|
||||
function draw() {
|
||||
if (!ctx) return;
|
||||
|
||||
ctx.fillStyle = 'rgba(27, 27, 31, 0.50)';
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
|
||||
|
||||
// Draw regular drops
|
||||
for (var i = drops.length; i > 0; i--) {
|
||||
var img = images[Math.floor(Math.random() * images.length)];
|
||||
ctx.drawImage(img, i * fontSize * 2 + gap, drops[i] * fontSize, (fontSize / 2) + gapY, (fontSize / 2) + gapY);
|
||||
|
||||
drops[i]++;
|
||||
if (Math.random() > .98) {
|
||||
drops[i] = randInt(midStart, midEnd);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw and update individual drops
|
||||
individualDrops = individualDrops.filter(drop => {
|
||||
if (!drop.active) return false;
|
||||
|
||||
var img = highlightedImages[Math.floor(Math.random() * images.length)];
|
||||
ctx.drawImage(img, drop.x - fontSize/2, drop.y, fontSize, fontSize);
|
||||
|
||||
drop.y += fontSize
|
||||
|
||||
// Remove if off screen
|
||||
if (drop.y > height) {
|
||||
drop.active = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function startAnimating(fps) {
|
||||
fpsInterval = 1000 / fps;
|
||||
then = Date.now();
|
||||
startTime = then;
|
||||
animate();
|
||||
}
|
||||
function animate() {
|
||||
|
||||
// request another frame
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
// calc elapsed time since last loop
|
||||
|
||||
now = Date.now();
|
||||
elapsed = now - then;
|
||||
|
||||
// if enough time has elapsed, draw the next frame
|
||||
|
||||
if (elapsed > fpsInterval) {
|
||||
|
||||
// Get ready for next frame by setting then=now, but also adjust for your
|
||||
// specified fpsInterval not being a multiple of RAF's interval (16.7ms)
|
||||
then = now - (elapsed % fpsInterval);
|
||||
|
||||
// Put your drawing code here
|
||||
draw();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
startAnimating(12);
|
||||
|
||||
window.addEventListener('resize', function() {
|
||||
canvas.value.width = window.innerWidth;
|
||||
canvas.value.height = window.innerHeight;
|
||||
rows = Math.floor(canvas.value.height / fontSize);
|
||||
columns = Math.floor(canvas.value.width / fontSize);
|
||||
|
||||
var oldDrops = drops.slice();
|
||||
drops = [];
|
||||
for (var i = 0; i < columns; i++) {
|
||||
drops[i] = oldDrops[i % oldDrops.length] ?? randInt(midStart, midEnd);
|
||||
}
|
||||
});
|
||||
|
||||
// Cleanup event listener
|
||||
onBeforeUnmount(() => {
|
||||
canvas.value?.removeEventListener('click', handleCanvasClick);
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="hero">
|
||||
<canvas ref="canvas" class="hero-canvas" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.hero {
|
||||
overflow: hidden;
|
||||
height: 60vh;
|
||||
}
|
||||
|
||||
.hero-canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 60vh;
|
||||
filter: blur(0px);
|
||||
}
|
||||
</style>
|
||||
@@ -1,27 +1,36 @@
|
||||
import { h } from 'vue';
|
||||
import { h, nextTick, onMounted, watch } from 'vue';
|
||||
import DefaultTheme from 'vitepress/theme';
|
||||
import './style.css';
|
||||
import { Theme } from 'vitepress';
|
||||
import 'virtual:group-icons.css'
|
||||
import { Theme, useRouter } from 'vitepress';
|
||||
import IconsSidebarNavAfter from './layouts/IconsSidebarNavAfter.vue';
|
||||
import HomeHeroIconsCard from './components/home/HomeHeroIconsCard.vue';
|
||||
import HomeHeroBefore from './components/home/HomeHeroBefore.vue';
|
||||
import HomeHeroAfter from './components/home/HomeHeroAfter.vue';
|
||||
import HomeHeroInfoBefore from './components/home/HomeHeroInfoBefore.vue';
|
||||
import { ICON_STYLE_CONTEXT, iconStyleContext } from './composables/useIconStyle';
|
||||
import { CATEGORY_VIEW_CONTEXT, categoryViewContext } from './composables/useCategoryView';
|
||||
import { EXTERNAL_LIBS_CONTEXT, externalLibContext } from './composables/useExternalLibs';
|
||||
import FrameworkSelect from './components/guide/FrameworkSelect.vue';
|
||||
import SnackPlayer from './components/editors/SnackPlayer.vue';
|
||||
import Sandbox from './components/editors/Sandbox.vue';
|
||||
|
||||
const theme: Partial<Theme> = {
|
||||
extends: DefaultTheme,
|
||||
Layout() {
|
||||
return h(DefaultTheme.Layout, null, {
|
||||
'home-hero-before': () => h(HomeHeroBefore),
|
||||
'sidebar-nav-before': () => h(FrameworkSelect),
|
||||
'home-hero-info-before': () => h(HomeHeroInfoBefore),
|
||||
'sidebar-nav-after': () => h(IconsSidebarNavAfter),
|
||||
'home-hero-image': () => h(HomeHeroIconsCard),
|
||||
'home-hero-actions-after': () => h(HomeHeroAfter),
|
||||
});
|
||||
},
|
||||
enhanceApp({ app }) {
|
||||
app.provide(ICON_STYLE_CONTEXT, iconStyleContext);
|
||||
app.provide(CATEGORY_VIEW_CONTEXT, categoryViewContext);
|
||||
app.provide(EXTERNAL_LIBS_CONTEXT, externalLibContext);
|
||||
app.component('SnackPlayer', SnackPlayer)
|
||||
app.component('Sandbox', Sandbox);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -26,6 +26,12 @@
|
||||
|
||||
--vp-c-text-4: rgba(60, 60, 67, 0.32);
|
||||
--vp-home-hero-name-color: var(--vp-c-text);
|
||||
|
||||
--vp-a11y-danger: var(--vp-c-danger-3);
|
||||
--vp-a11y-success: var(--vp-c-success-3);
|
||||
--vp-a11y-warning: #da9200;
|
||||
|
||||
--vp-sidebar-input: var(--vp-c-gray-3);
|
||||
}
|
||||
|
||||
.dark {
|
||||
@@ -43,8 +49,23 @@
|
||||
--vp-code-editor-string: #9ecbff;
|
||||
|
||||
--vp-c-text-4: rgba(235, 235, 245, 0.16);
|
||||
|
||||
--vp-a11y-danger: var(--vp-c-danger-2);
|
||||
--vp-a11y-success: var(--vp-c-success-2);
|
||||
--vp-a11y-warning: var(--vp-c-warning-2);
|
||||
|
||||
--vp-sidebar-input: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
@view-transition {
|
||||
navigation: auto;
|
||||
}
|
||||
|
||||
/* ::view-transition-old(icons-search-box),
|
||||
::view-transition-new(icons-search-box) {
|
||||
animation: .3s transition 0s ease;
|
||||
} */
|
||||
|
||||
.VPNavBarTitle .logo {
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
@@ -70,7 +91,7 @@
|
||||
.VPHomeHero .container .image {
|
||||
margin: 0;
|
||||
order: 2;
|
||||
margin-top: 32px;
|
||||
/* margin-top: 32px; */
|
||||
}
|
||||
|
||||
.VPHomeHero .container .image-container {
|
||||
@@ -91,6 +112,10 @@
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.VPHomeHero .container {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPHomeHero .container .image {
|
||||
order: 1;
|
||||
@@ -110,60 +135,12 @@
|
||||
.VPHomeHero .container .image-container {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.VPHomeHero .container {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.VPNavBarHamburger .container > span {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.sp-wrapper + * {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.sp-wrapper .sp-layout {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.sp-wrapper .sp-tabs-scrollable-container {
|
||||
border-radius: 8px 8px 0 0;
|
||||
position: relative;
|
||||
|
||||
box-shadow: inset 0 -1px var(--vp-code-tab-divider);
|
||||
margin-bottom: 0px;
|
||||
margin-top: -1px;
|
||||
height: 48px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.sp-wrapper .sp-preview-container {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.sp-wrapper .sp-tabs .sp-tab-button {
|
||||
padding: 0 12px;
|
||||
line-height: 48px;
|
||||
height: 48px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sp-wrapper .sp-tabs .sp-tab-button:after {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
left: 8px;
|
||||
bottom: 0px;
|
||||
z-index: 1;
|
||||
height: 1px;
|
||||
content: '';
|
||||
background-color: transparent;
|
||||
transition: background-color 0.25s;
|
||||
}
|
||||
|
||||
.sp-wrapper .sp-tabs .sp-tab-button[data-active='true'] {
|
||||
color: var(--vp-code-tab-active-text-color);
|
||||
}
|
||||
|
||||
.sp-wrapper .sp-tabs .sp-tab-button[data-active='true']:after {
|
||||
background-color: var(--vp-code-tab-active-bar-color);
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { ref, Ref, watch } from 'vue';
|
||||
import BRAND_STOPWORDS from '../../data/brandStopwords.json' with { type: 'json' };
|
||||
|
||||
export default function useSearchPlaceholder(
|
||||
searchQuery: Ref<string, string>,
|
||||
results: Ref<{ name: string }[]>,
|
||||
) {
|
||||
const state = ref({
|
||||
isNoResults: false,
|
||||
isBrand: false,
|
||||
query: '',
|
||||
});
|
||||
|
||||
watch(
|
||||
results,
|
||||
() => {
|
||||
const query = searchQuery.value;
|
||||
const searchResults = results.value;
|
||||
if (query.length > 0 && searchResults.length === 0) {
|
||||
for (const stopword of Object.keys(BRAND_STOPWORDS)) {
|
||||
if (stopword.startsWith(query)) {
|
||||
state.value = {
|
||||
isNoResults: true,
|
||||
isBrand: true,
|
||||
query: BRAND_STOPWORDS[stopword],
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
state.value = {
|
||||
isNoResults: query in BRAND_STOPWORDS && searchResults.length === 0 && query !== '',
|
||||
isBrand: query in BRAND_STOPWORDS,
|
||||
query: BRAND_STOPWORDS[query] ?? query,
|
||||
};
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
return state;
|
||||
}
|
||||
25
docs/.vitepress/types.d.ts
vendored
25
docs/.vitepress/types.d.ts
vendored
@@ -1,25 +0,0 @@
|
||||
import { type IconNode } from 'lucide-vue-next/src/types';
|
||||
import Vue from 'vue';
|
||||
|
||||
declare module '*.vue' {
|
||||
export default Vue;
|
||||
}
|
||||
|
||||
declare module '*.data.ts' {
|
||||
const data: any;
|
||||
|
||||
export { data };
|
||||
}
|
||||
|
||||
declare module '*.wasm' {}
|
||||
|
||||
declare const resvg_wasm: RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
|
||||
declare module 'node:module' {
|
||||
function createRequire(filename: string): NodeRequire;
|
||||
}
|
||||
|
||||
declare module '*.node.json' {
|
||||
const value: IconNode;
|
||||
export default value;
|
||||
}
|
||||
50
docs/.vitepress/vue-shim.d.ts
vendored
Normal file
50
docs/.vitepress/vue-shim.d.ts
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
import { IconNode } from 'lucide-vue-next/src/createLucideIcon';
|
||||
import Vue from 'vue';
|
||||
|
||||
declare module '*.vue' {
|
||||
export default Vue;
|
||||
}
|
||||
|
||||
declare module '*.data.ts' {
|
||||
const data: any;
|
||||
|
||||
export { data };
|
||||
}
|
||||
|
||||
declare module '*.data' {
|
||||
const data: any;
|
||||
|
||||
export { data };
|
||||
}
|
||||
|
||||
declare module '*.wasm' {}
|
||||
|
||||
declare const resvg_wasm: RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
|
||||
declare module 'node:module' {
|
||||
function createRequire(filename: string): NodeRequire;
|
||||
}
|
||||
|
||||
declare module '*.node.json' {
|
||||
export default IconNode;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
ExpoSnack?: {
|
||||
/**
|
||||
* Initialize all snack players on the page
|
||||
*/
|
||||
initialize(): void;
|
||||
/**
|
||||
* Remove a snack player container
|
||||
*/
|
||||
remove(container: Element): void;
|
||||
/**
|
||||
* Append/add a snack player container
|
||||
*/
|
||||
append(container: Element): void;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Accessibility
|
||||
---
|
||||
|
||||
# Accessibility
|
||||
# Accessibility in dept
|
||||
|
||||
Icons are pictures that show what something means without using words. They can be very helpful
|
||||
because they can quickly give information.
|
||||
@@ -17,7 +17,7 @@ Icons are a helpful tool to improve perception, but they aren't a replacement fo
|
||||
In most cases, it is probably a good idea to also provide a textual representation of your icon's
|
||||
function.
|
||||
|
||||

|
||||
<!--@include: ../../docs/images/a11y/visible-labels.svg -->
|
||||
|
||||
## Contrast
|
||||
|
||||
@@ -27,21 +27,21 @@ with low vision or color vision deficiencies.
|
||||
We recommend
|
||||
following [WCAG 2.1 SC 1.4.3](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html).
|
||||
|
||||

|
||||
<!--@include: ../../docs/images/a11y/contrast.svg -->
|
||||
|
||||
## Use of color
|
||||
|
||||
Avoid relying solely on color to convey meaning in icons, as some users may have color blindness.
|
||||
Instead, use additional visual cues like shape, shading or text.
|
||||
|
||||

|
||||
<!--@include: ../../docs/images/a11y/use-of-color.svg -->
|
||||
|
||||
## Interactivity
|
||||
|
||||
Ensure that interactive icons are accessible via keyboard navigation and provide clear feedback when
|
||||
activated.
|
||||
|
||||

|
||||
<!--@include: ../../docs/images/a11y/interactivity.svg -->
|
||||
|
||||
In most cases this is easily done by wrapping them in icon buttons.
|
||||
|
||||
@@ -50,7 +50,7 @@ In most cases this is easily done by wrapping them in icon buttons.
|
||||
Small targets can be difficult to click or touch, if your icon is interactive, we recommend that it
|
||||
should have a minimum target size of 44×44 pixels.
|
||||
|
||||

|
||||
<!--@include: ../../docs/images/a11y/target-size.svg -->
|
||||
|
||||
In practice, this doesn't necessarily mean that the icon itself should be this large, only its
|
||||
interactive wrapper element.
|
||||
@@ -60,14 +60,14 @@ interactive wrapper element.
|
||||
Icons should represent concepts or actions in a universally understandable way. Avoid using abstract
|
||||
or ambiguous, or culture-specific symbols that might confuse some users.
|
||||
|
||||

|
||||
<!--@include: ../../docs/images/a11y/meaningfulness.svg -->
|
||||
|
||||
## Consistency
|
||||
|
||||
Maintain consistency in icon design and usage across your interface to help users learn and
|
||||
understand their meanings more easily.
|
||||
|
||||

|
||||
<!--@include: ../../docs/images/a11y/consistency.svg -->
|
||||
|
||||
## Text Alternatives
|
||||
|
||||
@@ -89,7 +89,7 @@ In case some of your icons stand alone, and they serve a non-decorative function
|
||||
provide the appropriate accessible label for them.
|
||||
:::
|
||||
|
||||

|
||||
<!--@include: ../../docs/images/a11y/alttext-standalone.svg -->
|
||||
|
||||
In general try to avoid using functional icons with no interactivity, we recommend that:
|
||||
|
||||
@@ -104,7 +104,8 @@ elements (badges, buttons, nav items etc.) only, _not_ the icons themselves.
|
||||
Do not provide an accessible label to icons when used on a button, as this label will be read out by
|
||||
screen readers, leading to nonsensical text.
|
||||
|
||||

|
||||
<!--@include: ../../docs/images/a11y/alttext-buttons.svg -->
|
||||
|
||||
|
||||
::: details Code examples
|
||||
|
||||
@@ -132,7 +133,7 @@ the close button of a dialog for example).
|
||||
As previously stated, you should provide your accessible label on the icon button itself, not the
|
||||
contained icon.
|
||||
|
||||

|
||||
<!--@include: ../../docs/images/a11y/alttext-iconbuttons.svg -->
|
||||
|
||||
::: details Code examples
|
||||
|
||||
@@ -215,3 +216,16 @@ We also recommend checking out the following resources about accessibility:
|
||||
- [A11yTalks](https://www.a11ytalks.com/)
|
||||
- [A11y automation tracker](https://a11y-automation.dev/)
|
||||
- [The A11Y Project](https://www.a11yproject.com/)
|
||||
|
||||
<style>
|
||||
svg.a11y-example {
|
||||
max-width: calc(100% + 48px);
|
||||
margin: 0 -24px;
|
||||
}
|
||||
@media (min-width: 480px) {
|
||||
svg.a11y-example {
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
BIN
docs/guide/images/vscode-hover.png
Normal file
BIN
docs/guide/images/vscode-hover.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -64,25 +64,24 @@ Implementation of the lucide icon library for Vue applications.
|
||||
::: code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add lucide-vue-next
|
||||
pnpm add @lucide/vue
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add lucide-vue-next
|
||||
yarn add @lucide/vue
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm install lucide-vue-next
|
||||
npm install @lucide/vue
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add lucide-vue-next
|
||||
bun add @lucide/vue
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
For more details, see the [documentation](packages/lucide-vue-next.md).
|
||||
For Vue 2 use the `lucide-vue` package.
|
||||
For more details, see the [documentation](packages/lucide-vue.md).
|
||||
|
||||
## Svelte
|
||||
|
||||
@@ -91,22 +90,22 @@ Implementation of the lucide icon library for Svelte applications.
|
||||
::: code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add lucide-svelte
|
||||
pnpm add @lucide/svelte
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add lucide-svelte
|
||||
yarn add @lucide/svelte
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm install lucide-svelte
|
||||
npm install @lucide/svelte
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add lucide-svelte
|
||||
bun add @lucide/svelte
|
||||
```
|
||||
|
||||
:::
|
||||
> `@lucide/svelte` is only for Svelte 5, for Svelte 4 use the `lucide-svelte` package.
|
||||
|
||||
For more details, see the [documentation](packages/lucide-svelte.md).
|
||||
|
||||
@@ -248,13 +247,3 @@ The lucide figma plugin.
|
||||
Visit [Figma community page](https://www.figma.com/community/plugin/939567362549682242/Lucide-Icons) to install the plugin.
|
||||
|
||||

|
||||
|
||||
## Flutter
|
||||
|
||||
Implementation of Lucide icon library for Flutter applications.
|
||||
|
||||
```bash
|
||||
flutter pub add lucide_icons
|
||||
```
|
||||
|
||||
For more details, see the [pub.dev](https://pub.dev/packages/lucide_icons).
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
# Lucide React
|
||||
|
||||
React components for Lucide icons that integrate seamlessly into your React applications. Each icon is a fully-typed React component that renders as an optimized inline SVG, giving you the flexibility of components with the performance of vector graphics.
|
||||
|
||||
**What you can accomplish:**
|
||||
- Import icons as React components with full TypeScript support
|
||||
- Pass props to customize size, color, stroke width, and other SVG attributes
|
||||
- Use icons in JSX with the same ease as any other React component
|
||||
- Benefit from automatic tree-shaking to include only the icons you use
|
||||
- Create dynamic icon components that respond to state and user interactions
|
||||
|
||||
## Installation
|
||||
|
||||
::: code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add lucide-react
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add lucide-react
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm install lucide-react
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add lucide-react
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## How to use
|
||||
|
||||
Lucide is built with ES Modules, so it's completely tree-shakable.
|
||||
|
||||
Each icon can be imported as a React component, which renders an inline SVG element. This way, only the icons that are imported into your project are included in the final bundle. The rest of the icons are tree-shaken away.
|
||||
|
||||
### Example
|
||||
|
||||
Additional props can be passed to adjust the icon:
|
||||
|
||||
```jsx
|
||||
import { Camera } from 'lucide-react';
|
||||
|
||||
// Usage
|
||||
const App = () => {
|
||||
return <Camera color="red" size={48} />;
|
||||
};
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| name | type | default |
|
||||
| --------------------- | --------- | ------------ |
|
||||
| `size` | *number* | 24 |
|
||||
| `color` | *string* | currentColor |
|
||||
| `strokeWidth` | *number* | 2 |
|
||||
| `absoluteStrokeWidth` | *boolean* | false |
|
||||
|
||||
### Applying props
|
||||
|
||||
To customize the appearance of an icon, you can pass custom properties as props directly to the component. The component accepts all SVG attributes as props, which allows flexible styling of the SVG elements. See the list of SVG Presentation Attributes on [MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation).
|
||||
|
||||
```jsx
|
||||
// Usage
|
||||
const App = () => {
|
||||
return <Camera size={48} fill="red" />;
|
||||
};
|
||||
```
|
||||
|
||||
## With Lucide lab or custom icons
|
||||
|
||||
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
|
||||
|
||||
They can be used by using the `Icon` component.
|
||||
All props like regular lucide icons can be passed to adjust the icon appearance.
|
||||
|
||||
### Using the `Icon` component
|
||||
|
||||
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
|
||||
|
||||
```jsx
|
||||
import { Icon } from 'lucide-react';
|
||||
import { coconut } from '@lucide/lab';
|
||||
|
||||
const App = () => (
|
||||
<Icon iconNode={coconut} />
|
||||
);
|
||||
```
|
||||
|
||||
## Dynamic Icon Component
|
||||
|
||||
It is possible to create one generic icon component to load icons. But it is not recommended, since it is importing all icons during the build. This increases build time and the different modules it will create.
|
||||
|
||||
`DynamicIcon` is useful for applications that want to show icons dynamically by icon name. For example, when using a content management system with where icon names are stored in a database.
|
||||
|
||||
For static use cases, it is recommended to import the icons directly.
|
||||
|
||||
The same props can be passed to adjust the icon appearance. The `name` prop is required to load the correct icon.
|
||||
|
||||
```jsx
|
||||
import { DynamicIcon } from 'lucide-react/dynamic';
|
||||
|
||||
const App = () => (
|
||||
<DynamicIcon name="camera" color="red" size={48} />
|
||||
);
|
||||
```
|
||||
@@ -30,14 +30,7 @@ This package includes the following implementations of Lucide icons:
|
||||
|
||||
SVG sprites and icon fonts include **all icons**, which can significantly increase your app's bundle size and load time.
|
||||
|
||||
For production environments, we recommend using a bundler with tree-shaking support to include only the icons you actually use. Consider using:
|
||||
|
||||
- [lucide](lucide)
|
||||
- [lucide-react](lucide-react)
|
||||
- [lucide-vue](lucide-vue)
|
||||
- [lucide-vue-next](lucide-vue-next)
|
||||
- [lucide-angular](lucide-angular)
|
||||
- [lucide-preact](lucide-preact)
|
||||
For production environments, we recommend using a bundler with tree-shaking support to include only the icons you actually use. Consider using one of the framework-specific [packages](../../packages).
|
||||
:::
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
# Lucide Vue Next
|
||||
|
||||
Vue 3 components for Lucide icons that leverage the Composition API and modern Vue features. Each icon is a reactive Vue component that renders as an inline SVG, providing excellent performance and developer experience in Vue 3 applications.
|
||||
|
||||
**What you can accomplish:**
|
||||
- Use icons as Vue 3 components with full reactivity and TypeScript support
|
||||
- Bind icon properties to reactive data and computed values
|
||||
- Customize icons with props, slots, and Vue's powerful templating system
|
||||
- Integrate seamlessly with Vue 3's Composition API and script setup syntax
|
||||
- Build dynamic interfaces where icons respond to application state changes
|
||||
|
||||
## Installation
|
||||
|
||||
::: code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add lucide-vue-next
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add lucide-vue-next
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm install lucide-vue-next
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add lucide-vue-next
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## How to use
|
||||
|
||||
Lucide is built with ES Modules, so it's completely tree-shakable.
|
||||
|
||||
Each icon can be imported as a Vue component, which renders an inline SVG Element. This way only the icons that are imported into your project are included in the final bundle. The rest of the icons are tree-shaken away.
|
||||
|
||||
### Example
|
||||
|
||||
You can pass additional props to adjust the icon.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { Camera } from 'lucide-vue-next';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Camera
|
||||
color="red"
|
||||
:size="32"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| name | type | default |
|
||||
| ----------------------- | --------- | ------------ |
|
||||
| `size` | *number* | 24 |
|
||||
| `color` | *string* | currentColor |
|
||||
| `stroke-width` | *number* | 2 |
|
||||
| `absoluteStrokeWidth` | *boolean* | false |
|
||||
| `default-class` | *string* | lucide-icon |
|
||||
|
||||
### Applying props
|
||||
|
||||
To customize the appearance of an icon, you can pass custom properties as props directly to the component. The component accepts all SVG attributes as props, which allows flexible styling of the SVG elements. See the list of SVG Presentation Attributes on [MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation).
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<Camera fill="red" />
|
||||
</template>
|
||||
```
|
||||
|
||||
## With Lucide lab or custom icons
|
||||
|
||||
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
|
||||
|
||||
They can be used by using the `Icon` component.
|
||||
All props like regular lucide icons can be passed to adjust the icon appearance.
|
||||
|
||||
### Using the `Icon` component
|
||||
|
||||
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { Icon } from 'lucide-vue-next';
|
||||
import { baseball } from '@lucide/lab';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Icon :iconNode="baseball" />
|
||||
</template>
|
||||
```
|
||||
|
||||
## One generic icon component
|
||||
|
||||
It is possible to create one generic icon component to load icons, but it is not recommended.
|
||||
|
||||
::: danger
|
||||
The example below imports all ES Modules, so exercise caution when using it. Importing all icons will significantly increase the build size of the application, negatively affecting its performance. This is especially important when using bundlers like `Webpack`, `Rollup`, or `Vite`.
|
||||
:::
|
||||
|
||||
### Icon Component Example
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import * as icons from "lucide-vue-next";
|
||||
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
size: Number,
|
||||
color: String,
|
||||
strokeWidth: Number,
|
||||
defaultClass: String
|
||||
})
|
||||
|
||||
const icon = computed(() => icons[props.name]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="icon"
|
||||
:size="size"
|
||||
:color="color"
|
||||
:stroke-width="strokeWidth" :default-class="defaultClass"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Using the Icon Component
|
||||
|
||||
All other props listed above also work on the `Icon` Component.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div id="app">
|
||||
<Icon name="Airplay" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
@@ -9,28 +9,24 @@ Vue 2 components for Lucide icons that integrate with Vue's Options API and temp
|
||||
- Build applications using Vue 2's familiar syntax and patterns
|
||||
- Bridge the gap while planning migration to Vue 3
|
||||
|
||||
::: danger
|
||||
This package is deprecated. Vue 2 is EOF See [Announcement](https://v2.vuejs.org/eol/). Migrate to Vue 3.
|
||||
:::
|
||||
|
||||
## Installation
|
||||
|
||||
::: code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add lucide-vue
|
||||
pnpm add @lucide/vue
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add lucide-vue
|
||||
yarn add @lucide/vue
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm install lucide-vue
|
||||
npm install @lucide/vue
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add lucide-vue
|
||||
bun add @lucide/vue
|
||||
```
|
||||
|
||||
:::
|
||||
@@ -43,21 +39,19 @@ Each icon can be imported as a Vue component, which renders an inline SVG Elemen
|
||||
|
||||
### Example
|
||||
|
||||
Additional props can be passed to adjust the icon:
|
||||
You can pass additional props to adjust the icon.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<Camera color="red" :size="32" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Camera } from 'lucide-vue';
|
||||
|
||||
export default {
|
||||
name: 'My Component',
|
||||
components: { Camera }
|
||||
};
|
||||
<script setup>
|
||||
import { Camera } from '@lucide/vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Camera
|
||||
color="red"
|
||||
:size="32"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Props
|
||||
@@ -80,6 +74,28 @@ To customize the appearance of an icon, you can pass custom properties as props
|
||||
</template>
|
||||
```
|
||||
|
||||
## With Lucide lab or custom icons
|
||||
|
||||
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
|
||||
|
||||
They can be used by using the `Icon` component.
|
||||
All props like regular lucide icons can be passed to adjust the icon appearance.
|
||||
|
||||
### Using the `Icon` component
|
||||
|
||||
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { Icon } from '@lucide/vue';
|
||||
import { baseball } from '@lucide/lab';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Icon :iconNode="baseball" />
|
||||
</template>
|
||||
```
|
||||
|
||||
## One generic icon component
|
||||
|
||||
It is possible to create one generic icon component to load icons, but it is not recommended.
|
||||
@@ -91,30 +107,37 @@ The example below imports all ES Modules, so exercise caution when using it. Imp
|
||||
### Icon Component Example
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<component :is="icon" />
|
||||
</template>
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import * as icons from "@lucide/vue";
|
||||
|
||||
<script>
|
||||
import * as icons from 'lucide-vue';
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
size: Number,
|
||||
color: String,
|
||||
strokeWidth: Number,
|
||||
defaultClass: String
|
||||
})
|
||||
|
||||
export default {
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
icon() {
|
||||
return icons[this.name];
|
||||
}
|
||||
}
|
||||
};
|
||||
const icon = computed(() => icons[props.name]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="icon"
|
||||
:size="size"
|
||||
:color="color"
|
||||
:stroke-width="strokeWidth" :default-class="defaultClass"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
#### Using the Icon Component
|
||||
### Using the Icon Component
|
||||
|
||||
All other props listed above also work on the `Icon` Component.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
|
||||
39
docs/guide/preact/advanced/accessibility.md
Normal file
39
docs/guide/preact/advanced/accessibility.md
Normal file
@@ -0,0 +1,39 @@
|
||||
<script setup>
|
||||
import OverviewLink from '../../../.vitepress/theme/components/base/OverviewLink.vue'
|
||||
</script>
|
||||
|
||||
# Accessibility
|
||||
|
||||
By default all lucide icons are applied with `aria-hidden="true"` which makes them **not** accessible for screen readers.
|
||||
This is done because most of the time icons are used for decorative purposes only.
|
||||
|
||||
If you need to make an icon accessible, you can do so by passing a `title` element as a child or passing the `aria-label` prop to the icon component.
|
||||
This will remove the `aria-hidden` attribute and make the icon accessible.
|
||||
|
||||
```tsx
|
||||
<House>
|
||||
<title>This is my house</title>
|
||||
</House>
|
||||
|
||||
// or
|
||||
|
||||
<House aria-label="This is my house" />
|
||||
```
|
||||
|
||||
We recommend to describe the icon in a way that makes sense for the user, or the action it represents and that makes sense in the context of your application.
|
||||
|
||||
## Accessible Icon Buttons
|
||||
|
||||
When using icon buttons, you should not provide an accessible label on the icon itself, but rather on the button.
|
||||
|
||||
```tsx
|
||||
<button aria-label="Go to home">
|
||||
<House />
|
||||
</button>
|
||||
```
|
||||
|
||||
## Detailed Guide on Accessibility
|
||||
|
||||
For best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility.
|
||||
|
||||
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
|
||||
55
docs/guide/preact/advanced/aliased-names.md
Normal file
55
docs/guide/preact/advanced/aliased-names.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Aliased Names
|
||||
|
||||
Icons can have multiple names for the same icon. This is because we choose to rename some icons to make them more consistent with the rest of the icon set, or the name was not generic. For example, the `edit-2` icon is renamed to `pen` to make the name more generic, since it is just a pen icon.
|
||||
|
||||
Beside aliases names lucide also includes prefixed and suffixed names to use within your project. This is to prevent import name collisions with other libraries or your own code.
|
||||
|
||||
```tsx
|
||||
// These are all the same icon
|
||||
import {
|
||||
House,
|
||||
HouseIcon,
|
||||
LucideHouse,
|
||||
} from "lucide-preact";
|
||||
```
|
||||
|
||||
## Choosing import name style
|
||||
|
||||
To be consistent in your imports or want to change the autocompletion of Lucide icons in your IDE there an option to able the choose the import name style you want.
|
||||
|
||||
This can be done by creating a custom module declaration file to override the lucide imports and turning off the autocomplete in your IDE.
|
||||
|
||||
### Turn off autocomplete in your IDE
|
||||
|
||||
```json [.vscode/settings.json]
|
||||
{
|
||||
"typescript.preferences.autoImportFileExcludePatterns": [
|
||||
"lucide-preact",
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Create a custom module declaration file
|
||||
|
||||
This will enable you to choose the import name style you want to use in your project.
|
||||
|
||||
```ts [React]
|
||||
declare module "lucide-preact" {
|
||||
// Prefixed import names
|
||||
export * from "lucide-preact/dist/lucide-preact.prefixed";
|
||||
// or
|
||||
// Suffixed import names
|
||||
export * from "lucide-preact/dist/lucide-preact.suffixed";
|
||||
}
|
||||
```
|
||||
|
||||
Place this in your project root or in a folder where your tsconfig.json is located, or locate it in your defined type directory.
|
||||
Easiest way is to create a `@types` folder in your project root and name the file `lucide-preact.d.ts`.
|
||||
|
||||
### Import name styles
|
||||
|
||||
| Import Style | Available imports | Declaration file import |
|
||||
| ------------- | --------------------------- | ----------------------- |
|
||||
| Default | Home, HomeIcon, LucideHome | |
|
||||
| Prefixed | LucideHome | lucide-preact.prefixed |
|
||||
| Suffixed | HomeIcon | lucide-preact.suffixed |
|
||||
62
docs/guide/preact/advanced/combining-icons.md
Normal file
62
docs/guide/preact/advanced/combining-icons.md
Normal file
@@ -0,0 +1,62 @@
|
||||
<script setup>
|
||||
import { Sandpack } from 'sandpack-vue3'
|
||||
import sandpackTheme from '../../../.vitepress/theme/sandpackTheme.json'
|
||||
import combineIconsExample from './examples/combining-icons/files.ts'
|
||||
import combineCustomExample from './examples/combining-icons-custom/files.ts'
|
||||
import combineNotificationExample from './examples/combining-icons-notification/files.ts'
|
||||
</script>
|
||||
|
||||
# Combining icons
|
||||
|
||||
You can combine multiple icons into a single icon by using SVG in SVG.
|
||||
This is useful for if you want to be creative and make your own custom icons by combining existing icons.
|
||||
|
||||
<Sandpack
|
||||
template="vanilla"
|
||||
:theme="sandpackTheme"
|
||||
:files="combineIconsExample"
|
||||
:options="{
|
||||
editorHeight: 400,
|
||||
editorWidthPercentage: 60,
|
||||
}"
|
||||
/>
|
||||
|
||||
This is valid SVG and all SVG properties are supported on the icons.
|
||||
The `x` and `y` coordinates can be adjusted to position the icons as you like.
|
||||
|
||||
## Caveats
|
||||
|
||||
When combining icons, you need to make sure that the icon you is in the `viewBox` of the outer icon (24x24).
|
||||
|
||||
## With custom SVG elements
|
||||
|
||||
You can also use SVG elements to create your own icons.
|
||||
|
||||
## Example with notification badge
|
||||
|
||||
<Sandpack
|
||||
template="vanilla"
|
||||
:theme="sandpackTheme"
|
||||
:files="combineNotificationExample"
|
||||
:options="{
|
||||
editorHeight: 480,
|
||||
editorWidthPercentage: 60,
|
||||
}"
|
||||
/>
|
||||
|
||||
## Example with text element
|
||||
|
||||
You can also use the `text` SVG element to add text to your icon.
|
||||
|
||||
<Sandpack
|
||||
template="vanilla"
|
||||
:theme="sandpackTheme"
|
||||
:files="combineCustomExample"
|
||||
:options="{
|
||||
editorHeight: 480,
|
||||
editorWidthPercentage: 60,
|
||||
}"
|
||||
/>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { File } from "lucide-preact";
|
||||
import { h } from "preact";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="app">
|
||||
<File size={48}>
|
||||
<text
|
||||
x={7.5}
|
||||
y={19}
|
||||
font-size={8}
|
||||
font-family="Verdana,sans-serif"
|
||||
stroke-width={1}
|
||||
>
|
||||
JS
|
||||
</text>
|
||||
</File>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,12 @@
|
||||
import App from './App.js?raw'
|
||||
import { preactFiles } from '../../../basics/examples/files'
|
||||
|
||||
const files = {
|
||||
'App.js': {
|
||||
code: App,
|
||||
active: true,
|
||||
},
|
||||
...preactFiles
|
||||
}
|
||||
|
||||
export default files
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Mail } from "lucide-preact";
|
||||
import { h } from "preact";
|
||||
|
||||
function App() {
|
||||
const hasUnreadMessages = true;
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<Mail size={48}>
|
||||
{hasUnreadMessages && (
|
||||
<circle
|
||||
r="3"
|
||||
cx="21"
|
||||
cy="5"
|
||||
stroke="none"
|
||||
fill="#F56565"
|
||||
/>
|
||||
)}
|
||||
</Mail>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,12 @@
|
||||
import App from './App.js?raw'
|
||||
import { preactFiles } from '../../../basics/examples/files'
|
||||
|
||||
const files = {
|
||||
'App.js': {
|
||||
code: App,
|
||||
active: true,
|
||||
},
|
||||
...preactFiles
|
||||
}
|
||||
|
||||
export default files
|
||||
19
docs/guide/preact/advanced/examples/combining-icons/App.js
Normal file
19
docs/guide/preact/advanced/examples/combining-icons/App.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Scan, User } from "lucide-preact";
|
||||
import { h } from "preact";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="app">
|
||||
<Scan size={48}>
|
||||
<User
|
||||
size={12}
|
||||
x={6}
|
||||
y={6}
|
||||
absoluteStrokeWidth
|
||||
/>
|
||||
</Scan>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
12
docs/guide/preact/advanced/examples/combining-icons/files.ts
Normal file
12
docs/guide/preact/advanced/examples/combining-icons/files.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import App from './App.js?raw'
|
||||
import { preactFiles } from '../../../basics/examples/files'
|
||||
|
||||
const files = {
|
||||
'App.js': {
|
||||
code: App,
|
||||
active: true,
|
||||
},
|
||||
...preactFiles
|
||||
}
|
||||
|
||||
export default files
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Star, StarHalf } from "lucide-preact";
|
||||
import { h } from "preact";
|
||||
import "./icon.css";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="app">
|
||||
<div className="star-rating">
|
||||
<div className="stars">
|
||||
{ Array.from({ length: 5 }, () => (
|
||||
<Star fill="#111" strokeWidth={0} />
|
||||
))}
|
||||
</div>
|
||||
<div className="stars rating">
|
||||
<Star fill="yellow" strokeWidth={0} />
|
||||
<Star fill="yellow" strokeWidth={0} />
|
||||
<StarHalf fill="yellow" strokeWidth={0} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,17 @@
|
||||
import App from './App.js?raw'
|
||||
import IconCss from './icon.css?raw'
|
||||
import { preactFiles } from '../../../basics/examples/files'
|
||||
|
||||
const files = {
|
||||
'App.js': {
|
||||
code: App,
|
||||
active: true,
|
||||
},
|
||||
'icon.css': {
|
||||
code: IconCss,
|
||||
readOnly: false,
|
||||
},
|
||||
...preactFiles
|
||||
}
|
||||
|
||||
export default files
|
||||
@@ -0,0 +1,31 @@
|
||||
import {
|
||||
TentTree,
|
||||
Caravan,
|
||||
FlameKindling,
|
||||
MountainSnow,
|
||||
Trees,
|
||||
Axe,
|
||||
Map,
|
||||
CloudMoon,
|
||||
Sparkles,
|
||||
} from "lucide-preact";
|
||||
import { h } from "preact";
|
||||
import "./icon.css";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="grid">
|
||||
<TentTree />
|
||||
<Caravan />
|
||||
<FlameKindling />
|
||||
<MountainSnow />
|
||||
<Trees />
|
||||
<Axe />
|
||||
<Map />
|
||||
<CloudMoon />
|
||||
<Sparkles />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,17 @@
|
||||
import App from './App.js?raw'
|
||||
import IconCss from './icon.css?raw'
|
||||
import { preactFiles } from '../../../basics/examples/files'
|
||||
|
||||
const files = {
|
||||
'icon.css': {
|
||||
code: IconCss,
|
||||
readOnly: false,
|
||||
active: true,
|
||||
},
|
||||
'App.js': {
|
||||
code: App,
|
||||
},
|
||||
...preactFiles
|
||||
}
|
||||
|
||||
export default files
|
||||
@@ -0,0 +1,16 @@
|
||||
.lucide {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
stroke-width: 1.5;
|
||||
}
|
||||
|
||||
.lucide * {
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr 1fr;
|
||||
gap: 6px;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import {
|
||||
CakeSlice,
|
||||
Candy,
|
||||
Apple,
|
||||
Cookie,
|
||||
Martini,
|
||||
IceCream2,
|
||||
Sandwich,
|
||||
Wine,
|
||||
Dessert,
|
||||
} from "lucide-preact";
|
||||
import { h } from "preact";
|
||||
import "./icon.css";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="grid">
|
||||
<CakeSlice />
|
||||
<Candy />
|
||||
<Apple />
|
||||
<Cookie />
|
||||
<Martini />
|
||||
<IceCream2 />
|
||||
<Sandwich />
|
||||
<Wine />
|
||||
<Dessert />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,17 @@
|
||||
import App from './App.js?raw'
|
||||
import IconCss from './icon.css?raw'
|
||||
import { preactFiles } from '../../../basics/examples/files'
|
||||
|
||||
const files = {
|
||||
'icon.css': {
|
||||
code: IconCss,
|
||||
readOnly: false,
|
||||
active: true,
|
||||
},
|
||||
'App.js': {
|
||||
code: App,
|
||||
},
|
||||
...preactFiles
|
||||
}
|
||||
|
||||
export default files
|
||||
@@ -0,0 +1,14 @@
|
||||
.lucide {
|
||||
/* Change this! */
|
||||
color: #ffadff;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr 1fr;
|
||||
gap: 6px;
|
||||
}
|
||||
33
docs/guide/preact/advanced/filled-icons.md
Normal file
33
docs/guide/preact/advanced/filled-icons.md
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
head:
|
||||
- - link
|
||||
- rel: canonical
|
||||
href: https://lucide.dev/guide/vue/advanced/filled-icons
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import { Sandpack } from 'sandpack-vue3'
|
||||
import sandpackTheme from '../../../.vitepress/theme/sandpackTheme.json'
|
||||
import sizeIconExample from './examples/filled-icon-example/files.ts'
|
||||
</script>
|
||||
|
||||
# Filled Icons
|
||||
|
||||
Fills are officially not supported.
|
||||
However, all SVG properties are available on all icons.
|
||||
Fill can still be used and will work fine on certain icons.
|
||||
|
||||
Example with stars:
|
||||
<Sandpack
|
||||
template="vanilla"
|
||||
:theme="sandpackTheme"
|
||||
:files="sizeIconExample"
|
||||
:options="{
|
||||
editorHeight: 480,
|
||||
editorWidthPercentage: 60,
|
||||
}"
|
||||
/>
|
||||
|
||||
## Will Lucide have fills in the future?
|
||||
|
||||
This feature has been requested several times and discussion is happening at [#458](https://github.com/lucide-icons/lucide/discussions/458).
|
||||
70
docs/guide/preact/advanced/global-styling.md
Normal file
70
docs/guide/preact/advanced/global-styling.md
Normal file
@@ -0,0 +1,70 @@
|
||||
<script setup>
|
||||
import { Sandpack } from 'sandpack-vue3'
|
||||
import sandpackTheme from '../../../.vitepress/theme/sandpackTheme.json'
|
||||
import globalIconCssExample from './examples/global-styling-css-example/files.ts'
|
||||
import globalAbsoluteStrokewidthExample from './examples/global-styling-absolute-strokewidth-example/files.ts'
|
||||
|
||||
</script>
|
||||
|
||||
# Global Styling
|
||||
|
||||
Adjusting icons can be done by using [color](../basics/color.md), [size](../basics/sizing.md) and [stroke width](../basics/stroke-width.md).
|
||||
To style all icons globally, you can either use CSS, or use a context provider.
|
||||
|
||||
We recommend using CSS for global styling, as it is the most straightforward way to achieve this.
|
||||
But using CSS prevents you from using props like `size`, `color` and `strokeWidth` on individual icons, since CSS specificity will override these props, to be able to use the props on individual ones you need to use the Lucide context provider.
|
||||
|
||||
## Context Provider
|
||||
|
||||
For global styling using a context provider, you can use the `LucideProvider` component that is provided by the `lucide-preact` package.
|
||||
|
||||
```tsx
|
||||
import { LucideProvider, Home } from 'lucide-preact';
|
||||
|
||||
const App = () => (
|
||||
<LucideProvider
|
||||
color="red"
|
||||
size={48}
|
||||
strokeWidth={2}
|
||||
>
|
||||
<Home />
|
||||
</LucideProvider>
|
||||
);
|
||||
```
|
||||
|
||||
This will apply the `color`, `size` and `strokeWidth` props to all icons that are children of the `LucideProvider`.
|
||||
|
||||
|
||||
## Style by using CSS
|
||||
Styling icons is easy to accomplish using CSS.
|
||||
|
||||
Every icon has a class attribute applied called `lucide`. This class name can be used in the CSS file to target all icons that are being used within the app.
|
||||
|
||||
- The **color** of the icons can be changed using the [`color`](https://developer.mozilla.org/en-US/docs/Web/CSS/color) CSS property.
|
||||
- The **size** of the icons can be changed using [`width`](https://developer.mozilla.org/en-US/docs/Web/CSS/width) and [`height`](https://developer.mozilla.org/en-US/docs/Web/CSS/height) CSS properties.
|
||||
- The **stroke width** of the icons can be changed using the [`stroke-width`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-width) CSS property.
|
||||
|
||||
<Sandpack
|
||||
template="vanilla"
|
||||
:theme="sandpackTheme"
|
||||
:files="globalIconCssExample"
|
||||
:options="{
|
||||
eeditorHeight: 480,
|
||||
editorWidthPercentage: 60,
|
||||
}"
|
||||
/>
|
||||
|
||||
### Absolute stroke width
|
||||
|
||||
For global absolute stroke width styling the `vector-effect: non-scaling-stroke` CSS property can be applied to the children. This will keep the stroke-width the same size no matter the size of the icon. See [absolute-stroke-width](../basics/stroke-width.md#absolute-stroke-width) for more info.
|
||||
|
||||
<Sandpack
|
||||
template="vanilla"
|
||||
:theme="sandpackTheme"
|
||||
:files="globalAbsoluteStrokewidthExample"
|
||||
:options="{
|
||||
editorHeight: 480,
|
||||
editorWidthPercentage: 60,
|
||||
}"
|
||||
/>
|
||||
|
||||
93
docs/guide/preact/advanced/typescript.md
Normal file
93
docs/guide/preact/advanced/typescript.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# TypeScript Support
|
||||
|
||||
List of exported types from the `lucide-preact` package.
|
||||
These can be used to type your components when using Lucide icons in a TypeScript React project
|
||||
|
||||
## `LucideProps`
|
||||
|
||||
Exports all props that can be passed to an icon component and any other SVG attributes, see: [SVG Presentation Attributes on MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation).
|
||||
|
||||
```ts
|
||||
interface LucideProps {
|
||||
size?: number | string;
|
||||
color?: string;
|
||||
strokeWidth?: number;
|
||||
absoluteStrokeWidth?: boolean;
|
||||
[key: string]: any; // Any other SVG attributes
|
||||
}
|
||||
```
|
||||
|
||||
### Using `LucideProps`
|
||||
You can use the `LucideProps` interface to type your custom icon components or when you need to work with icon props.
|
||||
|
||||
```tsx
|
||||
import { type LucideProps } from 'lucide-preact';
|
||||
import { Camera } from 'lucide-preact';
|
||||
|
||||
const WrapIcon = (props: LucideProps) => {
|
||||
return <Camera {...props} />;
|
||||
};
|
||||
|
||||
export default WrapIcon;
|
||||
```
|
||||
|
||||
## `LucideIcon`
|
||||
|
||||
Type for individual icon components.
|
||||
|
||||
```ts
|
||||
type LucideIcon = React.FC<LucideProps>;
|
||||
```
|
||||
|
||||
### Using `LucideIcon`
|
||||
|
||||
You can use the `LucideIcon` type when you need to work with icon components directly.
|
||||
|
||||
```tsx
|
||||
import { type LucideIcon } from 'lucide-preact';
|
||||
|
||||
interface ButtonProps {
|
||||
icon: LucideIcon;
|
||||
label: string;
|
||||
}
|
||||
|
||||
const IconButton = ({ icon: Icon, label }: ButtonProps) => {
|
||||
return (
|
||||
<button aria-label={label}>
|
||||
<Icon size={16} />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default IconButton;
|
||||
```
|
||||
|
||||
## `IconNode`
|
||||
|
||||
Type for the raw SVG structure of an icon. This is an array of SVG elements and their attributes to render the icon.
|
||||
Not commonly used directly in application code. But can be useful for advanced use cases, such as using custom icons or with Lucide lab.
|
||||
|
||||
```ts
|
||||
type IconNode = [elementName: string, attrs: Record<string, string | number>][];
|
||||
```
|
||||
|
||||
### Using `IconNode`
|
||||
You can use the `IconNode` type when you need to work with the raw SVG structure of an icon.
|
||||
|
||||
```tsx
|
||||
import { type IconNode, Icon } from 'lucide-preact';
|
||||
|
||||
const customIcon: IconNode = [
|
||||
['circle', { cx: 12, cy: 12, r: 10 }],
|
||||
['line', { x1: 12, y1: 8, x2: 12, y2: 12 }],
|
||||
['line', { x1: 12, y1: 16, x2: 12, y2: 16 }],
|
||||
];
|
||||
|
||||
const MyCustomIcon = () => {
|
||||
return (
|
||||
<Icon iconNode={customIcon} size={24} color="blue" />
|
||||
);
|
||||
};
|
||||
|
||||
export default MyCustomIcon;
|
||||
```
|
||||
19
docs/guide/preact/advanced/with-lucide-lab.md
Normal file
19
docs/guide/preact/advanced/with-lucide-lab.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# With Lucide lab or custom icons
|
||||
|
||||
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
|
||||
|
||||
They can be used by using the `Icon` component.
|
||||
All props like regular lucide icons can be passed to adjust the icon appearance.
|
||||
|
||||
## Using the `Icon` component
|
||||
|
||||
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
|
||||
|
||||
```jsx
|
||||
import { Icon } from 'lucide-preact';
|
||||
import { coconut } from '@lucide/lab';
|
||||
|
||||
const App = () => (
|
||||
<Icon iconNode={coconut} />
|
||||
);
|
||||
```
|
||||
42
docs/guide/preact/basics/color.md
Normal file
42
docs/guide/preact/basics/color.md
Normal file
@@ -0,0 +1,42 @@
|
||||
<script setup>
|
||||
import { Sandpack } from 'sandpack-vue3'
|
||||
import sandpackTheme from '../../../.vitepress/theme/sandpackTheme.json'
|
||||
import buttonExampleFiles from './examples/button-example/files.ts'
|
||||
import iconColorExampleFiles from './examples/color-icon/files.ts'
|
||||
</script>
|
||||
|
||||
# Color
|
||||
|
||||
By default, all icons have the color value: `currentColor`. This keyword uses the element's computed text `color` value to represent the icon color.
|
||||
|
||||
Read more about [ `currentColor` on MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#currentcolor_keyword).
|
||||
|
||||
## Adjust the color using the `color` prop
|
||||
|
||||
The color can be adjusted by passing the color prop to the element.
|
||||
|
||||
<Sandpack
|
||||
template="vanilla"
|
||||
:theme="sandpackTheme"
|
||||
:files="iconColorExampleFiles"
|
||||
:options="{
|
||||
editorHeight: 320,
|
||||
editorWidthPercentage: 60,
|
||||
}"
|
||||
/>
|
||||
|
||||
## Using parent elements text color value
|
||||
|
||||
Because the color of lucide icons uses `currentColor`, the color of the icon depends on the computed `color` of the element, or it inherits it from its parent.
|
||||
|
||||
For example, if a parent element's color value is `#fff` and one of the children is a lucide icon, the color of the icon will be rendered as `#fff`. This is browser native behavior.
|
||||
|
||||
<Sandpack
|
||||
template="vanilla"
|
||||
:theme="sandpackTheme"
|
||||
:files="buttonExampleFiles"
|
||||
:options="{
|
||||
editorHeight: 340,
|
||||
editorWidthPercentage: 60,
|
||||
}"
|
||||
/>
|
||||
@@ -0,0 +1,15 @@
|
||||
import { h } from "preact";
|
||||
import { RollerCoaster } from "lucide-preact";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="app">
|
||||
<RollerCoaster
|
||||
size={96}
|
||||
absoluteStrokeWidth={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,12 @@
|
||||
import App from './App.js?raw'
|
||||
import { preactFiles } from '../files'
|
||||
|
||||
const files = {
|
||||
'App.js': {
|
||||
code: App,
|
||||
active: true,
|
||||
},
|
||||
...preactFiles
|
||||
}
|
||||
|
||||
export default files
|
||||
6
docs/guide/preact/basics/examples/button-example/App.js
Normal file
6
docs/guide/preact/basics/examples/button-example/App.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import { h } from "preact";
|
||||
import Button from "./Button";
|
||||
|
||||
export default function App() {
|
||||
return <Button />;
|
||||
}
|
||||
13
docs/guide/preact/basics/examples/button-example/Button.jsx
Normal file
13
docs/guide/preact/basics/examples/button-example/Button.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { h } from "preact";
|
||||
import { ThumbsUp } from "lucide-preact";
|
||||
|
||||
function LikeButton() {
|
||||
return (
|
||||
<button style={{ color: "#fff" }}>
|
||||
<ThumbsUp />
|
||||
Like
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default LikeButton;
|
||||
18
docs/guide/preact/basics/examples/button-example/files.ts
Normal file
18
docs/guide/preact/basics/examples/button-example/files.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import App from './App.js?raw'
|
||||
import Button from './Button.jsx?raw'
|
||||
import { preactFiles } from '../files'
|
||||
|
||||
const files = {
|
||||
'App.js': {
|
||||
code: App,
|
||||
hidden: true
|
||||
},
|
||||
'Button.jsx': {
|
||||
code: Button,
|
||||
active: true,
|
||||
readOnly: false,
|
||||
},
|
||||
...preactFiles
|
||||
}
|
||||
|
||||
export default files
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user