mirror of
https://github.com/lucide-icons/lucide.git
synced 2025-12-20 22:49:21 +01:00
Compare commits
39 Commits
update-sit
...
version-se
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
985fa35a0b | ||
|
|
8c0e5cf302 | ||
|
|
4fcfb6a4d1 | ||
|
|
0f732b411d | ||
|
|
ce09c31f08 | ||
|
|
c2b059fb60 | ||
|
|
b3c80d027a | ||
|
|
20f30bb5ea | ||
|
|
c47ae67a3b | ||
|
|
7866a5a5c6 | ||
|
|
92bc88b001 | ||
|
|
e75fbcdec4 | ||
|
|
4cef8283a7 | ||
|
|
330f4b37db | ||
|
|
fd31cb44a8 | ||
|
|
790d30dbfa | ||
|
|
e7c075785f | ||
|
|
6d4c91707d | ||
|
|
c0ea92ebe7 | ||
|
|
42dc5508dd | ||
|
|
4dda432471 | ||
|
|
0775d8647e | ||
|
|
83ef8fc98d | ||
|
|
5b56ef705d | ||
|
|
dafe529892 | ||
|
|
151c5b145c | ||
|
|
d6f9043096 | ||
|
|
b4405f05ab | ||
|
|
9076da5f1b | ||
|
|
2e7d806282 | ||
|
|
c4e5730bc4 | ||
|
|
02b35e2518 | ||
|
|
f183c3ba20 | ||
|
|
67e9efb801 | ||
|
|
2e4c9a65be | ||
|
|
de4e8d0acd | ||
|
|
1f113d4274 | ||
|
|
dffffc7aff | ||
|
|
4acd574de2 |
@@ -9,3 +9,9 @@ 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: read
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
@@ -36,25 +36,19 @@ jobs:
|
||||
id: latest-tag
|
||||
run: echo "LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Log latest tag
|
||||
run: echo '${{ steps.latest-tag.outputs.LATEST_TAG }}'
|
||||
|
||||
- name: Check if we can patch
|
||||
run: .github/workflows/version-up.sh --minor
|
||||
run: pnpm semver $LATEST_TAG -i minor
|
||||
env:
|
||||
LATEST_TAG: ${{ steps.latest-tag.outputs.LATEST_TAG }}
|
||||
|
||||
- name: Create new version
|
||||
id: new-version
|
||||
run: echo "NEW_VERSION=$(.github/workflows/version-up.sh --minor)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create change log
|
||||
id: change-log
|
||||
run: |
|
||||
CHANGE_LOG=$(pnpm run generate:changelog --old-tag=${{ steps.latest-tag.outputs.LATEST_TAG }})
|
||||
CHANGE_LOG=$(tail -n +5 <<< $CHANGE_LOG)
|
||||
echo $CHANGE_LOG
|
||||
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
|
||||
echo "CHANGE_LOG<<$EOF" >> $GITHUB_OUTPUT
|
||||
echo "$CHANGE_LOG" >> $GITHUB_OUTPUT
|
||||
echo "$EOF" >> $GITHUB_OUTPUT
|
||||
run: echo "NEW_VERSION=$(pnpm semver $LATEST_TAG -i minor)" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
GITHUB_API_KEY: ${{ secrets.GITHUB_TOKEN }}
|
||||
LATEST_TAG: ${{ steps.latest-tag.outputs.LATEST_TAG }}
|
||||
|
||||
- name: Check output
|
||||
run: |
|
||||
@@ -68,38 +62,6 @@ jobs:
|
||||
name: Version ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||
generate_release_notes: true
|
||||
|
||||
test-semantic-release:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@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 Issue with Banned Phrases
|
||||
name: Close Icon Requests with Brand Terms
|
||||
|
||||
on:
|
||||
issues:
|
||||
@@ -13,23 +13,36 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Check for blocked phrases in issue title
|
||||
- name: Load stopwords from JSON & check issue title & body
|
||||
if: contains(github.event.issue.labels.*.name, '🙌 icon request')
|
||||
run: |
|
||||
ISSUE_TITLE=$(jq -r '.issue.title' "$GITHUB_EVENT_PATH")
|
||||
BLOCKED_PHRASES=("twitter" "whatsapp" "logo" "google" "tiktok" "facebook" "slack" "discord" "bluesky" "spotify" "behance" "pix" "x.com" "telegram")
|
||||
ISSUE_BODY=$(jq -r '.issue.body // ""' "$GITHUB_EVENT_PATH")
|
||||
ICON_NAME_SECTION=$(printf '%s\n' "$ISSUE_BODY" | awk '/### Icon name/{flag=1; next} /^### /{flag=0} flag')
|
||||
|
||||
# Check title and body for blocked phrases
|
||||
for PHRASE in "${BLOCKED_PHRASES[@]}"
|
||||
do
|
||||
if echo "$ISSUE_TITLE" | grep -i "$PHRASE"; then
|
||||
gh issue close ${{ github.event.issue.number }} --reason "not planned" --comment "This looks like a duplicate, use the [search](https://github.com/lucide-icons/lucide/issues?q=is%3Aissue+$PHRASE) to find similar issues.
|
||||
jq -r 'to_entries[] | "\(.key) \(.value)"' brand-stopwords.json | while read -r KEY VALUE; do
|
||||
SAFE_KEY=$(printf '%s\n' "$KEY" | sed 's/[][\.^$*]/\\&/g')
|
||||
SAFE_VALUE=$(printf '%s\n' "$VALUE" | sed 's/[][\.^$*]/\\&/g')
|
||||
|
||||
Read [our official statement about brand logos in Lucide](https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md).
|
||||
if echo "$ISSUE_TITLE" | grep -iqE "$SAFE_KEY|$SAFE_VALUE" || \
|
||||
{ [ -n "$ICON_NAME_SECTION" ] && echo "$ICON_NAME_SECTION" | grep -iqE "$SAFE_KEY|$SAFE_VALUE"; }; then
|
||||
|
||||
gh issue close ${{ github.event.issue.number }} \
|
||||
--reason "not_planned" \
|
||||
--comment "It looks like this request is about **${VALUE}**, which is a brand logo.
|
||||
|
||||
Lucide **does not accept** brand logos, and we do not plan to add them in the future. This is due to a combination of **legal restrictions**, **design consistency concerns**, and **practical maintenance reasons**.
|
||||
|
||||
[Click here to read our official statement about brand logos in Lucide.](./BRAND_LOGOS_STATEMENT.md)
|
||||
|
||||
You can [search for similar issues.](https://github.com/lucide-icons/lucide/issues?q=is%3Aissue+${VALUE})
|
||||
|
||||
We’re always happy to help on [Discord](https://discord.gg/EH6nSts)."
|
||||
|
||||
Always happy to help on [Discord](https://discord.gg/EH6nSts)."
|
||||
gh issue lock ${{ github.event.issue.number }} --reason spam
|
||||
exit 1
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
43
.github/workflows/lucide-vue.yml
vendored
43
.github/workflows/lucide-vue.yml
vendored
@@ -1,43 +0,0 @@
|
||||
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: read
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
pre-release:
|
||||
@@ -60,7 +60,6 @@ jobs:
|
||||
'lucide-svelte',
|
||||
'@lucide/astro',
|
||||
'@lucide/svelte',
|
||||
'@lucide/vue',
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
@@ -73,9 +72,6 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Set Auth Token
|
||||
run: npm config set //registry.npmjs.org/:_authToken ${{ inputs.NPM_TOKEN || secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Set new version
|
||||
run: pnpm --filter ${{ matrix.package }} version --new-version ${{ needs.pre-release.outputs.VERSION }} --no-git-tag-version
|
||||
|
||||
@@ -110,9 +106,6 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Set Auth Token
|
||||
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Set new version
|
||||
run: pnpm --filter lucide-static version --new-version ${{ needs.pre-release.outputs.VERSION }} --no-git-tag-version
|
||||
|
||||
@@ -158,7 +151,9 @@ 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
284
.github/workflows/version-up.sh
vendored
@@ -1,284 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
## Copyright (C) 2017, Oleksandr Kucherenko
|
||||
## Last revisit: 2017-09-29
|
||||
|
||||
## get highest version tag for all branches
|
||||
function highest_tag(){
|
||||
local TAG=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
echo "$TAG"
|
||||
}
|
||||
|
||||
## extract current branch name
|
||||
function current_branch(){
|
||||
## expected: heads/{branch_name}
|
||||
## expected: {branch_name}
|
||||
local BRANCH=$(git rev-parse --abbrev-ref HEAD | cut -d"/" -f2)
|
||||
echo "$BRANCH"
|
||||
}
|
||||
|
||||
## get latest/head commit hash number
|
||||
function head_hash(){
|
||||
local COMMIT_HASH=$(git rev-parse --verify HEAD)
|
||||
echo "$COMMIT_HASH"
|
||||
}
|
||||
|
||||
## extract tag commit hash code, tag name provided by argument
|
||||
function tag_hash(){
|
||||
local TAG_HASH=$(git log -1 --format=format:"%H" $1 2>/dev/null | tail -n1)
|
||||
echo "$TAG_HASH"
|
||||
}
|
||||
|
||||
## get latest revision number
|
||||
function latest_revision(){
|
||||
local REV=$(git rev-list --count HEAD 2>/dev/null)
|
||||
echo "$REV"
|
||||
}
|
||||
|
||||
## parse last found tag, extract it PARTS
|
||||
function parse_last(){
|
||||
local position=$(($1-1))
|
||||
|
||||
# two parts found only
|
||||
local SUBS=( ${PARTS[$position]//-/ } )
|
||||
#echo ${SUBS[@]}, size: ${#SUBS}
|
||||
|
||||
# found NUMBER
|
||||
PARTS[$position]=${SUBS[0]}
|
||||
#echo ${PARTS[@]}
|
||||
|
||||
# found SUFFIX
|
||||
if [[ ${#SUBS} -ge 1 ]]; then
|
||||
PARTS[4]=${SUBS[1],,} #lowercase
|
||||
#echo ${PARTS[@]}, ${SUBS[@]}
|
||||
fi
|
||||
}
|
||||
|
||||
## increment REVISION part, don't touch STAGE
|
||||
function increment_revision(){
|
||||
PARTS[3]=$(( PARTS[3] + 1 ))
|
||||
IS_DIRTY=1
|
||||
}
|
||||
|
||||
## increment PATCH part, reset all other lower PARTS, don't touch STAGE
|
||||
function increment_patch(){
|
||||
PARTS[2]=$(( PARTS[2] + 1 ))
|
||||
PARTS[3]=0
|
||||
IS_DIRTY=1
|
||||
}
|
||||
|
||||
## increment MINOR part, reset all other lower PARTS, don't touch STAGE
|
||||
function increment_minor(){
|
||||
PARTS[1]=$(( PARTS[1] + 1 ))
|
||||
PARTS[2]=0
|
||||
PARTS[3]=0
|
||||
IS_DIRTY=1
|
||||
}
|
||||
|
||||
## increment MAJOR part, reset all other lower PARTS, don't touch STAGE
|
||||
function incremet_major(){
|
||||
PARTS[0]="v$(( PARTS[0] + 1 ))"
|
||||
PARTS[1]=0
|
||||
PARTS[2]=0
|
||||
PARTS[3]=0
|
||||
IS_DIRTY=1
|
||||
}
|
||||
|
||||
## increment the number only of last found PART: REVISION --> PATCH --> MINOR. don't touch STAGE
|
||||
function increment_last_found(){
|
||||
if [[ "${#PARTS[3]}" == 0 || "${PARTS[3]}" == "0" ]]; then
|
||||
if [[ "${#PARTS[2]}" == 0 || "${PARTS[2]}" == "0" ]]; then
|
||||
increment_minor
|
||||
else
|
||||
increment_patch
|
||||
fi
|
||||
else
|
||||
increment_revision
|
||||
fi
|
||||
|
||||
# stage part is not EMPTY
|
||||
if [[ "${#PARTS[4]}" != 0 ]]; then
|
||||
IS_SHIFT=1
|
||||
fi
|
||||
}
|
||||
|
||||
## compose version from PARTS
|
||||
function compose(){
|
||||
MAJOR="${PARTS[0]}"
|
||||
MINOR=".${PARTS[1]}"
|
||||
PATCH=".${PARTS[2]}"
|
||||
REVISION=".${PARTS[3]}"
|
||||
SUFFIX="-${PARTS[4]}"
|
||||
|
||||
if [[ "${#PATCH}" == 1 ]]; then # if empty {PATCH}
|
||||
PATCH=""
|
||||
fi
|
||||
|
||||
if [[ "${#REVISION}" == 1 ]]; then # if empty {REVISION}
|
||||
REVISION=""
|
||||
fi
|
||||
|
||||
if [[ "${PARTS[3]}" == "0" ]]; then # if revision is ZERO
|
||||
REVISION=""
|
||||
fi
|
||||
|
||||
# shrink patch and revision
|
||||
if [[ -z "${REVISION// }" ]]; then
|
||||
if [[ "${PARTS[2]}" == "0" ]]; then
|
||||
PATCH=".0"
|
||||
fi
|
||||
else # revision is not EMPTY
|
||||
if [[ "${#PATCH}" == 0 ]]; then
|
||||
PATCH=".0"
|
||||
fi
|
||||
fi
|
||||
|
||||
# remove suffix if we don't have a alpha/beta/rc
|
||||
if [[ "${#SUFFIX}" == 1 ]]; then
|
||||
SUFFIX=""
|
||||
fi
|
||||
|
||||
|
||||
echo "${MAJOR}${MINOR}${PATCH}${REVISION}${SUFFIX}" #full format
|
||||
}
|
||||
|
||||
# initial version used for repository without tags
|
||||
INIT_VERSION=0.0.0.0-alpha
|
||||
|
||||
# do GIT data extracting
|
||||
TAG=$(highest_tag)
|
||||
REVISION=$(latest_revision)
|
||||
BRANCH=$(current_branch)
|
||||
TAG_HASH=$(tag_hash $TAG)
|
||||
HEAD_HASH=$(head_hash)
|
||||
|
||||
# if tag and branch commit hashes are different, than print info about that
|
||||
#echo $HEAD_HASH vs $TAG_HASH
|
||||
if [[ "$@" == "" ]]; then
|
||||
if [[ "$TAG_HASH" == "$HEAD_HASH" ]]; then
|
||||
echo "Tag $TAG and HEAD are aligned. We will stay on the TAG version."
|
||||
echo ""
|
||||
NO_ARGS_VALUE='--stay'
|
||||
else
|
||||
PATTERN="^[0-9]+.[0-9]+(.[0-9]+)*(-(alpha|beta|rc))*$"
|
||||
|
||||
if [[ "$BRANCH" =~ $PATTERN ]]; then
|
||||
echo "Detected version branch '$BRANCH'. We will auto-increment the last version PART."
|
||||
echo ""
|
||||
NO_ARGS_VALUE='--default'
|
||||
else
|
||||
echo "Detected branch name '$BRANCH' than does not match version pattern. We will increase MINOR."
|
||||
echo ""
|
||||
NO_ARGS_VALUE='--minor'
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
#
|
||||
# {MAJOR}.{MINOR}[.{PATCH}[.{REVISION}][-(.*)]
|
||||
#
|
||||
# Suffix: alpha, beta, rc
|
||||
# No Suffix --> {NEW_VERSION}-alpha
|
||||
# alpha --> beta
|
||||
# beta --> rc
|
||||
# rc --> {VERSION}
|
||||
#
|
||||
PARTS=( ${TAG//./ } )
|
||||
parse_last ${#PARTS[@]} # array size as argument
|
||||
#echo ${PARTS[@]}
|
||||
|
||||
# if no parameters than emulate --default parameter
|
||||
if [[ "$@" == "" ]]; then
|
||||
set -- $NO_ARGS_VALUE
|
||||
fi
|
||||
|
||||
# parse input parameters
|
||||
for i in "$@"
|
||||
do
|
||||
key="$i"
|
||||
|
||||
case $key in
|
||||
-a|--alpha) # switched to ALPHA
|
||||
PARTS[4]="alpha"
|
||||
IS_SHIFT=1
|
||||
;;
|
||||
-b|--beta) # switched to BETA
|
||||
PARTS[4]="beta"
|
||||
IS_SHIFT=1
|
||||
;;
|
||||
-c|--release-candidate) # switched to RC
|
||||
PARTS[4]="rc"
|
||||
IS_SHIFT=1
|
||||
;;
|
||||
-r|--release) # switched to RELEASE
|
||||
PARTS[4]=""
|
||||
IS_SHIFT=1
|
||||
;;
|
||||
-p|--patch) # increment of PATCH
|
||||
increment_patch
|
||||
;;
|
||||
-e|--revision) # increment of REVISION
|
||||
increment_revision
|
||||
;;
|
||||
-g|--git-revision) # use git revision number as a revision part§
|
||||
PARTS[3]=$(( REVISION ))
|
||||
IS_DIRTY=1
|
||||
;;
|
||||
-i|--minor) # increment of MINOR by default
|
||||
increment_minor
|
||||
;;
|
||||
--default) # stay on the same stage, but increment only last found PART of version code
|
||||
increment_last_found
|
||||
;;
|
||||
-m|--major) # increment of MAJOR
|
||||
incremet_major
|
||||
;;
|
||||
-s|--stay) # extract version info
|
||||
IS_DIRTY=1
|
||||
NO_APPLY_MSG=1
|
||||
;;
|
||||
-t|--tag-only) # extract version info
|
||||
TAG_ONLY=1
|
||||
;;
|
||||
--apply)
|
||||
DO_APPLY=1
|
||||
;;
|
||||
-h|--help)
|
||||
help
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# detected shift, but no increment
|
||||
if [[ "$IS_SHIFT" == "1" ]]; then
|
||||
# temporary disable stage shift
|
||||
stage=${PARTS[4]}
|
||||
PARTS[4]=''
|
||||
|
||||
# detect first run on repository, INIT_VERSION was used
|
||||
if [[ "$(compose)" == "0.0" ]]; then
|
||||
increment_minor
|
||||
fi
|
||||
|
||||
PARTS[4]=$stage
|
||||
fi
|
||||
|
||||
# no increment applied yet and no shift of state, do minor increase
|
||||
if [[ "$IS_DIRTY$IS_SHIFT" == "" ]]; then
|
||||
increment_minor
|
||||
fi
|
||||
|
||||
compose
|
||||
|
||||
# is proposed tag in conflict with any other TAG
|
||||
PROPOSED_HASH=$(tag_hash $(compose))
|
||||
if [[ "${#PROPOSED_HASH}" -gt 0 && "$NO_APPLY_MSG" == "" ]]; then
|
||||
echo -e "\033[31mERROR:\033[0m "
|
||||
echo -e "\033[31mERROR:\033[0m Found conflict with existing tag \033[32m$(compose)\033[0m / $PROPOSED_HASH"
|
||||
echo -e "\033[31mERROR:\033[0m Only manual resolving is possible now."
|
||||
echo -e "\033[31mERROR:\033[0m "
|
||||
echo -e "\033[31mERROR:\033[0m To Resolve try to add --revision or --patch modifier."
|
||||
echo -e "\033[31mERROR:\033[0m "
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
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": "editor"
|
||||
"svg.preview.background": "transparent"
|
||||
}
|
||||
|
||||
149
brand-stopwords.json
Normal file
149
brand-stopwords.json
Normal file
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"adobe": "Adobe",
|
||||
"airplay": "AirPlay",
|
||||
"amazon": "Amazon",
|
||||
"angular": "Angular",
|
||||
"aws": "AWS",
|
||||
"azure": "Azure",
|
||||
"bandcamp": "Bandcamp",
|
||||
"behance": "Behance",
|
||||
"bitbucket": "Bitbucket",
|
||||
"blender": "Blender",
|
||||
"bluesky": "BlueSky",
|
||||
"bootstrap": "Bootstrap",
|
||||
"brave": "Brave",
|
||||
"chakra": "Chakra UI",
|
||||
"chrome": "Chrome",
|
||||
"codepen": "Codepen",
|
||||
"codesandbox": "CodeSandbox",
|
||||
"csharp": "C#",
|
||||
"cypress": "Cypress",
|
||||
"dart": "Dart",
|
||||
"deezer": "Deezer",
|
||||
"deno": "Deno",
|
||||
"discord": "Discord",
|
||||
"docker": "Docker",
|
||||
"dribbble": "Dribbble",
|
||||
"dropbox": "Dropbox",
|
||||
"edge": "Edge",
|
||||
"ember": "Ember",
|
||||
"epic": "Epic Games",
|
||||
"erlang": "Erlang",
|
||||
"esbuild": "esbuild",
|
||||
"eslint": "ESLint",
|
||||
"facebook": "Facebook",
|
||||
"figjam": "FigJam",
|
||||
"figma": "Figma",
|
||||
"firebase": "Firebase",
|
||||
"firefox": "Firefox",
|
||||
"framer": "Framer",
|
||||
"gatsby": "Gatsby",
|
||||
"gcp": "Google Cloud",
|
||||
"github": "GitHub",
|
||||
"gitlab": "GitLab",
|
||||
"golang": "GoLang",
|
||||
"google": "Google",
|
||||
"gmail": "Gmail",
|
||||
"gravatar": "Gravatar",
|
||||
"haskell": "Haskell",
|
||||
"instagram": "Instagram",
|
||||
"java": "Java",
|
||||
"javascript": "JavaScript",
|
||||
"jest": "Jest",
|
||||
"jira": "Jira",
|
||||
"kotlin": "Kotlin",
|
||||
"kubernetes": "Kubernetes",
|
||||
"less": "Less",
|
||||
"leetcode": "LeetCode",
|
||||
"leet-code": "LeetCode",
|
||||
"line": "LINE",
|
||||
"linkedin": "LinkedIn",
|
||||
"lua": "Lua",
|
||||
"mariadb": "MariaDB",
|
||||
"mcp": "MCP",
|
||||
"messenger": "Messenger",
|
||||
"microsoft": "Microsoft",
|
||||
"mongodb": "MongoDB",
|
||||
"mui": "Material UI",
|
||||
"mysql": "MySQL",
|
||||
"nestjs": "NestJS",
|
||||
"netflix": "Netflix",
|
||||
"netlify": "Netlify",
|
||||
"next": "Next.js",
|
||||
"nodejs": "Node.js",
|
||||
"notion": "Notion",
|
||||
"nostr": "Nostr",
|
||||
"npm": "npm",
|
||||
"nuxt": "Nuxt",
|
||||
"opera": "Opera",
|
||||
"oracle": "Oracle",
|
||||
"patreon": "Patreon",
|
||||
"paypal": "PayPal",
|
||||
"perl": "Perl",
|
||||
"php": "PHP",
|
||||
"pinterest": "Pinterest",
|
||||
"pix": "PiX",
|
||||
"playstation": "PlayStation",
|
||||
"playwright": "Playwright",
|
||||
"pnpm": "pnpm",
|
||||
"postcss": "PostCSS",
|
||||
"postgresql": "PostgreSQL",
|
||||
"prettier": "Prettier",
|
||||
"prisma": "Prisma",
|
||||
"python": "Python",
|
||||
"qwik": "Qwik",
|
||||
"react": "React",
|
||||
"reddit": "Reddit",
|
||||
"redis": "Redis",
|
||||
"rollup": "Rollup",
|
||||
"rust": "Rust",
|
||||
"safari": "Safari",
|
||||
"sass": "Sass",
|
||||
"scala": "Scala",
|
||||
"scss": "Sass",
|
||||
"semantic": "Semantic UI",
|
||||
"shopify": "Shopify",
|
||||
"skype": "Skype",
|
||||
"slack": "Slack",
|
||||
"solid": "SolidJS",
|
||||
"soundcloud": "SoundCloud",
|
||||
"spotify": "Spotify",
|
||||
"sqlite": "SQLite",
|
||||
"squarespace": "Squarespace",
|
||||
"steam": "Steam",
|
||||
"stripe": "Stripe",
|
||||
"substack": "Substack",
|
||||
"supabase": "Supabase",
|
||||
"surge": "Surge",
|
||||
"svelte": "Svelte",
|
||||
"swift": "Swift",
|
||||
"tailwind": "Tailwind CSS",
|
||||
"telegram": "Telegram",
|
||||
"terraform": "Terraform",
|
||||
"tesla": "Tesla",
|
||||
"tidal": "Tidal",
|
||||
"tiktok": "TikTok",
|
||||
"trello": "Trello",
|
||||
"twitch": "Twitch",
|
||||
"twitter": "Twitter",
|
||||
"typescript": "TypeScript",
|
||||
"unity": "Unity",
|
||||
"unreal": "Unreal Engine",
|
||||
"vercel": "Vercel",
|
||||
"vimeo": "Vimeo",
|
||||
"vite": "Vite",
|
||||
"vitest": "Vitest",
|
||||
"vue": "Vue",
|
||||
"webpack": "Webpack",
|
||||
"wechat": "WeChat",
|
||||
"whatsapp": "WhatsApp",
|
||||
"windows": "Windows",
|
||||
"wix": "Wix",
|
||||
"x.com": "X.com",
|
||||
"x-social": "X.com",
|
||||
"xbox": "Xbox",
|
||||
"yarn": "Yarn",
|
||||
"youtube": "YouTube",
|
||||
"zig": "Zig",
|
||||
"zoom": "Zoom"
|
||||
}
|
||||
5
categories/brands.json
Normal file
5
categories/brands.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Brands",
|
||||
"icon": "facebook"
|
||||
}
|
||||
55
docs/.vitepress/api/gh-icon/symmetry/[...data].get.ts
Normal file
55
docs/.vitepress/api/gh-icon/symmetry/[...data].get.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
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;
|
||||
});
|
||||
13
docs/.vitepress/api/release-info/index.get.ts
Normal file
13
docs/.vitepress/api/release-info/index.get.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { eventHandler, setResponseHeader } from 'h3';
|
||||
import releaseMeta from '../../data/releaseMetaData.json';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(releaseMeta).map(
|
||||
([name, { createdRelease }]) => [name, createdRelease.version]
|
||||
)
|
||||
);
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
export default eventHandler(() => {
|
||||
return { nitro: 'Is Awesome! asda' };
|
||||
});
|
||||
@@ -1,10 +1,6 @@
|
||||
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';
|
||||
@@ -17,19 +13,6 @@ 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: [
|
||||
@@ -51,9 +34,6 @@ export default defineConfig({
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
groupIconVitePlugin()
|
||||
],
|
||||
},
|
||||
head: [
|
||||
[
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
"name": "arrows",
|
||||
"title": "Arrows"
|
||||
},
|
||||
{
|
||||
"name": "brands",
|
||||
"title": "Brands"
|
||||
},
|
||||
{
|
||||
"name": "buildings",
|
||||
"title": "Buildings"
|
||||
|
||||
@@ -31,12 +31,20 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"@lucide/vue": {
|
||||
"lucide-vue-next": {
|
||||
"order": 2,
|
||||
"icon": "vue",
|
||||
"docsAlias": "lucide-vue",
|
||||
"packageDirname": "vue",
|
||||
"icon": "vue-next",
|
||||
"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,7 +457,18 @@ const SvgPreview = React.forwardRef<
|
||||
<ColoredPath
|
||||
paths={paths}
|
||||
colors={[
|
||||
'##dfdfd6',
|
||||
'#1982c4',
|
||||
'#4267AC',
|
||||
'#6a4c93',
|
||||
'#B55379',
|
||||
'#FF595E',
|
||||
'#FF7655',
|
||||
'#ff924c',
|
||||
'#FFAE43',
|
||||
'#ffca3a',
|
||||
'#C5CA30',
|
||||
'#8ac926',
|
||||
'#52A675',
|
||||
]}
|
||||
/>
|
||||
<Radii
|
||||
|
||||
@@ -9,14 +9,20 @@ type CodeExampleType = {
|
||||
const getIconCodes = (): CodeExampleType => {
|
||||
return [
|
||||
{
|
||||
language: 'js',
|
||||
language: 'html',
|
||||
title: 'Vanilla',
|
||||
code: `\
|
||||
import { createIcons, icons } from 'lucide';
|
||||
<script>
|
||||
import { createIcons, $CamelCase } from 'lucide';
|
||||
|
||||
createIcons({ icons });
|
||||
createIcons({
|
||||
icons: {
|
||||
$CamelCase
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
document.body.append('<i data-lucide="$Name"></i>');\
|
||||
<i data-lucide="$Name"></i>\
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -37,7 +43,7 @@ export default App;
|
||||
language: 'vue',
|
||||
title: 'Vue',
|
||||
code: `<script setup>
|
||||
import { $PascalCase } from '@lucide/vue';
|
||||
import { $PascalCase } from 'lucide-vue-next';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -10,10 +10,11 @@ type CodeExampleType = {
|
||||
const getIconCodes = (): CodeExampleType => {
|
||||
return [
|
||||
{
|
||||
language: 'js',
|
||||
language: 'html',
|
||||
title: 'Vanilla',
|
||||
code: `\
|
||||
import { createIcons, icons } from 'lucide';
|
||||
<script>
|
||||
import { createIcons } from 'lucide';
|
||||
import { $CamelCase } from '@lucide/lab';
|
||||
|
||||
createIcons({
|
||||
@@ -21,8 +22,9 @@ createIcons({
|
||||
$CamelCase
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
document.body.append('<i data-lucide="$Name"></i>');\
|
||||
<i data-lucide="$Name"></i>\
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
/**
|
||||
* 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>`
|
||||
);
|
||||
};
|
||||
}
|
||||
139
docs/.vitepress/sidebar.ts
Normal file
139
docs/.vitepress/sidebar.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
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;
|
||||
@@ -1,66 +0,0 @@
|
||||
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;
|
||||
@@ -1,68 +0,0 @@
|
||||
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 }[] }[];
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
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 }[] }[];
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
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 }[] }[];
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
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 }[] }[];
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
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 }[] }[];
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
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 }[] }[];
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
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,60 +1,71 @@
|
||||
<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 { 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();
|
||||
|
||||
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)
|
||||
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)"
|
||||
/>
|
||||
<kbd v-if="shortcut" class="shortcut" ref="shortcutEl">{{ shortcut }}</kbd>
|
||||
<kbd
|
||||
v-if="shortcut"
|
||||
class="shortcut"
|
||||
ref="shortcutEl"
|
||||
>{{ shortcut }}</kbd
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -71,13 +82,15 @@ defineExpose({
|
||||
height: 40px;
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
font-size: 14px;
|
||||
transition: border-color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
@@ -111,7 +124,7 @@ defineExpose({
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.input-wrapper svg {
|
||||
.input-wrapper > svg {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 12px;
|
||||
|
||||
@@ -1,38 +1,43 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import Input from './Input.vue'
|
||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
||||
import { search } from '../../../data/iconNodes'
|
||||
import { computed, ref } from 'vue';
|
||||
import Input from './Input.vue';
|
||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||
import { search, x } from '../../../data/iconNodes';
|
||||
|
||||
const SearchIcon = createLucideIcon('search', search)
|
||||
const SearchIcon = createLucideIcon('search', search);
|
||||
const XIcon = createLucideIcon('close', x);
|
||||
|
||||
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),
|
||||
});
|
||||
|
||||
const clear = () => {
|
||||
value.value = '';
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -45,8 +50,25 @@ const value = computed({
|
||||
v-model="value"
|
||||
class="input-wrapper"
|
||||
>
|
||||
<template #icon>
|
||||
<component :is="SearchIcon" class="search-icon" />
|
||||
<template #startIcon>
|
||||
<component
|
||||
:is="SearchIcon"
|
||||
class="search-icon"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #endIcon>
|
||||
<button
|
||||
class="clear-button"
|
||||
type="button"
|
||||
v-if="value"
|
||||
>
|
||||
<component
|
||||
:is="XIcon"
|
||||
class="x-icon"
|
||||
@click="clear"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
</Input>
|
||||
</template>
|
||||
@@ -59,18 +81,43 @@ const value = computed({
|
||||
padding: 0 10px 0 12px;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
color: var(--vp-c-text-1);
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
transition: border-color 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.input:hover, .input:focus {
|
||||
.input:hover,
|
||||
.input:focus {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
|
||||
.input-wrapper:deep(.input) {
|
||||
/* padding: 12px 24px; */
|
||||
padding-block: 12px;
|
||||
font-size: 14px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.clear-button {
|
||||
position: absolute;
|
||||
padding: 4px;
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: transparent;
|
||||
border-radius: 6px;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.clear-button:hover {
|
||||
background: var(--vp-button-alt-bg);
|
||||
}
|
||||
|
||||
.input-wrapper:deep(.input)::-webkit-search-decoration,
|
||||
.input-wrapper:deep(.input)::-webkit-search-cancel-button,
|
||||
.input-wrapper:deep(.input)::-webkit-search-results-button,
|
||||
.input-wrapper:deep(.input)::-webkit-search-results-decoration {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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;
|
||||
}>()
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
<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>
|
||||
@@ -1,31 +0,0 @@
|
||||
<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,226 +0,0 @@
|
||||
<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>
|
||||
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
/* background-color: var(--vp-c-neutral); */
|
||||
box-shadow: var(--vp-shadow-1);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
<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>
|
||||
@@ -1,48 +0,0 @@
|
||||
<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>
|
||||
@@ -1,50 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
<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>
|
||||
@@ -1,51 +0,0 @@
|
||||
<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>
|
||||
@@ -1,22 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import Badge from '../base/Badge.vue';
|
||||
import HomeContainer from './HomeContainer.vue';
|
||||
import { data } from './HomeHeroInfoBefore.data'
|
||||
import { data } from './HomeHeroBefore.data'
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<Badge :href="`https://github.com/lucide-icons/lucide/releases/tag/${data.version}`">v{{ data.version }}</Badge>
|
||||
<HomeContainer class="container">
|
||||
<Badge
|
||||
:href="`https://github.com/lucide-icons/lucide/releases/tag/${data.version}`"
|
||||
>v{{ data.version }}</Badge>
|
||||
</HomeContainer>
|
||||
</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;
|
||||
}
|
||||
@@ -31,9 +34,9 @@ import { data } from './HomeHeroInfoBefore.data'
|
||||
.container {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,428 +0,0 @@
|
||||
<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,13 +1,162 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import HomeHeroIconsAnimation from './HomeHeroIconsAnimation.vue'
|
||||
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)
|
||||
})
|
||||
|
||||
const animationRun = ref(1)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HomeHeroIconsAnimation
|
||||
:key="animationRun"
|
||||
@animation-complete="animationRun++"
|
||||
/>
|
||||
<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>
|
||||
|
||||
</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>
|
||||
|
||||
@@ -13,9 +13,9 @@ export default {
|
||||
label: 'Lucide documentation for React',
|
||||
},
|
||||
{
|
||||
name: 'lucide-vue',
|
||||
name: 'lucide-vue-next',
|
||||
logo: '/framework-logos/vue.svg',
|
||||
label: 'Lucide documentation for Vue',
|
||||
label: 'Lucide documentation for Vue 3',
|
||||
},
|
||||
{
|
||||
name: 'lucide-svelte',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
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 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';
|
||||
|
||||
@@ -29,12 +29,7 @@ const props = defineProps<{
|
||||
|
||||
const iconComponent = computed(() => {
|
||||
if (!props.name || !props.iconNode) return null;
|
||||
try {
|
||||
return createLucideIcon(props.name, props.iconNode);
|
||||
} catch (error) {
|
||||
console.warn(`Icon ${props.name} not found, using fallback`);
|
||||
return null;
|
||||
}
|
||||
return createLucideIcon(props.name, props.iconNode);
|
||||
});
|
||||
|
||||
const CalendarIcon = createLucideIcon('calendar', Calendar.iconNode);
|
||||
@@ -66,7 +61,7 @@ const prettyName = props.name
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="showcase" v-if="iconComponent">
|
||||
<section class="showcase">
|
||||
<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, watch, watchEffect } from 'vue';
|
||||
import { ref, computed, defineAsyncComponent, onMounted } 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 from './IconsCategory.vue';
|
||||
import IconsCategory, { CategoryRow } 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,26 +71,27 @@ 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(() => {
|
||||
@@ -107,26 +108,24 @@ 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) {
|
||||
@@ -145,16 +144,13 @@ 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 ...`"
|
||||
@@ -166,8 +162,9 @@ watchEffect(() => {
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="categories.length === 0"
|
||||
:searchQuery="searchQuery"
|
||||
v-if="searchPlaceholder.isNoResults"
|
||||
:searchQuery="searchPlaceholder.query"
|
||||
:isBrandSearch="searchPlaceholder.isBrand"
|
||||
@clear="searchQuery = ''"
|
||||
/>
|
||||
<div v-bind="wrapperProps">
|
||||
@@ -208,8 +205,4 @@ watchEffect(() => {
|
||||
.icons {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.overview-container {
|
||||
padding-bottom: 288px;
|
||||
}
|
||||
</style>
|
||||
|
||||
21
docs/.vitepress/theme/components/icons/IconsOverview.data.ts
Normal file
21
docs/.vitepress/theme/components/icons/IconsOverview.data.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { getAllData } from '../../../lib/icons';
|
||||
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
|
||||
import iconsMetaData from '../../../data/iconMetaData';
|
||||
import { fetchAllReleases } from '../../../../scripts/writeReleaseMetadata.mts';
|
||||
import { satisfies } from 'semver';
|
||||
|
||||
export default {
|
||||
async load() {
|
||||
const versions = await fetchAllReleases();
|
||||
|
||||
const mappedVersions = versions
|
||||
.map((tag) => tag.replace('v', ''))
|
||||
.reverse()
|
||||
|
||||
mappedVersions.length = 100
|
||||
|
||||
return {
|
||||
versions: mappedVersions,
|
||||
};
|
||||
},
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, defineAsyncComponent, onMounted, onBeforeUnmount, watch } from 'vue';
|
||||
import { ref, computed, defineAsyncComponent, onMounted, watch, watchEffect } from 'vue';
|
||||
import type { IconEntity } from '../../types';
|
||||
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
||||
import { useRoute } from 'vitepress';
|
||||
@@ -11,8 +11,13 @@ import useSearchShortcut from '../../utils/useSearchShortcut';
|
||||
import StickyBar from './StickyBar.vue';
|
||||
import useFetchTags from '../../composables/useFetchTags';
|
||||
import useFetchCategories from '../../composables/useFetchCategories';
|
||||
import useFetchVersionIcons from '../../composables/useFetchVersionIcons';
|
||||
import chunkArray from '../../utils/chunkArray';
|
||||
import CarbonAdOverlay from './CarbonAdOverlay.vue';
|
||||
import VersionSelect from './VersionSelect.vue';
|
||||
import { sort, satisfies } from 'semver';
|
||||
import useSearchPlaceholder from '../../utils/useSearchPlaceholder.ts';
|
||||
import { data as versionData } from './IconsOverview.data';
|
||||
|
||||
const ICON_SIZE = 56;
|
||||
const ICON_GRID_GAP = 8;
|
||||
@@ -31,32 +36,49 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const activeIconName = ref(null);
|
||||
const selectedVersion = ref();
|
||||
|
||||
const { execute: fetchTags, data: tags } = useFetchTags();
|
||||
const { execute: fetchCategories, data: categories } = useFetchCategories();
|
||||
const { execute: fetchVersionIcons, data: versionIcons } = useFetchVersionIcons(selectedVersion);
|
||||
|
||||
watch(selectedVersion, () => {
|
||||
fetchVersionIcons();
|
||||
});
|
||||
|
||||
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(() => {
|
||||
if (tags.value == null) {
|
||||
return props.icons;
|
||||
let icons = props.icons;
|
||||
|
||||
if (tags.value != null && categories.value != null) {
|
||||
icons = props.icons.map((icon) => {
|
||||
const iconTags = tags.value[icon.name];
|
||||
const iconCategories = categories.value?.[icon.name] ?? [];
|
||||
|
||||
return {
|
||||
...icon,
|
||||
tags: iconTags,
|
||||
categories: iconCategories,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return props.icons.map((icon) => {
|
||||
const iconTags = tags.value[icon.name];
|
||||
const iconCategories = categories.value?.[icon.name] ?? [];
|
||||
if (selectedVersion.value == null || versionIcons.value == null) {
|
||||
console.log('no release info');
|
||||
|
||||
return {
|
||||
...icon,
|
||||
tags: iconTags,
|
||||
categories: iconCategories,
|
||||
};
|
||||
});
|
||||
return icons;
|
||||
}
|
||||
|
||||
return Object.values(versionIcons.value).filter(([name, iconNode]) => ({
|
||||
name,
|
||||
iconNode,
|
||||
}));
|
||||
});
|
||||
|
||||
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
||||
@@ -71,29 +93,27 @@ 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;
|
||||
@@ -108,13 +128,19 @@ function onFocusSearchInput() {
|
||||
}
|
||||
}
|
||||
|
||||
// function onFocusVersionSelect() {
|
||||
// if (releaseInfo.value == null) {
|
||||
// fetchReleaseInfo();
|
||||
// }
|
||||
// }
|
||||
|
||||
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
||||
|
||||
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
|
||||
|
||||
watch(searchQueryDebounced, () => {
|
||||
scrollTo(0)
|
||||
})
|
||||
scrollTo(0);
|
||||
});
|
||||
|
||||
function handleCloseDrawer() {
|
||||
setActiveIconName('');
|
||||
@@ -124,20 +150,28 @@ function handleCloseDrawer() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="overviewEl" class="overview-container">
|
||||
<div
|
||||
ref="overviewEl"
|
||||
class="overview-container"
|
||||
>
|
||||
<StickyBar>
|
||||
<InputSearch
|
||||
:placeholder="`Search ${icons.length} icons ...`"
|
||||
:placeholder="`Search ${mappedIcons.length} icons ...`"
|
||||
v-model="searchQuery"
|
||||
ref="searchInput"
|
||||
:shortcut="kbdSearchShortcut"
|
||||
class="input-wrapper"
|
||||
@focus="onFocusSearchInput"
|
||||
/>
|
||||
<VersionSelect
|
||||
v-model="selectedVersion"
|
||||
:versions="versionData.versions"
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="searchResults.length === 0 && searchQuery !== ''"
|
||||
:searchQuery="searchQuery"
|
||||
v-if="searchPlaceholder.isNoResults"
|
||||
:searchQuery="searchPlaceholder.query"
|
||||
:isBrandSearch="searchPlaceholder.isBrand"
|
||||
@clear="searchQuery = ''"
|
||||
/>
|
||||
<IconGrid
|
||||
@@ -182,10 +216,5 @@ function handleCloseDrawer() {
|
||||
|
||||
.input-wrapper {
|
||||
width: 100%;
|
||||
view-transition-name: icons-search-box;
|
||||
}
|
||||
|
||||
.overview-container {
|
||||
padding-bottom: 288px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,56 +1,218 @@
|
||||
<script setup lang="ts">
|
||||
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'
|
||||
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';
|
||||
|
||||
defineProps<{
|
||||
searchQuery: string
|
||||
}>()
|
||||
const { searchQuery, isBrandSearch } = defineProps<{
|
||||
searchQuery: string;
|
||||
isBrandSearch: boolean;
|
||||
}>();
|
||||
|
||||
defineEmits(['clear'])
|
||||
defineEmits(['clear']);
|
||||
|
||||
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)
|
||||
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);
|
||||
|
||||
onMounted(() => {
|
||||
useEventListener(document, 'mousemove', (mouseEvent) => {
|
||||
const {width, height, x, y} = animalIcon.value.getBoundingClientRect()
|
||||
const { width, x } = placeholderIcon.value.getBoundingClientRect();
|
||||
|
||||
const centerX = (width / 2) + x
|
||||
|
||||
flip.value = mouseEvent.x < centerX
|
||||
})
|
||||
})
|
||||
const centerX = width / 2 + x;
|
||||
|
||||
flip.value = !isBrandSearch && mouseEvent.x < centerX;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="no-results">
|
||||
<component
|
||||
:is="animalComponent"
|
||||
class="animal-icon"
|
||||
ref="animalIcon"
|
||||
:is="iconComponent"
|
||||
class="placeholder-icon"
|
||||
ref="placeholderIcon"
|
||||
:class="{ flip }"
|
||||
:strokeWidth="1"
|
||||
/>
|
||||
<h2 class="no-results-text">
|
||||
No icons found for '{{ searchQuery }}'
|
||||
</h2>
|
||||
<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>
|
||||
<VPButton
|
||||
text="Clear your search and try again"
|
||||
theme="alt"
|
||||
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"
|
||||
@click="$emit('clear')"
|
||||
/>
|
||||
<span class="text-divider">or</span>
|
||||
<VPButton
|
||||
text="Search on Github issues"
|
||||
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"
|
||||
theme="alt"
|
||||
:href="`https://github.com/lucide-icons/lucide/issues?q=is%3Aopen+${searchQuery}`"
|
||||
target="_blank"
|
||||
@@ -63,33 +225,38 @@ onMounted(() => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding-block: 48px;
|
||||
}
|
||||
|
||||
.animal-icon {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
color: var(--vp-c-neutral);
|
||||
opacity: 0.8;
|
||||
margin-top: 72px;
|
||||
.placeholder-icon {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.animal-icon.flip {
|
||||
.placeholder-icon.flip {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.animal-icon {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
}
|
||||
}
|
||||
|
||||
.no-results-text {
|
||||
line-height: 40px;
|
||||
line-height: 1.35;
|
||||
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;
|
||||
text-align: center;
|
||||
color: var(--vp-c-text-2);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.text-divider {
|
||||
@@ -97,4 +264,10 @@ 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>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
margin-top: -32px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 32px;
|
||||
background: var(--vp-c-bg);
|
||||
box-shadow: 0 16px 24px var(--vp-c-bg);
|
||||
|
||||
221
docs/.vitepress/theme/components/icons/VersionSelect.vue
Normal file
221
docs/.vitepress/theme/components/icons/VersionSelect.vue
Normal file
@@ -0,0 +1,221 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxInput,
|
||||
ComboboxButton,
|
||||
ComboboxOptions,
|
||||
ComboboxOption,
|
||||
} from '@headlessui/vue';
|
||||
|
||||
import { chevronsUpDown, check } from '../../../data/iconNodes';
|
||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||
|
||||
const model = defineModel<string | undefined>();
|
||||
const props = defineProps<{
|
||||
versions: string[];
|
||||
}>();
|
||||
|
||||
let query = ref('');
|
||||
|
||||
let filteredVersions = computed(() =>
|
||||
query.value === ''
|
||||
? [...props.versions]
|
||||
: props.versions.filter((version) =>
|
||||
version
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '')
|
||||
.includes(query.value.toLowerCase().replace(/\s+/g, '')),
|
||||
),
|
||||
);
|
||||
|
||||
const emit = defineEmits(['focus']);
|
||||
|
||||
const ChevronUpDown = createLucideIcon('ChevronUpDown', chevronsUpDown);
|
||||
const Check = createLucideIcon('Check', check);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Combobox v-model="model">
|
||||
<div class="combobox-container">
|
||||
<div class="combobox-input-container">
|
||||
<ComboboxInput
|
||||
class="combobox-input"
|
||||
placeholder="Latest version"
|
||||
@change="query = $event.target.value"
|
||||
@focus="emit('focus')"
|
||||
/>
|
||||
<ComboboxButton class="combobox-button">
|
||||
<ChevronUpDown
|
||||
class="icon-chevron"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</ComboboxButton>
|
||||
</div>
|
||||
|
||||
<ComboboxOptions class="combobox-options">
|
||||
<div
|
||||
v-if="versions.length === 0 && query !== ''"
|
||||
class="combobox-no-results"
|
||||
>
|
||||
No match!
|
||||
</div>
|
||||
|
||||
<ComboboxOption
|
||||
v-for="version in filteredVersions"
|
||||
as="template"
|
||||
:key="version"
|
||||
:value="version"
|
||||
v-slot="{ selected, active }"
|
||||
>
|
||||
<li
|
||||
class="combobox-option"
|
||||
:class="{ active: active }"
|
||||
>
|
||||
<span
|
||||
class="combobox-option-text"
|
||||
:class="{ selected: selected }"
|
||||
>
|
||||
{{ version }}
|
||||
</span>
|
||||
<span
|
||||
v-if="selected"
|
||||
class="combobox-option-icon"
|
||||
:class="{ active: active }"
|
||||
>
|
||||
<Check
|
||||
class="icon-check"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
</ComboboxOptions>
|
||||
</div>
|
||||
</Combobox>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.combobox-input-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
min-width: 160px;
|
||||
cursor: default;
|
||||
overflow: hidden;
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
text-align: left;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.combobox-input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
color: var(--vp-c-text-1);
|
||||
border: 1px solid transparent;
|
||||
transition: border-color 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.combobox-input:hover,
|
||||
.combobox-input:focus {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
|
||||
.combobox-button {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
padding: 9px 8px;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: 0.5rem;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.icon-chevron {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.combobox-options {
|
||||
position: absolute;
|
||||
margin-top: 0.25rem;
|
||||
width: 100%;
|
||||
max-width: 160px;
|
||||
padding: 12px;
|
||||
min-width: 128px;
|
||||
overflow-y: auto;
|
||||
border-radius: 12px;
|
||||
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: 240px;
|
||||
outline: none;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.combobox-no-results {
|
||||
position: relative;
|
||||
cursor: default;
|
||||
padding: 0.5rem 1rem;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.combobox-option {
|
||||
position: relative;
|
||||
cursor: default;
|
||||
padding: 2px 8px;
|
||||
border-radius: 6px;
|
||||
color: var(--vp-c-text-1);
|
||||
line-height: 32px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
transition:
|
||||
background-color 0.25s,
|
||||
color 0.25s;
|
||||
}
|
||||
|
||||
.combobox-option.active {
|
||||
color: var(--vp-c-brand);
|
||||
background-color: var(--vp-c-default-soft);
|
||||
}
|
||||
|
||||
.combobox-option-text {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.combobox-option-text.selected {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.combobox-option-icon {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 0.75rem;
|
||||
color: var(--vp-c-brand) !important;
|
||||
}
|
||||
|
||||
.combobox-option-icon.active {
|
||||
color: var(--vp-c-brand) !important;
|
||||
}
|
||||
|
||||
.icon-check {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,379 +0,0 @@
|
||||
<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>
|
||||
@@ -1,248 +0,0 @@
|
||||
<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>
|
||||
17
docs/.vitepress/theme/composables/useFetchVersionIcons.ts
Normal file
17
docs/.vitepress/theme/composables/useFetchVersionIcons.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useFetch } from '@vueuse/core';
|
||||
import { computed, Ref } from 'vue';
|
||||
|
||||
const useFetchVersionIcons = (version: Ref<string | undefined>) =>{
|
||||
const fetchUrl = computed(() => {
|
||||
if(version.value == null ) return ''
|
||||
return `https://unpkg.com/lucide-static@${version.value}/icon-nodes.json`
|
||||
})
|
||||
return useFetch<Record<string, string>>(
|
||||
fetchUrl,
|
||||
{
|
||||
refetch: true
|
||||
}
|
||||
).json();
|
||||
}
|
||||
|
||||
export default useFetchVersionIcons;
|
||||
@@ -1,36 +1,27 @@
|
||||
import { h, nextTick, onMounted, watch } from 'vue';
|
||||
import { h } from 'vue';
|
||||
import DefaultTheme from 'vitepress/theme';
|
||||
import './style.css';
|
||||
import 'virtual:group-icons.css'
|
||||
import { Theme, useRouter } from 'vitepress';
|
||||
import { Theme } from 'vitepress';
|
||||
import IconsSidebarNavAfter from './layouts/IconsSidebarNavAfter.vue';
|
||||
import HomeHeroIconsCard from './components/home/HomeHeroIconsCard.vue';
|
||||
import HomeHeroAfter from './components/home/HomeHeroAfter.vue';
|
||||
import HomeHeroInfoBefore from './components/home/HomeHeroInfoBefore.vue';
|
||||
import HomeHeroBefore from './components/home/HomeHeroBefore.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, {
|
||||
'sidebar-nav-before': () => h(FrameworkSelect),
|
||||
'home-hero-info-before': () => h(HomeHeroInfoBefore),
|
||||
'home-hero-before': () => h(HomeHeroBefore),
|
||||
'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,12 +26,6 @@
|
||||
|
||||
--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 {
|
||||
@@ -49,23 +43,8 @@
|
||||
--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;
|
||||
@@ -91,7 +70,7 @@
|
||||
.VPHomeHero .container .image {
|
||||
margin: 0;
|
||||
order: 2;
|
||||
/* margin-top: 32px; */
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.VPHomeHero .container .image-container {
|
||||
@@ -112,10 +91,6 @@
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.VPHomeHero .container {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPHomeHero .container .image {
|
||||
order: 1;
|
||||
@@ -135,12 +110,60 @@
|
||||
.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);
|
||||
}
|
||||
|
||||
41
docs/.vitepress/theme/utils/useSearchPlaceholder.ts
Normal file
41
docs/.vitepress/theme/utils/useSearchPlaceholder.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
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;
|
||||
}
|
||||
26
docs/.vitepress/vue-shim.d.ts
vendored
26
docs/.vitepress/vue-shim.d.ts
vendored
@@ -11,12 +11,6 @@ declare module '*.data.ts' {
|
||||
export { data };
|
||||
}
|
||||
|
||||
declare module '*.data' {
|
||||
const data: any;
|
||||
|
||||
export { data };
|
||||
}
|
||||
|
||||
declare module '*.wasm' {}
|
||||
|
||||
declare const resvg_wasm: RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
@@ -28,23 +22,3 @@ declare module 'node:module' {
|
||||
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 in dept
|
||||
# Accessibility
|
||||
|
||||
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,8 +104,7 @@ 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
|
||||
|
||||
@@ -133,7 +132,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
|
||||
|
||||
@@ -216,16 +215,3 @@ 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>
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
House,
|
||||
HouseIcon,
|
||||
LucideHouse,
|
||||
} from "lucide-react-native";
|
||||
} from "lucide-react";
|
||||
```
|
||||
|
||||
## Choosing import name style
|
||||
@@ -24,16 +24,52 @@ This can be done by creating a custom module declaration file to override the lu
|
||||
```json [.vscode/settings.json]
|
||||
{
|
||||
"typescript.preferences.autoImportFileExcludePatterns": [
|
||||
"lucide-react-native",
|
||||
"lucide-react", // or
|
||||
"lucide-preact", // or
|
||||
"lucide-react-native", // or
|
||||
"lucide-vue-next",
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Create a custom module declaration file
|
||||
|
||||
Only available for `lucide-react`, `lucide-preact`, `lucide-react-native`, `lucide-vue-next` package.
|
||||
This will enable you to choose the import name style you want to use in your project.
|
||||
|
||||
::: code-group
|
||||
|
||||
```ts [React]
|
||||
declare module "lucide-react" {
|
||||
// Prefixed import names
|
||||
export * from "lucide-react/dist/lucide-react.prefixed";
|
||||
// or
|
||||
// Suffixed import names
|
||||
export * from "lucide-react/dist/lucide-react.suffixed";
|
||||
}
|
||||
```
|
||||
|
||||
```ts [Vue]
|
||||
declare module "lucide-vue-next" {
|
||||
// Prefixed import names
|
||||
export * from "lucide-vue-next/dist/lucide-vue-next.prefixed";
|
||||
// or
|
||||
// Suffixed import names
|
||||
export * from "lucide-vue-next/dist/lucide-vue-next.suffixed";
|
||||
}
|
||||
```
|
||||
|
||||
```ts [Preact]
|
||||
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";
|
||||
}
|
||||
```
|
||||
|
||||
```ts [React Native]
|
||||
declare module "lucide-react-native" {
|
||||
// Prefixed import names
|
||||
export * from "lucide-react-native/dist/lucide-react-native.prefixed";
|
||||
@@ -43,13 +79,15 @@ declare module "lucide-react-native" {
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
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-react-native.d.ts`.
|
||||
Easiest way is to create a `@types` folder in your project root and name the file `[package-name].d.ts`.
|
||||
|
||||
### Import name styles
|
||||
|
||||
| Import Style | Available imports | Declaration file import |
|
||||
| ------------- | --------------------------- | ----------------------- |
|
||||
| Default | Home, HomeIcon, LucideHome | |
|
||||
| Prefixed | LucideHome | lucide-react-native.prefixed |
|
||||
| Suffixed | HomeIcon | lucide-react-native.suffixed |
|
||||
| Prefixed | LucideHome | [package].prefixed |
|
||||
| Suffixed | HomeIcon | [package].suffixed |
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { Sandpack } from 'sandpack-vue3'
|
||||
import sandpackTheme from '../../../.vitepress/theme/sandpackTheme.json'
|
||||
import sandpackTheme from '../../.vitepress/theme/sandpackTheme.json'
|
||||
import sizeIconExample from './examples/filled-icon-example/files.ts'
|
||||
</script>
|
||||
|
||||
@@ -12,12 +12,12 @@ Fill can still be used and will work fine on certain icons.
|
||||
|
||||
Example with stars:
|
||||
<Sandpack
|
||||
template="vue"
|
||||
template="react"
|
||||
:theme="sandpackTheme"
|
||||
:files="sizeIconExample"
|
||||
:customSetup='{
|
||||
dependencies: {
|
||||
"lucide-vue-next": "latest"
|
||||
"lucide-react": "latest"
|
||||
}
|
||||
}'
|
||||
:options="{
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { Sandpack } from 'sandpack-vue3'
|
||||
import sandpackTheme from '../../../.vitepress/theme/sandpackTheme.json'
|
||||
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'
|
||||
|
||||
@@ -8,32 +8,9 @@ import globalAbsoluteStrokewidthExample from './examples/global-styling-absolute
|
||||
|
||||
# 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-react` package.
|
||||
|
||||
```tsx
|
||||
import { LucideProvider, Home } from 'lucide-react';
|
||||
|
||||
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`.
|
||||
Adjusting icons can be done by [color](../basics/color.md), [size](../basics/sizing.md) and [stroke width](../basics/stroke-width.md).
|
||||
|
||||
By default, all icons have a **color** value of `currentColor`, a **size** value of `24px`, and a **stroke width** of `2`. Styling icons individually can be done by passing props to the icon component.
|
||||
|
||||
## Style by using CSS
|
||||
Styling icons is easy to accomplish using CSS.
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { Sandpack } from 'sandpack-vue3'
|
||||
import sandpackTheme from '../../../.vitepress/theme/sandpackTheme.json'
|
||||
import sandpackTheme from '../../.vitepress/theme/sandpackTheme.json'
|
||||
import buttonExampleFiles from './examples/button-example/files.ts'
|
||||
import iconColorExampleFiles from './examples/color-icon/files.ts'
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@ import App from './App.js?raw'
|
||||
import styles from '../styles.css?raw'
|
||||
|
||||
const files = {
|
||||
'/App.js': {
|
||||
'App.js': {
|
||||
code: App,
|
||||
active: true,
|
||||
},
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { Sandpack } from 'sandpack-vue3'
|
||||
import sandpackTheme from '../../../.vitepress/theme/sandpackTheme.json'
|
||||
import sandpackTheme from '../../.vitepress/theme/sandpackTheme.json'
|
||||
import sizeIconExample from './examples/size-icon-example/files.ts'
|
||||
import sizeIconCssExample from './examples/size-icon-css-example/files.ts'
|
||||
import sizeIconFontExample from './examples/size-icon-font-example/files.ts'
|
||||
@@ -66,7 +66,7 @@ It is possible to resize icons based on font size. This can be achieved using th
|
||||
|
||||
### Resizing with Tailwind
|
||||
|
||||
`size-*` utilities can be used to adjust the size of the icon. See the [Tailwind documentation](https://tailwindcss.com/docs/width#setting-both-width-and-height) for more information on the `size-*` utilities.
|
||||
`h-*` and `w-*` utilities can be used to adjust the size of the icon.
|
||||
|
||||
<Sandpack
|
||||
template="react"
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { Sandpack } from 'sandpack-vue3'
|
||||
import sandpackTheme from '../../../.vitepress/theme/sandpackTheme.json'
|
||||
import sandpackTheme from '../../.vitepress/theme/sandpackTheme.json'
|
||||
import strokeWidth from './examples/stroke-width-icon/files.ts'
|
||||
import absoluteStrokeWidth from './examples/absolute-stroke-width-icon/files.ts'
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user