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 |
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 }}
|
||||
|
||||
12
.github/workflows/release.yml
vendored
@@ -20,7 +20,7 @@ on:
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: read
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
pre-release:
|
||||
@@ -72,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
|
||||
|
||||
@@ -109,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
|
||||
|
||||
@@ -157,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
@@ -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
@@ -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
|
||||
|
||||
|
||||
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"
|
||||
}
|
||||
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
@@ -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' };
|
||||
});
|
||||
@@ -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>\
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
/* background-color: var(--vp-c-neutral); */
|
||||
box-shadow: var(--vp-shadow-1);
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
@@ -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,
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -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,19 +11,24 @@ 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;
|
||||
|
||||
const initialGridItems = computed(() => {
|
||||
if (containerWidth.value === 0) return 120;
|
||||
|
||||
|
||||
const itemsPerRow = columnSize.value || 10;
|
||||
const visibleRows = Math.ceil(window.innerHeight / (ICON_SIZE + ICON_GRID_GAP));
|
||||
|
||||
return Math.min(itemsPerRow * (visibleRows + 2), 200);
|
||||
return Math.min(itemsPerRow * (visibleRows + 2), 200);
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -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
|
||||
@@ -183,8 +217,4 @@ function handleCloseDrawer() {
|
||||
.input-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.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
@@ -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>
|
||||
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;
|
||||
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;
|
||||
}
|
||||
@@ -9,13 +9,14 @@
|
||||
"docs:build": "pnpm run /^prebuild:.*/ && vitepress build",
|
||||
"docs:preview": "vitepress preview",
|
||||
"build:docs": "vitepress build",
|
||||
"prebuild:iconNodes": "node --experimental-strip-types ./scripts/writeIconNodes.mjs",
|
||||
"prebuild:metaJson": "node --experimental-strip-types ./scripts/writeIconMetaIndex.mjs",
|
||||
"prebuild:releaseJson": "node --experimental-strip-types ./scripts/writeReleaseMetadata.mjs",
|
||||
"prebuild:categoriesJson": "node --experimental-strip-types ./scripts/writeCategoriesMetadata.mjs",
|
||||
"prebuild:relatedIcons": "node --experimental-strip-types ./scripts/writeIconRelatedIcons.mjs",
|
||||
"prebuild:iconDetails": "node --experimental-strip-types ./scripts/writeIconDetails.mjs",
|
||||
"postbuild:vercelJson": "node --experimental-strip-types ./scripts/writeVercelOutput.mjs",
|
||||
"prebuild:iconNodes": "node ./scripts/writeIconNodes.mts",
|
||||
"prebuild:metaJson": "node ./scripts/writeIconMetaIndex.mts",
|
||||
"prebuild:releaseJson": "node ./scripts/writeReleaseMetadata.mts",
|
||||
"prebuild:categoriesJson": "node ./scripts/writeCategoriesMetadata.mts",
|
||||
"prebuild:relatedIcons": "node ./scripts/writeIconRelatedIcons.mts",
|
||||
"prebuild:iconDetails": "node ./scripts/writeIconDetails.mts",
|
||||
"prebuild:brandStopwords": "node ./scripts/writeBrandStopwords.mts",
|
||||
"postbuild:vercelJson": "node ./scripts/writeVercelOutput.mts",
|
||||
"dev": "npx nitropack dev",
|
||||
"prebuild:api": "npx nitropack prepare",
|
||||
"build:api": "npx nitropack build",
|
||||
|
||||
15
docs/scripts/writeBrandStopwords.mts
Normal file
@@ -0,0 +1,15 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
const currentDir = process.cwd();
|
||||
const dataDirectory = path.resolve(currentDir, '.vitepress/data');
|
||||
const stopwordsSource = path.resolve(currentDir, `../brand-stopwords.json`);
|
||||
const stopwordsFile = path.resolve(dataDirectory, `brandStopwords.json`);
|
||||
|
||||
fs.copyFile(stopwordsSource, stopwordsFile)
|
||||
.then(() => {
|
||||
console.log('Successfully copied brandStopwords.json file');
|
||||
})
|
||||
.catch((error) => {
|
||||
throw new Error(`Something went wrong generating the brandStopwords.json file,\n ${error}`);
|
||||
});
|
||||
@@ -42,7 +42,7 @@ const getRelatedIcons = (currentIcon, icons) => {
|
||||
};
|
||||
|
||||
const iconsMetaDataPromises = svgFiles.map(async (iconName) => {
|
||||
const metaDataFileContent = await fs.promises.readFile(`../icons/${iconName}`);
|
||||
const metaDataFileContent = await fs.promises.readFile(`../icons/${iconName}`, 'utf-8');
|
||||
const metaData = JSON.parse(metaDataFileContent);
|
||||
|
||||
const name = iconName.replace('.json', '');
|
||||
@@ -29,7 +29,7 @@ if (!fs.existsSync(releaseMetaDataDirectory)) {
|
||||
fs.mkdirSync(releaseMetaDataDirectory);
|
||||
}
|
||||
|
||||
const fetchAllReleases = async () => {
|
||||
export const fetchAllReleases = async () => {
|
||||
await git.fetch('https://github.com/lucide-icons/lucide.git', '--tags');
|
||||
|
||||
return Promise.all(
|
||||
@@ -73,7 +73,7 @@ const comparisonsPromises = tags.map(async (tag, index) => {
|
||||
const comparisons = await Promise.all(comparisonsPromises);
|
||||
const newReleaseMetaData = {};
|
||||
|
||||
comparisons.forEach(({ tag, iconFiles, date } = {}) => {
|
||||
comparisons.forEach(({ tag, iconFiles, date } = {} as typeof comparisons[number]) => {
|
||||
if (tag == null) return;
|
||||
|
||||
iconFiles.forEach(({ status, file, renamedFile }) => {
|
||||
@@ -11,7 +11,7 @@ const iconMetaData = await getIconMetaData(path.resolve(scriptDir, '../../icons'
|
||||
const iconAliasesRedirectRoutes = Object.entries(iconMetaData)
|
||||
.filter(([, { aliases }]) => aliases?.length)
|
||||
.map(([iconName, { aliases }]) => {
|
||||
const aliasRouteMatches = aliases.map((alias) => alias.name).join('|');
|
||||
const aliasRouteMatches = aliases.map((alias) => typeof alias === 'string' ? alias : alias.name).join('|');
|
||||
|
||||
return {
|
||||
src: `/icons/${aliasRouteMatches}`,
|
||||
@@ -32,7 +32,7 @@ const vercelRouteConfig = {
|
||||
},
|
||||
{
|
||||
src: '(?<url>/api/.*)',
|
||||
dest: '/__fallback?url=$url',
|
||||
dest: '/__nitro?url=$url',
|
||||
},
|
||||
...iconAliasesRedirectRoutes,
|
||||
],
|
||||
22
icons/balloon.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"peteruithoven"
|
||||
],
|
||||
"tags": [
|
||||
"party",
|
||||
"festival",
|
||||
"congratulations",
|
||||
"celebration",
|
||||
"decoration",
|
||||
"colorful",
|
||||
"floating",
|
||||
"fun",
|
||||
"birthday",
|
||||
"event",
|
||||
"entertainment"
|
||||
],
|
||||
"categories": [
|
||||
"emoji"
|
||||
]
|
||||
}
|
||||
15
icons/balloon.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M12 16v1a2 2 0 0 0 2 2h1a2 2 0 0 1 2 2v1" />
|
||||
<path d="M12 6a2 2 0 0 1 2 2" />
|
||||
<path d="M18 8c0 4-3.5 8-6 8s-6-4-6-8a6 6 0 0 1 12 0" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 358 B |
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"karsa-mistmere"
|
||||
"karsa-mistmere",
|
||||
"jguddas"
|
||||
],
|
||||
"tags": [
|
||||
"cleaning",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m16 22-1-4" />
|
||||
<path d="M19 13.99a1 1 0 0 0 1-1V12a2 2 0 0 0-2-2h-3a1 1 0 0 1-1-1V4a2 2 0 0 0-4 0v5a1 1 0 0 1-1 1H6a2 2 0 0 0-2 2v.99a1 1 0 0 0 1 1" />
|
||||
<path d="M5 14h14l1.973 6.767A1 1 0 0 1 20 22H4a1 1 0 0 1-.973-1.233z" />
|
||||
<path d="M19 14a1 1 0 0 0 1-1v-1a2 2 0 0 0-2-2h-3a1 1 0 0 1-1-1V4a2 2 0 0 0-4 0v5a1 1 0 0 1-1 1H6a2 2 0 0 0-2 2v1a1 1 0 0 0 1 1" />
|
||||
<path d="M19 14H5l-1.973 6.767A1 1 0 0 0 4 22h16a1 1 0 0 0 .973-1.233z" />
|
||||
<path d="m8 22 1-4" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 474 B After Width: | Height: | Size: 470 B |
@@ -2,7 +2,8 @@
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"vqh2602",
|
||||
"jguddas"
|
||||
"jguddas",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"tags": [
|
||||
"water",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M7.2 14.8a2 2 0 0 1 2 2" />
|
||||
<path d="M7.001 15.085A1.5 1.5 0 0 1 9 16.5" />
|
||||
<circle cx="18.5" cy="8.5" r="3.5" />
|
||||
<circle cx="7.5" cy="16.5" r="5.5" />
|
||||
<circle cx="7.5" cy="4.5" r="2.5" />
|
||||
|
||||
|
Before Width: | Height: | Size: 366 B After Width: | Height: | Size: 377 B |
29
icons/circle-pile.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"colebemis",
|
||||
"nathan-de-pachtere"
|
||||
],
|
||||
"tags": [
|
||||
"off",
|
||||
"zero",
|
||||
"record",
|
||||
"shape",
|
||||
"circle-pile",
|
||||
"circle",
|
||||
"pile",
|
||||
"stack",
|
||||
"layer",
|
||||
"structure",
|
||||
"form",
|
||||
"group",
|
||||
"collection",
|
||||
"stock",
|
||||
"inventory",
|
||||
"materials",
|
||||
"warehouse"
|
||||
],
|
||||
"categories": [
|
||||
"shapes"
|
||||
]
|
||||
}
|
||||
9
icons/circle-pile.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="19" r="2" />
|
||||
<circle cx="12" cy="5" r="2" />
|
||||
<circle cx="16" cy="12" r="2" />
|
||||
<circle cx="20" cy="19" r="2" />
|
||||
<circle cx="4" cy="19" r="2" />
|
||||
<circle cx="8" cy="12" r="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 397 B |
33
icons/cloud-backup.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"ericfennis",
|
||||
"jguddas",
|
||||
"danielbayley",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"tags": [
|
||||
"storage",
|
||||
"memory",
|
||||
"bytes",
|
||||
"servers",
|
||||
"backup",
|
||||
"timemachine",
|
||||
"rotate",
|
||||
"synchronize",
|
||||
"synchronise",
|
||||
"refresh",
|
||||
"reconnect",
|
||||
"transfer",
|
||||
"data",
|
||||
"security",
|
||||
"upload",
|
||||
"save",
|
||||
"remote",
|
||||
"safety"
|
||||
],
|
||||
"categories": [
|
||||
"arrows",
|
||||
"files"
|
||||
]
|
||||
}
|
||||
15
icons/cloud-backup.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M21 15.251A4.5 4.5 0 0 0 17.5 8h-1.79A7 7 0 1 0 3 13.607" />
|
||||
<path d="M7 11v4h4" />
|
||||
<path d="M8 19a5 5 0 0 0 9-3 4.5 4.5 0 0 0-4.5-4.5 4.82 4.82 0 0 0-3.41 1.41L7 15" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 393 B |
27
icons/cloud-sync.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"colebemis",
|
||||
"csandman",
|
||||
"ericfennis",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"tags": [
|
||||
"synchronize",
|
||||
"synchronise",
|
||||
"refresh",
|
||||
"reconnect",
|
||||
"transfer",
|
||||
"backup",
|
||||
"storage",
|
||||
"upload",
|
||||
"download",
|
||||
"connection",
|
||||
"network",
|
||||
"data"
|
||||
],
|
||||
"categories": [
|
||||
"arrows",
|
||||
"files"
|
||||
]
|
||||
}
|
||||
17
icons/cloud-sync.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m17 18-1.535 1.605a5 5 0 0 1-8-1.5" />
|
||||
<path d="M17 22v-4h-4" />
|
||||
<path d="M20.996 15.251A4.5 4.5 0 0 0 17.495 8h-1.79a7 7 0 1 0-12.709 5.607" />
|
||||
<path d="M7 10v4h4" />
|
||||
<path d="m7 14 1.535-1.605a5 5 0 0 1 8 1.5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 442 B |
28
icons/fishing-hook.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"7ender",
|
||||
"jguddas",
|
||||
"karsa-mistmere",
|
||||
"jamiemlaw"
|
||||
],
|
||||
"tags": [
|
||||
"sea",
|
||||
"boating",
|
||||
"angler",
|
||||
"bait",
|
||||
"reel",
|
||||
"tackle",
|
||||
"marine",
|
||||
"outdoors",
|
||||
"fish",
|
||||
"fishing",
|
||||
"hook",
|
||||
"sports",
|
||||
"travel"
|
||||
],
|
||||
"categories": [
|
||||
"sports",
|
||||
"travel"
|
||||
]
|
||||
}
|
||||
15
icons/fishing-hook.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m17.586 11.414-5.93 5.93a1 1 0 0 1-8-8l3.137-3.137a.707.707 0 0 1 1.207.5V10" />
|
||||
<path d="M20.414 8.586 22 7" />
|
||||
<circle cx="19" cy="10" r="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 369 B |
@@ -4,10 +4,17 @@
|
||||
"Andreto",
|
||||
"ericfennis",
|
||||
"karsa-mistmere",
|
||||
"csandman"
|
||||
"csandman",
|
||||
"jamiemlaw"
|
||||
],
|
||||
"tags": [
|
||||
"torch"
|
||||
"torch",
|
||||
"light",
|
||||
"beam",
|
||||
"emergency",
|
||||
"safety",
|
||||
"tool",
|
||||
"bright"
|
||||
],
|
||||
"categories": [
|
||||
"photography",
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M16 16v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2V10c0-2-2-2-2-4" />
|
||||
<path d="M7 2h11v4c0 2-2 2-2 4v1" />
|
||||
<line x1="11" x2="18" y1="6" y2="6" />
|
||||
<line x1="2" x2="22" y1="2" y2="22" />
|
||||
<path d="M11.652 6H18" />
|
||||
<path d="M12 13v1" />
|
||||
<path d="M16 16v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-8a4 4 0 0 0-.8-2.4l-.6-.8A3 3 0 0 1 6 7V6" />
|
||||
<path d="m2 2 20 20" />
|
||||
<path d="M7.649 2H17a1 1 0 0 1 1 1v4a3 3 0 0 1-.6 1.8l-.6.8a4 4 0 0 0-.55 1.007" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 399 B After Width: | Height: | Size: 470 B |
@@ -2,10 +2,17 @@
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"csandman",
|
||||
"ericfennis"
|
||||
"ericfennis",
|
||||
"jamiemlaw"
|
||||
],
|
||||
"tags": [
|
||||
"torch"
|
||||
"torch",
|
||||
"light",
|
||||
"beam",
|
||||
"emergency",
|
||||
"safety",
|
||||
"tool",
|
||||
"bright"
|
||||
],
|
||||
"categories": [
|
||||
"photography",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M18 6c0 2-2 2-2 4v10a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2V10c0-2-2-2-2-4V2h12z" />
|
||||
<line x1="6" x2="18" y1="6" y2="6" />
|
||||
<line x1="12" x2="12" y1="12" y2="12" />
|
||||
<path d="M12 13v1" />
|
||||
<path d="M17 2a1 1 0 0 1 1 1v4a3 3 0 0 1-.6 1.8l-.6.8A4 4 0 0 0 16 12v8a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2v-8a4 4 0 0 0-.8-2.4l-.6-.8A3 3 0 0 1 6 7V3a1 1 0 0 1 1-1z" />
|
||||
<path d="M6 6h12" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 379 B After Width: | Height: | Size: 422 B |
21
icons/hd.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"ahtohbi4",
|
||||
"jamiemlaw",
|
||||
"karsa-mistmere",
|
||||
"jguddas"
|
||||
],
|
||||
"tags": [
|
||||
"tv",
|
||||
"resolution",
|
||||
"video",
|
||||
"high definition",
|
||||
"720p",
|
||||
"1080p"
|
||||
],
|
||||
"categories": [
|
||||
"devices",
|
||||
"multimedia"
|
||||
]
|
||||
}
|
||||
17
icons/hd.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M10 12H6" />
|
||||
<path d="M10 15V9" />
|
||||
<path d="M14 14.5a.5.5 0 0 0 .5.5h1a2.5 2.5 0 0 0 2.5-2.5v-1A2.5 2.5 0 0 0 15.5 9h-1a.5.5 0 0 0-.5.5z" />
|
||||
<path d="M6 15V9" />
|
||||
<rect x="2" y="5" width="20" height="14" rx="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 440 B |
22
icons/layers-plus.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"juanisidoro",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"tags": [
|
||||
"stack",
|
||||
"layers",
|
||||
"add",
|
||||
"new",
|
||||
"increase",
|
||||
"create",
|
||||
"positive",
|
||||
"copy",
|
||||
"upgrade"
|
||||
],
|
||||
"categories": [
|
||||
"design",
|
||||
"layout"
|
||||
]
|
||||
}
|
||||
17
icons/layers-plus.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 .83.18 2 2 0 0 0 .83-.18l8.58-3.9a1 1 0 0 0 0-1.831z" />
|
||||
<path d="M16 17h6" />
|
||||
<path d="M19 14v6" />
|
||||
<path d="M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 .825.178" />
|
||||
<path d="M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l2.116-.962" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 539 B |
@@ -9,13 +9,15 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M6 19v-3" />
|
||||
<path d="M10 19v-3" />
|
||||
<path d="M14 19v-3" />
|
||||
<path d="M18 19v-3" />
|
||||
<path d="M8 11V9" />
|
||||
<path d="M16 11V9" />
|
||||
<path d="M12 11V9" />
|
||||
<path d="M2 15h20" />
|
||||
<path d="M2 7a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v1.1a2 2 0 0 0 0 3.837V17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-5.1a2 2 0 0 0 0-3.837Z" />
|
||||
<path d="M12 12v-2" />
|
||||
<path d="M12 18v-2" />
|
||||
<path d="M16 12v-2" />
|
||||
<path d="M16 18v-2" />
|
||||
<path d="M2 11h1.5" />
|
||||
<path d="M20 18v-2" />
|
||||
<path d="M20.5 11H22" />
|
||||
<path d="M4 18v-2" />
|
||||
<path d="M8 12v-2" />
|
||||
<path d="M8 18v-2" />
|
||||
<rect x="2" y="6" width="20" height="10" rx="2" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 532 B After Width: | Height: | Size: 510 B |
@@ -9,15 +9,14 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M10 12h4" />
|
||||
<path d="M10 17h4" />
|
||||
<path d="M10 7h4" />
|
||||
<path d="M18 12h2" />
|
||||
<path d="M18 16h2" />
|
||||
<path d="M18 20h2" />
|
||||
<path d="M18 4h2" />
|
||||
<path d="M18 8h2" />
|
||||
<path d="M18 18h2" />
|
||||
<path d="M18 6h2" />
|
||||
<path d="M4 12h2" />
|
||||
<path d="M4 16h2" />
|
||||
<path d="M4 20h2" />
|
||||
<path d="M4 4h2" />
|
||||
<path d="M4 8h2" />
|
||||
<path d="M8 2a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2h-1.5c-.276 0-.494.227-.562.495a2 2 0 0 1-3.876 0C9.994 2.227 9.776 2 9.5 2z" />
|
||||
<path d="M4 18h2" />
|
||||
<path d="M4 6h2" />
|
||||
<rect x="6" y="2" width="12" height="20" rx="2" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 598 B After Width: | Height: | Size: 471 B |
@@ -9,8 +9,8 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m19 11-8-8-8.6 8.6a2 2 0 0 0 0 2.8l5.2 5.2c.8.8 2 .8 2.8 0L19 11Z" />
|
||||
<path d="m5 2 5 5" />
|
||||
<path d="M2 13h15" />
|
||||
<path d="M22 20a2 2 0 1 1-4 0c0-1.6 1.7-2.4 2-4 .3 1.6 2 2.4 2 4Z" />
|
||||
<path d="M19 12H2" />
|
||||
<path d="M21.145 18.38A3.34 3.34 0 0 1 20 16.5a3.3 3.3 0 0 1-1.145 1.88c-.575.46-.855 1.02-.855 1.595A2 2 0 0 0 20 22a2 2 0 0 0 2-2.025c0-.58-.285-1.13-.855-1.595" />
|
||||
<path d="m6 2 5 5" />
|
||||
<path d="m8.5 4.5 2.148-2.148a1.205 1.205 0 0 1 1.704 0l7.296 7.296a1.205 1.205 0 0 1 0 1.704l-7.592 7.592a3.615 3.615 0 0 1-5.112 0l-3.888-3.888a3.615 3.615 0 0 1 0-5.112L5.67 7.33" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 409 B After Width: | Height: | Size: 613 B |
@@ -10,7 +10,11 @@
|
||||
"energy",
|
||||
"electronics",
|
||||
"socket",
|
||||
"outlet"
|
||||
"outlet",
|
||||
"power",
|
||||
"voltage",
|
||||
"current",
|
||||
"charger"
|
||||
],
|
||||
"categories": [
|
||||
"devices",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M12 22v-5" />
|
||||
<path d="M9 8V2" />
|
||||
<path d="M15 8V2" />
|
||||
<path d="M18 8v5a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V8Z" />
|
||||
<path d="M17 8a1 1 0 0 1 1 1v4a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1z" />
|
||||
<path d="M9 8V2" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 335 B After Width: | Height: | Size: 363 B |
22
icons/scooter.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"Ahmed-Dghaies",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"tags": [
|
||||
"vehicle",
|
||||
"drive",
|
||||
"trip",
|
||||
"journey",
|
||||
"transport",
|
||||
"electric",
|
||||
"ride",
|
||||
"urban",
|
||||
"commute",
|
||||
"speed"
|
||||
],
|
||||
"categories": [
|
||||
"transportation"
|
||||
]
|
||||
}
|
||||
16
icons/scooter.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M21 4h-3.5l2 11.05" />
|
||||
<path d="M6.95 17h5.142c.523 0 .95-.406 1.063-.916a6.5 6.5 0 0 1 5.345-5.009" />
|
||||
<circle cx="19.5" cy="17.5" r="2.5" />
|
||||
<circle cx="4.5" cy="17.5" r="2.5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 405 B |
25
icons/search-alert.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"colebemis",
|
||||
"ericfennis",
|
||||
"jguddas",
|
||||
"Veatec22"
|
||||
],
|
||||
"tags": [
|
||||
"find",
|
||||
"scan",
|
||||
"magnifier",
|
||||
"magnifying glass",
|
||||
"stop",
|
||||
"warning",
|
||||
"alert",
|
||||
"error",
|
||||
"anomaly",
|
||||
"lens"
|
||||
],
|
||||
"categories": [
|
||||
"text",
|
||||
"social"
|
||||
]
|
||||
}
|
||||
16
icons/search-alert.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<path d="m21 21-4.3-4.3" />
|
||||
<path d="M11 7v4" />
|
||||
<path d="M11 15h.01" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 321 B |
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"danielbayley"
|
||||
"danielbayley",
|
||||
"eden881"
|
||||
],
|
||||
"tags": [
|
||||
"cut",
|
||||
|
||||
@@ -9,12 +9,18 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M4 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2" />
|
||||
<path d="M10 22H8" />
|
||||
<path d="M16 22h-2" />
|
||||
<circle cx="8" cy="8" r="2" />
|
||||
<path d="M9.414 9.414 12 12" />
|
||||
<path d="M14.8 14.8 18 18" />
|
||||
<circle cx="8" cy="16" r="2" />
|
||||
<path d="m18 6-8.586 8.586" />
|
||||
<line x1="5" y1="3" x2="19" y2="3" />
|
||||
<line x1="3" y1="5" x2="3" y2="19" />
|
||||
<line x1="21" y1="5" x2="21" y2="19" />
|
||||
<line x1="9" y1="21" x2="10" y2="21" />
|
||||
<line x1="14" y1="21" x2="15" y2="21" />
|
||||
<path d="M 3 5 A2 2 0 0 1 5 3" />
|
||||
<path d="M 19 3 A2 2 0 0 1 21 5" />
|
||||
<path d="M 5 21 A2 2 0 0 1 3 19" />
|
||||
<path d="M 21 19 A2 2 0 0 1 19 21" />
|
||||
<circle cx="8.5" cy="8.5" r="1.5" />
|
||||
<line x1="9.56066" y1="9.56066" x2="12" y2="12" />
|
||||
<line x1="17" y1="17" x2="14.82" y2="14.82" />
|
||||
<circle cx="8.5" cy="15.5" r="1.5" />
|
||||
<line x1="9.56066" y1="14.43934" x2="17" y2="7" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 508 B After Width: | Height: | Size: 801 B |
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"danielbayley"
|
||||
"danielbayley",
|
||||
"eden881"
|
||||
],
|
||||
"tags": [
|
||||
"cut",
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect width="20" height="20" x="2" y="2" rx="2" />
|
||||
<circle cx="8" cy="8" r="2" />
|
||||
<path d="M9.414 9.414 12 12" />
|
||||
<path d="M14.8 14.8 18 18" />
|
||||
<circle cx="8" cy="16" r="2" />
|
||||
<path d="m18 6-8.586 8.586" />
|
||||
<rect width="18" height="18" x="3" y="3" rx="2" />
|
||||
<circle cx="8.5" cy="8.5" r="1.5" />
|
||||
<line x1="9.56066" y1="9.56066" x2="12" y2="12" />
|
||||
<line x1="17" y1="17" x2="14.82" y2="14.82" />
|
||||
<circle cx="8.5" cy="15.5" r="1.5" />
|
||||
<line x1="9.56066" y1="14.43934" x2="17" y2="7" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 427 B After Width: | Height: | Size: 495 B |
@@ -2,7 +2,8 @@
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"ericfennis",
|
||||
"karsa-mistmere"
|
||||
"karsa-mistmere",
|
||||
"jguddas"
|
||||
],
|
||||
"tags": [
|
||||
"temperature",
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M12 9a4 4 0 0 0-2 7.5" />
|
||||
<path d="M12 3v2" />
|
||||
<path d="m6.6 18.4-1.4 1.4" />
|
||||
<path d="M20 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" />
|
||||
<path d="M4 13H2" />
|
||||
<path d="M6.34 7.34 4.93 5.93" />
|
||||
<path d="M12 2v2" />
|
||||
<path d="M12 8a4 4 0 0 0-1.645 7.647" />
|
||||
<path d="M2 12h2" />
|
||||
<path d="M20 14.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0z" />
|
||||
<path d="m4.93 4.93 1.41 1.41" />
|
||||
<path d="m6.34 17.66-1.41 1.41" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 418 B After Width: | Height: | Size: 426 B |
@@ -9,6 +9,6 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M17 14V2" />
|
||||
<path d="M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z" />
|
||||
<path d="M17 14V2" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 399 B After Width: | Height: | Size: 399 B |
@@ -9,6 +9,6 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M7 10v12" />
|
||||
<path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z" />
|
||||
<path d="M7 10v12" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 400 B After Width: | Height: | Size: 400 B |
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"jguddas"
|
||||
"jguddas",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"tags": [
|
||||
"plane",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
>
|
||||
<path d="M10.5 17h1.227a2 2 0 0 0 1.345-.52L18 12" />
|
||||
<path d="m12 13.5 3.75.5" />
|
||||
<path d="m4.5 8 10.58-5.06a1 1 0 0 1 1.342.488L18.5 8" />
|
||||
<path d="m3.173 8.18 11-5a2 2 0 0 1 2.647.993L18.56 8" />
|
||||
<path d="M6 10V8" />
|
||||
<path d="M6 14v1" />
|
||||
<path d="M6 19v2" />
|
||||
|
||||
|
Before Width: | Height: | Size: 477 B After Width: | Height: | Size: 477 B |
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"jguddas"
|
||||
"jguddas",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"tags": [
|
||||
"trip",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m4.5 8 10.58-5.06a1 1 0 0 1 1.342.488L18.5 8" />
|
||||
<path d="m3.173 8.18 11-5a2 2 0 0 1 2.647.993L18.56 8" />
|
||||
<path d="M6 10V8" />
|
||||
<path d="M6 14v1" />
|
||||
<path d="M6 19v2" />
|
||||
|
||||
|
Before Width: | Height: | Size: 390 B After Width: | Height: | Size: 390 B |
28
icons/van.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"Ahmed-Dghaies",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"tags": [
|
||||
"minivan",
|
||||
"cart",
|
||||
"wagon",
|
||||
"truck",
|
||||
"lorry",
|
||||
"trailer",
|
||||
"camper",
|
||||
"vehicle",
|
||||
"drive",
|
||||
"trip",
|
||||
"journey",
|
||||
"van",
|
||||
"transport",
|
||||
"carriage",
|
||||
"delivery",
|
||||
"travel"
|
||||
],
|
||||
"categories": [
|
||||
"transportation"
|
||||
]
|
||||
}
|
||||
17
icons/van.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M13 6v5a1 1 0 0 0 1 1h6.102a1 1 0 0 1 .712.298l.898.91a1 1 0 0 1 .288.702V17a1 1 0 0 1-1 1h-3" />
|
||||
<path d="M5 18H3a1 1 0 0 1-1-1V8a2 2 0 0 1 2-2h12c1.1 0 2.1.8 2.4 1.8l1.176 4.2" />
|
||||
<path d="M9 18h5" />
|
||||
<circle cx="16" cy="18" r="2" />
|
||||
<circle cx="7" cy="18" r="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 494 B |
20
icons/weight-tilde.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"nathan-de-pachtere"
|
||||
],
|
||||
"tags": [
|
||||
"measure",
|
||||
"scale",
|
||||
"estimate",
|
||||
"load",
|
||||
"balance",
|
||||
"size",
|
||||
"measurement",
|
||||
"quantity",
|
||||
"mass"
|
||||
],
|
||||
"categories": [
|
||||
"math"
|
||||
]
|
||||
}
|
||||
15
icons/weight-tilde.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M6.5 8a2 2 0 0 0-1.906 1.46L2.1 18.5A2 2 0 0 0 4 21h16a2 2 0 0 0 1.925-2.54L19.4 9.5A2 2 0 0 0 17.48 8z" />
|
||||
<path d="M7.999 15a2.5 2.5 0 0 1 4 0 2.5 2.5 0 0 0 4 0" />
|
||||
<circle cx="12" cy="5" r="3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 422 B |
@@ -7,8 +7,8 @@ const filenamesToAjvOption = (filenames) => filenames.map((filename) => `-d ${fi
|
||||
/** @satisfies {import('lint-staged').Config} */
|
||||
const config = {
|
||||
'icons/*.svg': [
|
||||
'node ./scripts/optimizeStagedSvgs.mjs',
|
||||
'node ./scripts/generateNextJSAliases.mjs',
|
||||
'node ./scripts/optimizeStagedSvgs.mts',
|
||||
'node ./scripts/generateNextJSAliases.mts',
|
||||
],
|
||||
'icons/*.json': (filenames) => [
|
||||
`ajv --spec=draft2020 -s icon.schema.json ${filenamesToAjvOption(filenames)}`,
|
||||
|
||||
@@ -73,9 +73,9 @@
|
||||
"zod": "^3.25.67"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=23.0.0"
|
||||
"node": ">=24.11.1"
|
||||
},
|
||||
"packageManager": "pnpm@10.23.0+sha512.21c4e5698002ade97e4efe8b8b4a89a8de3c85a37919f957e7a0f30f38fbc5bbdd05980ffe29179b2fb6e6e691242e098d945d1601772cad0fef5fb6411e2a4b",
|
||||
"packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a",
|
||||
"pnpm": {
|
||||
"packageExtensions": {
|
||||
"vue-template-compiler": {
|
||||
|
||||
9764
pnpm-lock.yaml
generated
@@ -52,7 +52,8 @@ const getImageTagsByFiles = (
|
||||
const svgFiles = await readSvgDirectory(ICONS_DIR);
|
||||
const svgFilePaths = svgFiles.map((file) => `icons/${file}`);
|
||||
|
||||
const iconsFilteredByName = (search: string) => svgFilePaths.filter((file) => file.includes(search));
|
||||
const iconsFilteredByName = (search: string) =>
|
||||
svgFilePaths.filter((file) => file.includes(search));
|
||||
|
||||
const cohesionRandomImageTags = getImageTagsByFiles(
|
||||
shuffleArray(svgFilePaths).slice(0, changedFiles.length),
|
||||
@@ -120,6 +121,30 @@ const changeFilesDiffImageTags = getImageTagsByFiles(
|
||||
400,
|
||||
).join(' ');
|
||||
|
||||
const changeFilesSymmetryImageTagsRotate180 = getImageTagsByFiles(
|
||||
changedFiles,
|
||||
() => `${BASE_URL}/symmetry/rotate-180`,
|
||||
400,
|
||||
).join(' ');
|
||||
|
||||
const changeFilesSymmetryImageTagsFlipHorizontal = getImageTagsByFiles(
|
||||
changedFiles,
|
||||
() => `${BASE_URL}/symmetry/flip-horizontal`,
|
||||
400,
|
||||
).join(' ');
|
||||
|
||||
const changeFilesSymmetryImageTagsFlipVertical = getImageTagsByFiles(
|
||||
changedFiles,
|
||||
() => `${BASE_URL}/symmetry/flip-vertical`,
|
||||
400,
|
||||
).join(' ');
|
||||
|
||||
const changeFilesSymmetryImageTagsFlipSlash = getImageTagsByFiles(
|
||||
changedFiles,
|
||||
() => `${BASE_URL}/symmetry/flip-slash`,
|
||||
400,
|
||||
).join(' ');
|
||||
|
||||
const readyToUseCode = changedFiles
|
||||
.map((changedFile) => {
|
||||
const svgContent = fs.readFileSync(path.join(process.cwd(), changedFile), 'utf-8');
|
||||
@@ -179,6 +204,17 @@ ${changeFilesXRayImageTags}
|
||||
${changeFilesDiffImageTags}
|
||||
</details>
|
||||
<details>
|
||||
<summary>Icon Symmetry</summary>
|
||||
<h4>Flip Horizontal</h4>
|
||||
${changeFilesSymmetryImageTagsFlipHorizontal}
|
||||
<h4>Flip Vertical</h4>
|
||||
${changeFilesSymmetryImageTagsFlipVertical}
|
||||
<h4>Flip Diagonal</h4>
|
||||
${changeFilesSymmetryImageTagsFlipSlash}
|
||||
<h4>Rotate 180°</h4>
|
||||
${changeFilesSymmetryImageTagsRotate180}
|
||||
</details>
|
||||
<details>
|
||||
<summary>Icons as code</summary>
|
||||
|
||||
Works for: \`lucide-react\`, \`lucide-react-native\`, \`lucide-preact\`, \`lucide-vue-next\`
|
||||
|
||||