Compare commits

..
Author SHA1 Message Date
Eric Fennis f9a909f1c2 try to speed up build times 2024-01-13 11:46:24 +01:00
1946 changed files with 8789 additions and 19707 deletions
-15
View File
@@ -1,15 +0,0 @@
# Custom words
fullscreen
gamepad
gantt
kanban
pilcrow
squircle
strikethrough
touchpad
ungroup
# Brands
codepen
codesandbox
dribbble
+2 -1
View File
@@ -6,5 +6,6 @@ tests
node_modules
.eslintrc.js
docs/images
docs/**/examples/
docs/guide/basics/examples
docs/guide/advanced/examples
packages/lucide-react/dynamicIconImports.js
+15 -9
View File
@@ -7,12 +7,20 @@ module.exports = {
node: true,
},
extends: ['airbnb-base', 'prettier'],
plugins: ['import', '@html-eslint'],
plugins: ['import', 'prettier', '@html-eslint'],
rules: {
'no-console': 'off',
'no-param-reassign': 'off',
'no-shadow': 'off',
'no-use-before-define': 'off',
'prettier/prettier': [
'error',
{
singleQuote: true,
trailingComma: 'all',
printWidth: 100
},
],
'import/no-extraneous-dependencies': [
'error',
{ devDependencies: ['**/*.test.js', '**/*.spec.js', './scripts/**'] },
@@ -38,19 +46,17 @@ module.exports = {
files: ['./icons/*.svg'],
parser: '@html-eslint/parser',
rules: {
'prettier/prettier': 'off',
'@html-eslint/require-doctype': 'off',
'@html-eslint/no-duplicate-attrs': 'error',
'@html-eslint/no-inline-styles': 'error',
'@html-eslint/require-attrs': [
'error',
...Object.entries(DEFAULT_ATTRS).map(([attr, value]) => ({
tag: 'svg',
attr,
value: String(value),
})),
'error',
...Object.entries(DEFAULT_ATTRS)
.map(([attr, value]) => ({ tag: 'svg', attr, value: String(value) }))
],
'@html-eslint/indent': ['error', 2],
'@html-eslint/no-multiple-empty-lines': ['error', { max: 0 }],
"@html-eslint/no-multiple-empty-lines": ["error", { "max": 0 }],
'@html-eslint/no-extra-spacing-attrs': [
'error',
{
@@ -67,7 +73,7 @@ module.exports = {
'@html-eslint/element-newline': 'error',
'@html-eslint/no-trailing-spaces': 'error',
'@html-eslint/quotes': 'error',
},
}
},
],
};
-10
View File
@@ -35,16 +35,6 @@ body:
placeholder: e.g. 0.289.1
validations:
required: true
- type: checkboxes
id: can-reproduce-in-latest-version
attributes:
label: Can you reproduce this in the latest version?
description: i.e. after running `npm install lucide-react@latest`
options:
- label: 'Yes'
- label: 'No'
validations:
required: false
- type: checkboxes
id: browsers
attributes:
+4 -4
View File
@@ -1,5 +1,5 @@
name: 'Build and Test'
description: 'Builds and test a package'
name: "Build and Test"
description: "Builds and test a package"
inputs:
name:
@@ -7,7 +7,7 @@ inputs:
required: true
runs:
using: 'composite'
using: "composite"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -18,7 +18,7 @@ runs:
name: Install pnpm
id: pnpm-install
with:
version: 8
version: 7
run_install: false
- name: Get pnpm store directory
+4 -4
View File
@@ -1,5 +1,5 @@
name: 'Check icons'
description: 'Cross-checks icon and category references in JSON descriptors'
name: "Check icons"
description: "Cross-checks icon and category references in JSON descriptors"
inputs:
name:
@@ -7,7 +7,7 @@ inputs:
required: true
runs:
using: 'composite'
using: "composite"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -18,7 +18,7 @@ runs:
name: Install pnpm
id: pnpm-install
with:
version: 8
version: 7
run_install: false
- name: Get pnpm store directory
+48 -48
View File
@@ -1,92 +1,92 @@
# For changed dependencies
📦 dependencies:
- changed-files:
- any-glob-to-any-file:
- pnpm-lock.yaml
- changed-files:
- any-glob-to-any-file:
- pnpm-lock.yaml
# For changes in documentation
📖 documentation:
- changed-files:
- any-glob-to-any-file:
- docs/*.md
- docs/**/*.md
- changed-files:
- any-glob-to-any-file:
- docs/*.md
- docs/**/*.md
# For changes in the site, but not markdown files
🌍 site:
- changed-files:
- any-glob-to-any-file:
- 'docs/**'
- changed-files:
- any-glob-to-any-file:
- 'docs/**'
# For changes in the metadata
🫧 metadata:
- changed-files:
- any-glob-to-any-file:
- 'icons/*.json'
- categories/*
- changed-files:
- any-glob-to-any-file:
- 'icons/*.json'
- categories/*
# For changes or added icons
🎨 icon:
- changed-files:
- any-glob-to-any-file:
- 'icons/*.svg'
- changed-files:
- any-glob-to-any-file:
- 'icons/*.svg'
# For changes in the lucide package
🧳 lucide package:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide/*'
# For changes in the lucide React package
⚛️ react package:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-react/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-react/*'
# For changes in the lucide React Native package
⚛️ react native package:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-react-native/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-react-native/*'
# For changes in the lucide vue packages
💎 vue package:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-vue/*'
- 'packages/lucide-vue-next/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-vue/*'
- 'packages/lucide-vue-next/*'
# For changes in the lucide angular package
🅰️ angular package:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-angular/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-angular/*'
# For changes in the lucide preact package
⚛️ preact package:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-preact/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-preact/*'
# For changes in the lucide svelte package
🧣 svelte package:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-svelte/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-svelte/*'
# For changes in the lucide solid package
🪝 solid package:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-solid/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-solid/*'
# For changes in the lucide static package
🪨 static package:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-static/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-static/*'
# For changes in the lucide flutter package
🏹 flutter package:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-flutter/*'
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-flutter/*'
+1 -1
View File
@@ -47,7 +47,7 @@
- [ ] I've made sure that the icons look sharp on low DPI displays.
- [ ] I've made sure that the icons look consistent with the icon set in size, optical volume and density.
- [ ] I've made sure that the icons are visually centered.
- [ ] I've correctly optimized all icons to three points of precision.
- [ ] I've correctly optimized all icons to two points of precision.
## Before Submitting <!-- For every PR! -->
<!-- All of these requirements must be fulfilled. -->
+1 -4
View File
@@ -1,13 +1,11 @@
name: Close stale issues and PR
on:
schedule:
- cron: '45 1 * * *'
- cron: "45 1 * * *"
jobs:
stale:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/stale@v9
with:
@@ -16,5 +14,4 @@ jobs:
close-pr-message: This PR was closed because it has been stalled for 5 days with no activity.
close-pr-label: 🧶 stale
days-before-stale: 30
days-before-issue-stale: -1
days-before-close: -1
+3 -3
View File
@@ -1,6 +1,6 @@
name: 'Pull Request Labeler'
name: "Pull Request Labeler"
on:
- pull_request_target
- pull_request_target
jobs:
triage:
@@ -9,4 +9,4 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5
- uses: actions/labeler@v5
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
- name: Create font in ./lucide-font
run: pnpm build:font
- name: 'Upload to Artifacts'
- name: "Upload to Artifacts"
uses: actions/upload-artifact@v3
with:
name: lucide-font
+16 -14
View File
@@ -41,18 +41,17 @@ jobs:
strategy:
fail-fast: false
matrix:
package:
[
'lucide',
'lucide-react',
'lucide-react-native',
'lucide-vue',
'lucide-vue-next',
'lucide-angular',
'lucide-preact',
'lucide-solid',
'lucide-svelte',
]
package: [
'lucide',
'lucide-react',
'lucide-react-native',
'lucide-vue',
'lucide-vue-next',
'lucide-angular',
'lucide-preact',
'lucide-solid',
'lucide-svelte',
]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
@@ -137,7 +136,7 @@ jobs:
- name: Create font in ./lucide-font
run: pnpm build:font
- name: 'Upload to Artifacts'
- name: "Upload to Artifacts"
uses: actions/upload-artifact@v3
with:
name: lucide-font
@@ -146,7 +145,10 @@ jobs:
post-release:
if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest
needs: [pre-release, lucide-font]
needs: [
pre-release,
lucide-font,
]
steps:
- uses: actions/checkout@v4
-19
View File
@@ -1,19 +0,0 @@
pnpm-lock.yaml
# docs examples
docs/**/examples/
# lucide-angular
packages/lucide-angular/.angular/cache
# lucide-static
packages/lucide-static/icons
packages/lucide-static/lib
packages/lucide-static/sprite.svg
packages/lucide-static/tags.json
packages/lucide-static/icon-nodes.json
packages/lucide-static/font
# lucide-svelte
packages/lucide-svelte/src/icons/*.svelte
packages/lucide-svelte/.svelte-kit
+1 -1
View File
@@ -12,4 +12,4 @@
"webRoot": "${workspaceFolder}"
}
]
}
}
+9 -2
View File
@@ -1,6 +1,13 @@
{
"cSpell.words": ["devs", "preact", "Preact"],
"cSpell.words": [
"devs",
"preact",
"Preact"
],
"eslint.enable": true,
"eslint.validate": ["javascript", "svg"],
"eslint.validate": [
"javascript",
"svg"
],
"svg.preview.background": "transparent"
}
+1 -1
View File
@@ -49,7 +49,7 @@
"circle",
"<circle"
],
"body": "<circle cx=\"${2:12}\" cy=\"${3:$2}\" r=\"${1|10,2,.5\" fill=\"currentColor|}\" />"
"body": "<circle cx=\"${2:12}\" cy=\"${3:$2}\" r=\"${1|10,2,.5|}\" />"
},
"Ellipse": {
"scope": "xml",
+3 -3
View File
@@ -72,8 +72,8 @@ pnpm install # Install dependencies, including the workspace packages
To distribute different packages we use PNPM workspaces. Before you start make sure you are familiar with this concept. The concept of working in workspaces is created by Yarn, they have a well written introduction: [yarn workspaces](https://classic.yarnpkg.com/lang/enhttps://lucide.dev/docs/workspaces).
The configured directory for workspaces is the [packages](https://github.com/lucide-icons/lucide/tree/main/packages) directory, located in the root directory. There you will find all the current packages from lucide.
There are more workspaces defined, see [`pnpm-workspace.yaml`](https://github.com/lucide-icons/lucide/blob/main/pnpm-workspace.yaml).
The configured directory for workspaces is the [packages](./packages) directory, located in the root directory. There you will find all the current packages from lucide.
There are more workspaces defined, see [`pnpm-workspace.yaml`](./pnpm-workspace.yaml).
> Note: One package is not managed by pnpm: **lucide-flutter**, this package is written in Dart and used pub for publishing.
@@ -153,7 +153,7 @@ lucide
### Docs
The lucide.dev website is using [vitepress](https://vitepress.dev/) to generate the static website. The markdown files are located in the docs directory.
The lucide.dev website is using [vitepress](https://vitepress.dev/) to generate the static website. The markdown files are located in the docs directory.
### Guides
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Accessibility",
"icon": "accessibility"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Accounts & access",
"icon": "user"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Animals",
"icon": "dog"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Arrows",
"icon": "arrow-left-right"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Brands",
"icon": "facebook"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Buildings",
"icon": "building"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Charts",
"icon": "pie-chart"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Communication",
"icon": "message-circle"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Connectivity",
"icon": "wifi"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Currency",
"icon": "dollar-sign"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Cursors",
"icon": "mouse-pointer-2"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Design",
"icon": "palette"
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"$schema": "../category.schema.json",
"title": "Coding & development",
"icon": "code-xml"
"icon": "code-2"
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Devices",
"icon": "smartphone"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Emoji",
"icon": "smile"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "File icons",
"icon": "panels-top-left"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Food & beverage",
"icon": "coffee"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Furniture",
"icon": "rocking-chair"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Gaming",
"icon": "gamepad-2"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Home",
"icon": "home"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Layout",
"icon": "panels-top-left"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Mail",
"icon": "mail"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Maps",
"icon": "map"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Maths",
"icon": "divide"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Medical",
"icon": "heart"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Money",
"icon": "piggy-bank"
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
{
"$schema": "../category.schema.json",
"title": "Multimedia",
"icon": "circle-play"
}
"icon": "play-circle"
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Nature",
"icon": "sprout"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Navigation",
"icon": "compass"
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
{
"$schema": "../category.schema.json",
"title": "Notifications",
"icon": "triangle-alert"
}
"icon": "alert-triangle"
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "People",
"icon": "person-standing"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Photography",
"icon": "camera"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Science",
"icon": "flask-conical"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Seasons",
"icon": "leaf"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Security",
"icon": "shield"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Shapes",
"icon": "triangle"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Shopping",
"icon": "shopping-bag"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Social",
"icon": "thumbs-up"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Sports",
"icon": "type"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Sustainability",
"icon": "recycle"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Time & calendar",
"icon": "calendar"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Tools",
"icon": "hammer"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Transportation",
"icon": "train-front"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Travel",
"icon": "backpack"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
"$schema": "../category.schema.json",
"title": "Weather",
"icon": "cloud-sun"
}
}
-10
View File
@@ -1,10 +0,0 @@
{
"dictionaries": ["en-us", "custom-words"],
"dictionaryDefinitions": [
{
"name": "custom-words",
"path": "./.cspell/custom-words.txt",
"addWords": true
}
]
}
+7 -7
View File
@@ -1,11 +1,11 @@
import { eventHandler, setResponseHeader } from 'h3';
import iconMetaData from '../../data/iconMetaData';
import { eventHandler, setResponseHeader } from 'h3'
import iconMetaData from '../../data/iconMetaData'
export default eventHandler((event) => {
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
return Object.fromEntries(
Object.entries(iconMetaData).map(([name, { categories }]) => [name, categories]),
);
});
Object.entries(iconMetaData).map(([name, { categories }]) => [ name, categories ])
)
})
+2 -2
View File
@@ -37,13 +37,13 @@ export default eventHandler((event) => {
backdropString,
src,
color: name in iconNodes ? 'red' : '#777',
}),
})
);
}
const svg = Buffer.from(
// We can't use jsx here, is not supported here by nitro.
renderToString(createElement(SvgPreview, { src, showGrid: true }, children)),
renderToString(createElement(SvgPreview, { src, showGrid: true }, children))
).toString('utf8');
defaultContentType(event, 'image/svg+xml');
@@ -28,7 +28,7 @@ export default eventHandler(async (event) => {
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
`,
`
);
const resvg = new Resvg(svg, { background: '#000' });
@@ -1,12 +1,12 @@
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
import { renderToString } from 'react-dom/server';
import { createElement } from 'react';
import { eventHandler, setResponseHeader, defaultContentType } from 'h3'
import { renderToString } from 'react-dom/server'
import { createElement } from 'react'
import SvgPreview from '../../../lib/SvgPreview/index.tsx';
import createLucideIcon, { IconNode } from 'lucide-react/src/createLucideIcon';
import createLucideIcon, { IconNode } from 'lucide-react/src/createLucideIcon'
import { parseSync } from 'svgson';
export default eventHandler((event) => {
const { params } = event.context;
const { params } = event.context
const [strokeWidth, svgData] = params.data.split('/');
const data = svgData.slice(0, -4);
@@ -16,8 +16,8 @@ export default eventHandler((event) => {
const Icon = createLucideIcon(
'icon',
parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`).children.map(
({ name, attributes }) => [name, attributes],
) as IconNode,
({ name, attributes }) => [name, attributes]
) as IconNode
);
const svg = Buffer.from(
@@ -33,12 +33,12 @@ export default eventHandler((event) => {
@media screen and (prefers-color-scheme: dark) {
svg { stroke: #fff; fill: transparent !important; }
}
</style>`,
),
</style>`
)
).toString('utf8');
defaultContentType(event, 'image/svg+xml');
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
defaultContentType(event, 'image/svg+xml')
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000')
return svg;
});
return svg
})
+16 -16
View File
@@ -1,30 +1,30 @@
import { eventHandler, getQuery, setResponseHeader } from 'h3';
import iconNodes from '../../data/iconNodes';
import { IconNodeWithKeys } from '../../theme/types';
import { eventHandler, getQuery, setResponseHeader } from 'h3'
import iconNodes from '../../data/iconNodes'
import { IconNodeWithKeys } from '../../theme/types'
export default eventHandler((event) => {
const query = getQuery(event);
const query = getQuery(event)
const withUniqueKeys = query.withUniqueKeys === 'true';
const withUniqueKeys = query.withUniqueKeys === 'true'
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
if (withUniqueKeys) {
return iconNodes;
return iconNodes
}
return Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
if (withUniqueKeys) {
return [name, iconNode];
return [name, iconNode]
}
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
return [name, attrs];
});
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs}]) => {
return [name, attrs]
})
acc[name] = newIconNode;
acc[name] = newIconNode
return acc;
}, {});
});
return acc
}, {})
})
+22 -21
View File
@@ -1,29 +1,29 @@
import { eventHandler, getQuery, setResponseHeader, createError } from 'h3';
import iconNodes from '../../data/iconNodes';
import createLucideIcon from 'lucide-react/src/createLucideIcon';
import { renderToString } from 'react-dom/server';
import { createElement } from 'react';
import { eventHandler, getQuery, setResponseHeader, createError } from 'h3'
import iconNodes from '../../data/iconNodes'
import createLucideIcon from 'lucide-react/src/createLucideIcon'
import { renderToString } from 'react-dom/server'
import { createElement } from 'react'
export default eventHandler((event) => {
const { params } = event.context;
const { params } = event.context
const iconNode = iconNodes[params.iconName];
const iconNode = iconNodes[params.iconName]
if (iconNode == null) {
const error = createError({
statusCode: 404,
message: `Icon "${params.iconName}" not found`,
});
})
return sendError(event, error);
return sendError(event, error)
}
const width = getQuery(event).width || undefined;
const height = getQuery(event).height || undefined;
const color = getQuery(event).color || undefined;
const strokeWidth = getQuery(event).strokeWidth || undefined;
const width = getQuery(event).width || undefined
const height = getQuery(event).height || undefined
const color = getQuery(event).color || undefined
const strokeWidth = getQuery(event).strokeWidth || undefined
const LucideIcon = createLucideIcon(params.iconName, iconNode);
const LucideIcon = createLucideIcon(params.iconName, iconNode)
const svg = Buffer.from(
renderToString(
@@ -32,13 +32,14 @@ export default eventHandler((event) => {
height,
color: color ? `#${color}` : undefined,
strokeWidth,
}),
),
}
))
).toString('utf8');
defaultContentType(event, 'image/svg+xml');
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
defaultContentType(event, 'image/svg+xml')
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000')
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
return svg;
});
return svg
})
+8 -6
View File
@@ -1,9 +1,11 @@
import { eventHandler, setResponseHeader } from 'h3';
import iconMetaData from '../../data/iconMetaData';
import { eventHandler, setResponseHeader } from 'h3'
import iconMetaData from '../../data/iconMetaData'
export default eventHandler((event) => {
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
return Object.fromEntries(Object.entries(iconMetaData).map(([name, { tags }]) => [name, tags]));
});
return Object.fromEntries(
Object.entries(iconMetaData).map(([name, { tags }]) => [ name, tags ])
)
})
+2 -2
View File
@@ -1,3 +1,3 @@
export default eventHandler(() => {
return { nitro: 'Is Awesome! asda' };
});
return { nitro: 'Is Awesome! asda' }
})
+81 -126
View File
@@ -1,10 +1,10 @@
import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vitepress';
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vitepress'
import sidebar from './sidebar';
const title = 'Lucide';
const socialTitle = 'Lucide Icons';
const description = 'Beautiful & consistent icon toolkit made by the community.';
const title = "Lucide";
const socialTitle = "Lucide Icons";
const description = "Beautiful & consistent icon toolkit made by the community."
// https://vitepress.dev/reference/site-config
export default defineConfig({
@@ -19,131 +19,86 @@ export default defineConfig({
{
find: /^.*\/VPIconAlignLeft\.vue$/,
replacement: fileURLToPath(
new URL('./theme/components/overrides/VPIconAlignLeft.vue', import.meta.url),
),
new URL('./theme/components/overrides/VPIconAlignLeft.vue', import.meta.url)
)
},
{
find: /^.*\/VPFooter\.vue$/,
replacement: fileURLToPath(
new URL('./theme/components/overrides/VPFooter.vue', import.meta.url),
),
},
],
new URL('./theme/components/overrides/VPFooter.vue', import.meta.url)
)
}
]
},
},
head: [
[
'script',
{
src: 'https://analytics.lucide.dev/js/script.js',
'data-domain': 'lucide.dev',
defer: '',
},
],
[
'meta',
{
property: 'og:locale',
content: 'en_US',
},
],
[
'meta',
{
property: 'og:type',
content: 'website',
},
],
[
'meta',
{
property: 'og:site_name',
content: title,
},
],
[
'meta',
{
property: 'og:title',
content: socialTitle,
},
],
[
'meta',
{
property: 'og:description',
content: description,
},
],
[
'meta',
{
property: 'og:url',
content: 'https://lucide.dev',
},
],
[
'meta',
{
property: 'og:image',
content: 'https://lucide.dev/og.png',
},
],
[
'meta',
{
property: 'og:image:width',
content: '1200',
},
],
[
'meta',
{
property: 'og:image:height',
content: '630',
},
],
[
'meta',
{
property: 'og:image:type',
content: 'image/png',
},
],
[
'meta',
{
property: 'twitter:card',
content: 'summary_large_image',
},
],
[
'meta',
{
property: 'twitter:title',
content: socialTitle,
},
],
[
'meta',
{
property: 'twitter:description',
content: description,
},
],
[
'meta',
{
property: 'twitter:image',
content: 'https://lucide.dev/og.png',
},
],
[ 'script', {
src: 'https://analytics.lucide.dev/js/script.js',
'data-domain': 'lucide.dev',
defer: ''
}],
[ 'meta', {
property:"og:locale",
content:"en_US"
}],
[ 'meta', {
property:"og:type",
content:"website"
}],
[ 'meta', {
property:"og:site_name",
content: title,
}],
[ 'meta', {
property:"og:title",
content: socialTitle,
}],
[ 'meta', {
property:"og:description",
content: description
}],
[ 'meta', {
property:"og:url",
content:"https://lucide.dev"
}],
[ 'meta', {
property:"og:image",
content: "https://lucide.dev/og.png"
}],
[ 'meta', {
property:"og:image:width",
content:"1200"
}],
[ 'meta', {
property:"og:image:height",
content:"630"
}],
[ 'meta', {
property:"og:image:type",
content:"image/png"
}],
[ 'meta', {
property:"twitter:card",
content:"summary_large_image"
}],
[ 'meta', {
property:"twitter:title",
content: socialTitle,
}],
[ 'meta', {
property:"twitter:description",
content: description
}],
[ 'meta', {
property:"twitter:image",
content:"https://lucide.dev/og.png"
}],
],
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
logo: {
light: '/logo.light.svg',
dark: '/logo.dark.svg',
dark: '/logo.dark.svg'
},
nav: [
{ text: 'Icons', link: '/icons/' },
@@ -155,21 +110,21 @@ export default defineConfig({
sidebar,
socialLinks: [
{ icon: 'github', link: 'https://github.com/lucide-icons/lucide' },
{ icon: 'discord', link: 'https://discord.gg/EH6nSts' },
{ icon: 'discord', link: 'https://discord.gg/EH6nSts' }
],
footer: {
message: 'Released under the ISC License.',
copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`,
copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`
},
editLink: {
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path',
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path'
},
carbonAds: {
code: 'CWYIC53U',
placement: 'lucidedev',
},
placement: 'lucidedev'
}
},
sitemap: {
hostname: 'https://lucide.dev/',
},
});
hostname: 'https://lucide.dev/'
}
})
+21 -105
View File
@@ -3,171 +3,87 @@
"order": 0,
"icon": "js",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/lucide",
"href": "https://www.npmjs.com/package/lucide"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide",
"href": "https://www.npmjs.com/package/lucide"
}
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide", "href": "https://www.npmjs.com/package/lucide" },
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide", "href": "https://www.npmjs.com/package/lucide" }
]
},
"lucide-react": {
"order": 1,
"icon": "react",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-react",
"href": "https://www.npmjs.com/package/lucide-react"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-react",
"href": "https://www.npmjs.com/package/lucide-react"
}
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-react", "href": "https://www.npmjs.com/package/lucide-react" },
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-react", "href": "https://www.npmjs.com/package/lucide-react" }
]
},
"lucide-vue": {
"order": 2,
"icon": "vue",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-vue",
"href": "https://www.npmjs.com/package/lucide-vue"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-vue",
"href": "https://www.npmjs.com/package/lucide-vue"
}
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-vue", "href": "https://www.npmjs.com/package/lucide-vue" },
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-vue", "href": "https://www.npmjs.com/package/lucide-vue" }
]
},
"lucide-vue-next": {
"order": 3,
"icon": "vue-next",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-vue-next",
"href": "https://www.npmjs.com/package/lucide-vue-next"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-vue-next",
"href": "https://www.npmjs.com/package/lucide-vue-next"
}
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-vue-next", "href": "https://www.npmjs.com/package/lucide-vue-next" },
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-vue-next", "href": "https://www.npmjs.com/package/lucide-vue-next" }
]
},
"lucide-svelte": {
"order": 4,
"icon": "svelte",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-svelte",
"href": "https://www.npmjs.com/package/lucide-svelte"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-svelte",
"href": "https://www.npmjs.com/package/lucide-svelte"
}
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-svelte", "href": "https://www.npmjs.com/package/lucide-svelte" },
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-svelte", "href": "https://www.npmjs.com/package/lucide-svelte" }
]
},
"lucide-solid": {
"order": 4,
"icon": "solid",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-solid",
"href": "https://www.npmjs.com/package/lucide-solid"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-solid",
"href": "https://www.npmjs.com/package/lucide-solid"
}
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-solid", "href": "https://www.npmjs.com/package/lucide-solid" },
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-solid", "href": "https://www.npmjs.com/package/lucide-solid" }
]
},
"lucide-preact": {
"order": 5,
"icon": "preact",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-preact",
"href": "https://www.npmjs.com/package/lucide-preact"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-preact",
"href": "https://www.npmjs.com/package/lucide-preact"
}
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-preact", "href": "https://www.npmjs.com/package/lucide-preact" },
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-preact", "href": "https://www.npmjs.com/package/lucide-preact" }
]
},
"lucide-react-native": {
"order": 6,
"icon": "react-native",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-react-native",
"href": "https://www.npmjs.com/package/lucide-react-native"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-react-native",
"href": "https://www.npmjs.com/package/lucide-react-native"
}
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-react-native", "href": "https://www.npmjs.com/package/lucide-react-native" },
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-react-native", "href": "https://www.npmjs.com/package/lucide-react-native" }
]
},
"lucide-angular": {
"order": 7,
"icon": "angular",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-angular",
"href": "https://www.npmjs.com/package/lucide-angular"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-angular",
"href": "https://www.npmjs.com/package/lucide-angular"
}
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-angular", "href": "https://www.npmjs.com/package/lucide-angular" },
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-angular", "href": "https://www.npmjs.com/package/lucide-angular" }
]
},
"lucide-static": {
"order": 8,
"icon": "svg",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-static",
"href": "https://www.npmjs.com/package/lucide-static"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-static",
"href": "https://www.npmjs.com/package/lucide-static"
}
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-static", "href": "https://www.npmjs.com/package/lucide-static" },
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-static", "href": "https://www.npmjs.com/package/lucide-static" }
]
},
"lucide-flutter": {
"order": 9,
"icon": "flutter",
"shields": [
{
"alt": "flutter",
"src": "https://img.shields.io/pub/v/lucide_icons",
"href": "https://img.shields.io/pub/v/lucide_icons"
}
{ "alt": "flutter", "src": "https://img.shields.io/pub/v/lucide_icons", "href": "https://img.shields.io/pub/v/lucide_icons" }
]
}
}
+11 -52
View File
@@ -17,62 +17,21 @@ const Backdrop = ({ src, color = 'red', backdropString }: BackdropProps): JSX.El
patternUnits="userSpaceOnUse"
patternTransform="rotate(45 50 50)"
>
<line
stroke={color}
strokeWidth={0.1}
y2={1}
/>
<line
stroke={color}
strokeWidth={0.1}
y2={1}
/>
<line stroke={color} strokeWidth={0.1} y2={1} />
<line stroke={color} strokeWidth={0.1} y2={1} />
</pattern>
</defs>
<mask
id="svg-preview-backdrop-mask-outline"
maskUnits="userSpaceOnUse"
>
<g
stroke="#fff"
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
<g
dangerouslySetInnerHTML={{ __html: src }}
strokeWidth={2.05}
/>
<mask id="svg-preview-backdrop-mask-outline" maskUnits="userSpaceOnUse">
<g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} />
<g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} />
</mask>
<mask
id="svg-preview-backdrop-mask-fill"
maskUnits="userSpaceOnUse"
>
<g
stroke="#fff"
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
<g
dangerouslySetInnerHTML={{ __html: src }}
strokeWidth={2.05}
/>
<g
strokeWidth={1.75}
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
<mask id="svg-preview-backdrop-mask-fill" maskUnits="userSpaceOnUse">
<g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} />
<g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} />
<g strokeWidth={1.75} dangerouslySetInnerHTML={{ __html: backdropString }} />
</mask>
<g
strokeWidth={2.25}
stroke="url(#pattern)"
mask={'url(#svg-preview-backdrop-mask-outline)'}
>
<rect
x="0"
y="0"
width="24"
height="24"
fill="url(#pattern)"
opacity={0.5}
stroke="none"
/>
<g strokeWidth={2.25} stroke="url(#pattern)" mask={'url(#svg-preview-backdrop-mask-outline)'}>
<rect x="0" y="0" width="24" height="24" fill="url(#pattern)" opacity={0.5} stroke="none" />
</g>
<rect
x="0"
+38 -155
View File
@@ -10,11 +10,7 @@ const Grid = ({
strokeWidth: number;
radius: number;
} & PathProps<'stroke', 'strokeWidth'>) => (
<g
className="svg-preview-grid-group"
strokeLinecap="butt"
{...props}
>
<g className="svg-preview-grid-group" strokeLinecap="butt" {...props}>
<rect
className="svg-preview-grid-rect"
width={24 - props.strokeWidth}
@@ -25,29 +21,11 @@ const Grid = ({
fill={fill}
/>
<path
strokeDasharray={'0 0.1 ' + '0.1 0.15 '.repeat(11) + '0 0.15'}
strokeWidth={0.1}
d={
props.d ||
new Array(Math.floor(24 - 1))
.fill(null)
.map((_, i) => i)
.filter((i) => i % 3 !== 2)
.flatMap((i) => [
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
])
.join('')
}
/>
<path
d={
props.d ||
new Array(Math.floor(24 - 1))
.fill(null)
.map((_, i) => i)
.filter((i) => i % 3 === 2)
.flatMap((i) => [
.flatMap((_, i) => [
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
])
@@ -66,21 +44,15 @@ const Shadow = ({
paths: Path[];
} & PathProps<'stroke' | 'strokeWidth' | 'strokeOpacity', 'd'>) => {
const groupedPaths = Object.entries(
paths.reduce(
(groups, val) => {
const key = val.c.id;
groups[key] = [...(groups[key] || []), val];
return groups;
},
{} as Record<number, Path[]>,
),
paths.reduce((groups, val) => {
const key = val.c.id;
groups[key] = [...(groups[key] || []), val];
return groups;
}, {} as Record<number, Path[]>)
);
return (
<>
<g
className="svg-preview-shadow-mask-group"
{...props}
>
<g className="svg-preview-shadow-mask-group" {...props}>
{groupedPaths.map(([id, paths]) => (
<mask
id={`svg-preview-shadow-mask-${id}`}
@@ -89,15 +61,7 @@ const Shadow = ({
strokeWidth={props.strokeWidth}
stroke="#000"
>
<rect
x={0}
y={0}
width={24}
height={24}
fill="#fff"
stroke="none"
rx={radius}
/>
<rect x={0} y={0} width={24} height={24} fill="#fff" stroke="none" rx={radius} />
<path
d={paths
.flatMap(({ prev, next }) => [
@@ -110,16 +74,9 @@ const Shadow = ({
</mask>
))}
</g>
<g
className="svg-preview-shadow-group"
{...props}
>
<g className="svg-preview-shadow-group" {...props}>
{paths.map(({ d, c: { id } }, i) => (
<path
key={i}
mask={`url(#svg-preview-shadow-mask-${id})`}
d={d}
/>
<path key={i} mask={`url(#svg-preview-shadow-mask-${id})`} d={d} />
))}
<path
d={paths
@@ -137,16 +94,9 @@ const ColoredPath = ({
paths,
...props
}: { paths: Path[]; colors: string[] } & PathProps<never, 'd' | 'stroke'>) => (
<g
className="svg-preview-colored-path-group"
{...props}
>
<g className="svg-preview-colored-path-group" {...props}>
{paths.map(({ d, c }, i) => (
<path
key={i}
d={d}
stroke={colors[(c.name === 'path' ? i : c.id) % colors.length]}
/>
<path key={i} d={d} stroke={colors[(c.name === 'path' ? i : c.id) % colors.length]} />
))}
</g>
);
@@ -188,15 +138,7 @@ const ControlPath = ({
key={i}
maskUnits="userSpaceOnUse"
>
<rect
x="0"
y="0"
width="24"
height="24"
fill="#fff"
stroke="none"
rx={radius}
/>
<rect x="0" y="0" width="24" height="24" fill="#fff" stroke="none" rx={radius} />
<path d={`M${prev.x} ${prev.y}h.01`} />
<path d={`M${next.x} ${next.y}h.01`} />
</mask>
@@ -204,10 +146,7 @@ const ControlPath = ({
);
})}
</g>
<g
className="svg-preview-control-path-group"
{...props}
>
<g className="svg-preview-control-path-group" {...props}>
{controlPaths.map(({ d, showMarker }, i) => (
<path
key={i}
@@ -216,33 +155,18 @@ const ControlPath = ({
/>
))}
</g>
<g
className="svg-preview-control-path-marker-group"
{...props}
>
<g className="svg-preview-control-path-marker-group" {...props}>
<path
d={controlPaths
.flatMap(({ prev, next, showMarker }) =>
showMarker ? [`M${prev.x} ${prev.y}h.01`, `M${next.x} ${next.y}h.01`] : [],
showMarker ? [`M${prev.x} ${prev.y}h.01`, `M${next.x} ${next.y}h.01`] : []
)
.join('')}
/>
{controlPaths.map(({ d, prev, next, startMarker, endMarker }, i) => (
<React.Fragment key={i}>
{startMarker && (
<circle
cx={prev.x}
cy={prev.y}
r={pointSize / 2}
/>
)}
{endMarker && (
<circle
cx={next.x}
cy={next.y}
r={pointSize / 2}
/>
)}
{startMarker && <circle cx={prev.x} cy={prev.y} r={pointSize / 2} />}
{endMarker && <circle cx={next.x} cy={next.y} r={pointSize / 2} />}
</React.Fragment>
))}
</g>
@@ -258,16 +182,15 @@ const Radii = ({
any
>) => {
return (
<g
className="svg-preview-radii-group"
{...props}
>
<g className="svg-preview-radii-group" {...props}>
{paths.map(
({ c, prev, next, circle }, i) =>
circle && (
<React.Fragment key={i}>
{c.name !== 'circle' && (
<path d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`} />
{c.name !== "circle" && (
<path
d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`}
/>
)}
<circle
cy={circle.y}
@@ -277,7 +200,7 @@ const Radii = ({
stroke={
(Math.round(circle.x * 100) / 100) % 1 !== 0 ||
(Math.round(circle.y * 100) / 100) % 1 !== 0
? 'red'
? "red"
: undefined
}
/>
@@ -285,7 +208,11 @@ const Radii = ({
cy={circle.y}
cx={circle.x}
r={circle.r}
stroke={(Math.round(circle.r * 1000) / 1000) % 1 !== 0 ? 'red' : undefined}
stroke={
(Math.round(circle.r * 1000) / 1000) % 1 !== 0
? "red"
: undefined
}
/>
</React.Fragment>
),
@@ -303,28 +230,13 @@ const Handles = ({
>) => {
console.log(paths);
return (
<g
className="svg-preview-handles-group"
{...props}
>
<g className="svg-preview-handles-group" {...props}>
{paths.map(({ c, prev, next, cp1, cp2 }) => (
<>
{cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />}
{cp1 && (
<circle
cy={cp1.y}
cx={cp1.x}
r={0.25}
/>
)}
{cp1 && <circle cy={cp1.y} cx={cp1.x} r={0.25} />}
{cp2 && <path d={`M${next.x} ${next.y} ${cp2.x} ${cp2.y}`} />}
{cp2 && (
<circle
cy={cp2.y}
cx={cp2.x}
r={0.25}
/>
)}
{cp2 && <circle cy={cp2.y} cx={cp2.x} r={0.25} />}
</>
))}
</g>
@@ -368,27 +280,9 @@ const SvgPreview = React.forwardRef<
{...props}
>
<style>{darkModeCss}</style>
{showGrid && (
<Grid
strokeWidth={0.1}
stroke="#777"
strokeOpacity={0.3}
radius={1}
/>
)}
<Shadow
paths={paths}
strokeWidth={4}
stroke="#777"
radius={1}
strokeOpacity={0.15}
/>
<Handles
paths={paths}
strokeWidth={0.12}
stroke="#777"
strokeOpacity={0.6}
/>
{showGrid && <Grid strokeWidth={0.1} stroke="#777" strokeOpacity={0.3} radius={1} />}
<Shadow paths={paths} strokeWidth={4} stroke="#777" radius={1} strokeOpacity={0.15} />
<Handles paths={paths} strokeWidth={0.12} stroke="#777" strokeOpacity={0.6} />
<ColoredPath
paths={paths}
colors={[
@@ -413,19 +307,8 @@ const SvgPreview = React.forwardRef<
stroke="#777"
strokeOpacity={0.3}
/>
<ControlPath
radius={1}
paths={paths}
pointSize={1}
stroke="#fff"
strokeWidth={0.125}
/>
<Handles
paths={paths}
strokeWidth={0.12}
stroke="#FFF"
strokeOpacity={0.3}
/>
<ControlPath radius={1} paths={paths} pointSize={1} stroke="#fff" strokeWidth={0.125} />
<Handles paths={paths} strokeWidth={0.12} stroke="#FFF" strokeOpacity={0.3} />
{children}
</svg>
);
+1 -1
View File
@@ -16,7 +16,7 @@ export type Path = {
export type PathProps<
RequiredProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
NeverProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
NeverProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>
> = Required<Pick<React.SVGProps<SVGElement & SVGRectElement & SVGCircleElement>, RequiredProps>> &
Omit<
React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>,
+10 -10
View File
@@ -51,7 +51,7 @@ export const getCommands = (src: string) =>
getNodes(src)
.map(convertToPathNode)
.flatMap(({ d, name }, idx) =>
new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })),
new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name }))
);
export const getPaths = (src: string) => {
@@ -60,10 +60,10 @@ export const getPaths = (src: string) => {
let prev: Point | undefined = undefined;
let start: Point | undefined = undefined;
const addPath = (
c: (typeof commands)[number],
c: typeof commands[number],
next: Point,
d?: string,
extras?: { circle?: Path['circle']; cp1?: Path['cp1']; cp2?: Path['cp2'] },
extras?: { circle?: Path['circle']; cp1?: Path['cp1']; cp2?: Path['cp2'] }
) => {
assert(prev);
paths.push({
@@ -153,7 +153,7 @@ export const getPaths = (src: string) => {
{
cp1: { x: prev.x - reflectedCp1.x, y: prev.y - reflectedCp1.y },
cp2: { x: c.x2, y: c.y2 },
},
}
);
break;
}
@@ -169,7 +169,7 @@ export const getPaths = (src: string) => {
assert(prev);
const backTrackCP = (
index: number,
currentPoint: { x: number; y: number },
currentPoint: { x: number; y: number }
): { x: number; y: number } => {
const previousCommand = commands[index - 1];
if (!previousCommand) {
@@ -211,7 +211,7 @@ export const getPaths = (src: string) => {
{
cp1: { x: prevCP.x, y: prevCP.y },
cp2: { x: prevCP.x, y: prevCP.y },
},
}
);
break;
}
@@ -226,13 +226,13 @@ export const getPaths = (src: string) => {
c.lArcFlag,
c.sweepFlag,
c.x,
c.y,
c.y
);
addPath(
c,
c,
`M ${prev.x} ${prev.y} A${c.rX} ${c.rY} ${c.xRot} ${c.lArcFlag} ${c.sweepFlag} ${c.x} ${c.y}`,
{ circle: c.rX === c.rY ? { ...center, r: c.rX } : undefined },
{ circle: c.rX === c.rY ? { ...center, r: c.rX } : undefined }
);
break;
}
@@ -253,7 +253,7 @@ export const arcEllipseCenter = (
fa: number,
fs: number,
x2: number,
y2: number,
y2: number
) => {
const phi = (a * Math.PI) / 180;
@@ -280,7 +280,7 @@ export const arcEllipseCenter = (
sign *
Math.sqrt(
Math.max(rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p, 0) /
(rx * rx * y1p * y1p + ry * ry * x1p * x1p),
(rx * rx * y1p * y1p + ry * ry * x1p * x1p)
);
const V2 = [(rx * y1p) / ry, (-ry * x1p) / rx];
+11 -17
View File
@@ -1,34 +1,28 @@
import fs from 'fs';
import path from 'path';
import { Category, IconEntity } from '../theme/types';
import fs from "fs";
import path from "path";
import {Category, IconEntity} from "../theme/types";
const directory = path.join(process.cwd(), '../categories');
const directory = path.join(process.cwd(), "../categories");
export function getAllCategoryFiles(): Category[] {
const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json');
return fileNames.map((fileName) => {
const name = path.basename(fileName, '.json');
const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8');
const name = path.basename(fileName, '.json')
const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8')
const parsedFileContent = JSON.parse(fileContent);
const parsedFileContent = JSON.parse(fileContent)
return {
name,
title: parsedFileContent.title,
};
}
});
}
export function mapCategoryIconCount(
categories: Category[],
icons: { categories: IconEntity['categories'] }[],
) {
export function mapCategoryIconCount(categories: Category[], icons: { categories: IconEntity['categories'] }[]) {
return categories.map((category) => ({
...category,
iconCount: icons.reduce(
(acc, curr) => (curr.categories.includes(category.name) ? ++acc : acc),
0,
),
}));
iconCount: icons.reduce((acc, curr) => (curr.categories.includes(category.name) ? ++acc : acc), 0)
}))
}
+31 -26
View File
@@ -1,18 +1,24 @@
import { bundledLanguages, type ThemeRegistration } from 'shikiji';
import { getHighlighter } from 'shikiji';
import {
bundledLanguages,
type ThemeRegistration
} from 'shikiji'
import {
getHighlighter,
} from 'shikiji'
type CodeExampleType = {
title: string;
language: string;
code: string;
}[];
title: string,
language: string,
code: string,
}[]
const getIconCodes = (): CodeExampleType => {
return [
{
language: 'html',
title: 'HTML',
code: `<i data-lucide="Name"></i>`,
code: `<i data-lucide="Name"></i>`
},
{
language: 'tsx',
@@ -103,37 +109,36 @@ import { LucideAngularModule, PascalCase } from 'lucide-angular';
<div class="icon-Name"></div>
`,
},
];
};
}
]
}
export type ThemeOptions =
| ThemeRegistration
| { light: ThemeRegistration; dark: ThemeRegistration };
| { light: ThemeRegistration; dark: ThemeRegistration }
const highLightCode = async (code: string, lang: string, active?: boolean) => {
const highlighter = await getHighlighter({
themes: ['github-light', 'github-dark'],
langs: Object.keys(bundledLanguages),
});
langs: Object.keys(bundledLanguages)
})
const highlightedCode = highlighter
.codeToHtml(code, {
lang,
themes: {
light: 'github-light',
dark: 'github-dark',
},
defaultColor: false,
})
.replace('shiki-themes', 'shiki-themes vp-code');
const highlightedCode = highlighter.codeToHtml(code, {
lang,
themes: {
light: 'github-light',
dark: 'github-dark'
},
defaultColor: false
}).replace('shiki-themes', 'shiki-themes vp-code')
return `<div class="language-${lang} ${active ? 'active' : ''}">
<button title="Copy Code" class="copy"></button>
<span class="lang">${lang}</span>
${highlightedCode}
</div>`;
};
</div>`
}
export default async function createCodeExamples() {
const codes = getIconCodes();
@@ -148,7 +153,7 @@ export default async function createCodeExamples() {
language: language,
code: codeString,
};
});
})
return Promise.all(codeExamplePromises);
}
+23 -27
View File
@@ -1,42 +1,38 @@
import { promises as fs, constants } from 'fs';
import path from 'path';
import yaml from 'js-yaml';
import yaml from 'js-yaml'
import { PackageItem } from '../theme/types';
const fileExist = (filePath) =>
fs
.access(filePath, constants.F_OK)
.then(() => true)
.catch(() => false);
const fileExist = (filePath) => fs.access(filePath, constants.F_OK).then(() => true).catch(() => false)
const fetchPackages = async (): Promise<PackageItem[]> => {
const docsDir = path.resolve(process.cwd(), '../packages');
const fileNames = await (
await fs.readdir(docsDir)
).map((filename) => ({ filename, directory: docsDir }));
const fileNames = await (await fs.readdir(docsDir)).map(filename => ({filename, directory: docsDir}))
const packageJsons = await Promise.all(
fileNames.map(async ({ filename, directory }) => {
const filePath = path.resolve(directory, filename);
const fileStat = await fs.lstat(filePath);
const packageJsons = await Promise.all(fileNames.map( async ({filename, directory}) => {
const filePath = path.resolve(directory, filename)
const fileStat = await fs.lstat(filePath);
if (!fileStat.isDirectory()) return null;
if(!fileStat.isDirectory()) return null;
const jsonFilePath = path.resolve(filePath, 'package.json');
if (await fileExist(jsonFilePath)) {
return JSON.parse(await fs.readFile(jsonFilePath, 'utf-8'));
}
const jsonFilePath = path.resolve(filePath, 'package.json')
if (await fileExist(jsonFilePath)) {
return JSON.parse(
await fs.readFile(jsonFilePath, 'utf-8')
)
}
const ymlFilePath = path.resolve(filePath, 'pubspec.yaml');
if (await fileExist(ymlFilePath)) {
return yaml.load(await fs.readFile(ymlFilePath, 'utf-8'));
}
const ymlFilePath = path.resolve(filePath, 'pubspec.yaml')
if(await fileExist(ymlFilePath)) {
return yaml.load(
await fs.readFile(ymlFilePath, 'utf-8')
);
}
return null;
}),
);
return null
}))
return packageJsons;
};
return packageJsons
}
export default fetchPackages;
+7 -5
View File
@@ -1,15 +1,17 @@
export type IconContent = [icon: string, src: string];
export type IconContent = [icon: string, src:string];
async function generateZip(icons: IconContent[]) {
const JSZip = (await import('jszip')).default;
const JSZip = (await import('jszip')).default
const zip = new JSZip();
const addingZipPromises = icons.map(([name, src]) => zip.file(`${name}.svg`, src));
const addingZipPromises = icons.map(([name, src]) =>
zip.file(`${name}.svg`, src),
);
await Promise.all(addingZipPromises);
await Promise.all(addingZipPromises)
return zip.generateAsync({ type: 'blob' });
}
export default generateZip;
export default generateZip
+13 -11
View File
@@ -1,15 +1,17 @@
import { createLucideIcon } from 'lucide-react/src/lucide-react';
import { type LucideProps, type IconNode } from 'lucide-react/src/createLucideIcon';
import { IconEntity } from '../theme/types';
import { createLucideIcon } from "lucide-react/src/lucide-react"
import { type LucideProps, type IconNode } from "lucide-react/src/createLucideIcon"
import { IconEntity } from "../theme/types"
import { renderToStaticMarkup } from 'react-dom/server';
import { IconContent } from './generateZip';
import { IconContent } from "./generateZip";
const getFallbackZip = (icons: IconEntity[], params: LucideProps) => {
return icons.map<IconContent>((icon) => {
const Icon = createLucideIcon(icon.name, icon.iconNode as IconNode);
const src = renderToStaticMarkup(<Icon {...params} />);
return [icon.name, src];
});
};
return icons
.map<IconContent>((icon) => {
const Icon = createLucideIcon(icon.name, icon.iconNode as IconNode)
const src = renderToStaticMarkup(<Icon {...params} />)
return [icon.name, src]
})
}
export default getFallbackZip;
export default getFallbackZip
+19 -19
View File
@@ -1,34 +1,34 @@
import fs from 'fs';
import path from 'path';
import { IconNodeWithKeys } from '../theme/types';
import iconNodes from '../data/iconNodes';
import releaseMeta from '../data/releaseMetaData.json';
import fs from "fs";
import path from "path";
import { IconNodeWithKeys } from "../theme/types";
import iconNodes from '../data/iconNodes'
import releaseMeta from "../data/releaseMetaData.json";
const DATE_OF_FORK = '2020-06-08T16:39:52+0100';
const directory = path.join(process.cwd(), '../icons');
const directory = path.join(process.cwd(), "../icons");
export interface GetDataOptions {
withChildKeys?: boolean;
withChildKeys?: boolean
}
export async function getData(name: string) {
const jsonPath = path.join(directory, `${name}.json`);
const jsonContent = fs.readFileSync(jsonPath, 'utf8');
const jsonContent = fs.readFileSync(jsonPath, "utf8");
const { tags, categories, contributors } = JSON.parse(jsonContent);
const iconNode = iconNodes[name];
const iconNode = iconNodes[name]
const releaseData = releaseMeta?.[name] ?? {
createdRelease: {
version: '0.0.0',
date: DATE_OF_FORK,
"createdRelease": {
"version": "0.0.0",
"date": DATE_OF_FORK
},
changedRelease: {
version: '0.0.0',
date: DATE_OF_FORK,
},
};
"changedRelease": {
"version": "0.0.0",
"date": DATE_OF_FORK
}
}
return {
name,
@@ -36,11 +36,11 @@ export async function getData(name: string) {
categories,
iconNode,
contributors,
...releaseData,
...releaseData
};
}
export async function getAllData(): Promise<{ name: string; iconNode: IconNodeWithKeys }[]> {
export async function getAllData(): Promise<{ name: string, iconNode: IconNodeWithKeys}[]> {
const names = Object.keys(iconNodes);
return Promise.all(names.map((name) => getData(name)));
+39 -29
View File
@@ -1,31 +1,31 @@
import { DefaultTheme, UserConfig } from 'vitepress';
import { DefaultTheme, UserConfig } from "vitepress"
const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
guide: [
'guide':[
{
text: 'Introduction',
items: [
{ text: 'What is lucide?', link: '/guide/' },
{ text: 'Installation', link: '/guide/installation' },
{ text: 'Comparison', link: '/guide/comparison' },
],
{ text: 'Comparison', link: '/guide/comparison' }
]
},
{
text: 'Basics',
items: [
{
text: 'Color',
link: '/guide/basics/color',
link: '/guide/basics/color'
},
{
text: 'Sizing',
link: '/guide/basics/sizing',
link: '/guide/basics/sizing'
},
{
text: 'Stroke width',
link: '/guide/basics/stroke-width',
link: '/guide/basics/stroke-width'
},
],
]
},
// TODO: Add this section
{
@@ -37,14 +37,14 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
// },
{
text: 'Global styling',
link: '/guide/advanced/global-styling',
link: '/guide/advanced/global-styling'
},
// {
// text: 'Animations',
// },
{
text: 'Filled icons',
link: '/guide/advanced/filled-icons',
link: '/guide/advanced/filled-icons'
},
// {
// text: 'Combining icons',
@@ -55,73 +55,83 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
// {
// text: 'Auto importing'
// },
],
]
},
{
text: 'Packages',
items: [
{
text: 'Lucide',
link: '/guide/packages/lucide',
link: '/guide/packages/lucide'
},
{
text: 'Lucide React',
link: '/guide/packages/lucide-react',
link: '/guide/packages/lucide-react'
},
{
text: 'Lucide React Native',
link: '/guide/packages/lucide-react-native',
link: '/guide/packages/lucide-react-native'
},
{
text: 'Lucide Vue',
link: '/guide/packages/lucide-vue-next',
link: '/guide/packages/lucide-vue'
},
{
text: 'Lucide Vue Next (Vue 3)',
link: '/guide/packages/lucide-vue-next'
},
{
text: 'Lucide Svelte',
link: '/guide/packages/lucide-svelte',
link: '/guide/packages/lucide-svelte'
},
{
text: 'Lucide Solid',
link: '/guide/packages/lucide-solid',
link: '/guide/packages/lucide-solid'
},
{
text: 'Lucide Preact',
link: '/guide/packages/lucide-preact',
link: '/guide/packages/lucide-preact'
},
{
text: 'Lucide Angular',
link: '/guide/packages/lucide-angular',
link: '/guide/packages/lucide-angular'
},
{
text: 'Lucide Static',
link: '/guide/packages/lucide-static',
link: '/guide/packages/lucide-static'
},
],
{
text: 'Lucide Flutter',
link: '/guide/packages/lucide-flutter'
},
]
},
{
text: 'Contributing',
items: [
{
text: 'Icon Design Principles',
link: '/guide/design/icon-design-guide',
link: '/guide/design/icon-design-guide'
},
{
text: 'Designing in Illustrator',
link: '/guide/design/illustrator-guide',
link: '/guide/design/illustrator-guide'
},
{
text: 'Designing in InkScape',
link: '/guide/design/inkscape-guide',
link: '/guide/design/inkscape-guide'
},
{
text: 'Designing in Figma',
link: '/guide/design/figma-guide',
link: '/guide/design/figma-guide'
},
],
]
},
],
// This should be here to keep the sidebar shown on the icons page
icons: [{ text: '', link: '/' }],
};
'icons': [
{ text: '', link: '/' },
],
}
export default sidebar;
export default sidebar
@@ -9,6 +9,8 @@ const props = defineProps<{
}>()
const component = computed(() => props.href ? 'a' : 'div')
</script>
<template>
@@ -23,7 +25,7 @@ const component = computed(() => props.href ? 'a' : 'div')
</template>
<style>
.badge, a.badge, .vp-doc a.badge {
.badge, a.badge {
display: block;
border: 1px solid transparent;
text-align: center;
@@ -58,6 +60,4 @@ const component = computed(() => props.href ? 'a' : 'div')
/* color: var(--vp-button-alt-active-text);
background-color: var(--vp-button-alt-active-bg); */
}
</style>
@@ -94,7 +94,8 @@ const ChevronUp = createLucideIcon('ChevronUp', chevronUp)
.menu {
position: relative;
}
ul.menu-items {
.menu-items {
--menu-offset: 44px;
position: absolute;
display: flex;
flex-direction: column;
@@ -102,7 +103,7 @@ ul.menu-items {
padding: 12px;
min-width: 128px;
border: 1px solid var(--vp-c-divider);
background-color: var(--vp-c-bg-elv);
background-color: var(--vp-c-bg);
box-shadow: var(--vp-shadow-3);
transition: background-color 0.5s;
max-height: calc(100vh - var(--vp-nav-height));
@@ -123,12 +124,11 @@ ul.menu-items {
color: var(--vp-c-text-1);
white-space: nowrap;
transition: background-color .25s,color .25s;
list-style: none;
}
.menu-item:hover {
color: var(--vp-c-brand);
background-color: var(--vp-c-default-soft);
background-color: var(--vp-c-bg-elv-mute);
}
.menu-item:active {
@@ -170,11 +170,11 @@ ul.menu-items {
}
.menu-items.bottom {
top: 32px;
top: var(--menu-offset);
}
.menu-items.top {
bottom: 48px;
bottom: var(--menu-offset);
}
.arrow-up-button.top::before {
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { computed, defineProps, onMounted } from 'vue'
const props = defineProps<{
groups: string[] | undefined,
groupName: string,
@@ -17,7 +17,7 @@ const SearchIcon = createLucideIcon('search', search)
background: var(--vp-c-bg-soft);
border-radius: 8px;
color: var(--vp-c-text-2);
padding: 11px 16px;
padding: 12px 16px;
height: auto;
font-size: 14px;
/* box-shadow: var(--vp-shadow-4), var(--vp-shadow-2); */
@@ -1,18 +1,16 @@
export default {
async load() {
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest')
.then((res) => {
if (res.ok) {
const releaseData = res.json() as Promise<{ tag_name: string }>;
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest').then(res => {
if (res.ok) {
const releaseData = res.json() as Promise<{ tag_name: string }>
return releaseData;
}
return null;
})
.then((res) => res.tag_name);
return releaseData
}
return null
}).then(res => res.tag_name)
return {
version,
};
},
};
version
}
}
}
@@ -1,17 +1,16 @@
import iconNodes from '../../../data/iconNodes';
import iconNodes from '../../../data/iconNodes'
const getRandomItem = <Item>(items: Item[]): Item =>
items[Math.floor(Math.random() * items.length)];
const getRandomItem = <Item>(items: Item[]): Item => items[Math.floor(Math.random()*items.length)];
export default {
async load() {
const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode }));
const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode }))
const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons));
const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons))
return {
icons: randomIcons,
iconsCount: icons.length,
};
},
};
}
}
}
@@ -47,7 +47,7 @@ export default {
logo: '/framework-logos/flutter.svg',
label: 'Lucide documentation for Flutter',
},
],
};
},
};
]
}
}
}
@@ -1,15 +1,16 @@
import { getAllData } from '../../../lib/icons';
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
import iconsMetaData from '../../../data/iconMetaData';
import iconsMetaData from '../../../data/iconMetaData'
export default {
async load() {
let categories = getAllCategoryFiles();
let categories = getAllCategoryFiles()
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData));
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData))
return {
categories,
};
},
};
}
}
}
@@ -1,15 +1,13 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { ref, computed } from 'vue'
import { useData } from 'vitepress'
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
import { isActive } from 'vitepress/dist/client/shared'
import { useActiveAnchor } from '../../composables/useActiveAnchor'
import { data } from './CategoryList.data'
import CategoryListItem from './CategoryListItem.vue'
import { useCategoryView } from '../../composables/useCategoryView'
const { page } = useData()
const { categoryCounts } = useCategoryView();
const categoriesIsActive = computed(() => {
return isActive(page.value.relativePath, '/icons/categories');
@@ -27,21 +25,14 @@ const headers = computed(() => {
level: 2,
link: `${linkPrefix}#${name}`,
title,
iconCount: categoryCounts.value[name] ?? iconCount,
name
iconCount
}))
})
const container = ref()
const marker = ref()
const { setActiveLinkDebounced } = useActiveAnchor(container, marker)
watch(headers, () => {
setTimeout(() => {
setActiveLinkDebounced()
}, 200)
})
useActiveAnchor(container, marker)
</script>
<template>
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { onMounted } from 'vue';
import { useCategoryView } from '../../composables/useCategoryView';
interface Header {
@@ -7,7 +8,6 @@ interface Header {
slug: string;
iconCount: number;
link: string;
name: string;
children: Header[];
}
@@ -15,35 +15,36 @@ type MenuItem = Omit<Header, 'slug' | 'children'> & {
children?: MenuItem[];
};
defineProps<{
const props = defineProps<{
headers: MenuItem[];
root?: boolean;
}>();
const { selectedCategory } = useCategoryView();
function onClick(categoryName: string) {
selectedCategory.value = categoryName;
function onClick(event: Event) {
const target =
(event.target as HTMLElement).nodeName === 'span'
? (event.target as HTMLElement).parentNode
: (event.target as HTMLElement);
const href = (target as HTMLAnchorElement)?.href;
const heading = document.querySelector<HTMLAnchorElement>(categoryName);
heading?.focus();
if (href) {
const id = '#' + href.split('#')[1];
const decodedId = decodeURIComponent(id);
window.history.pushState({}, '', `/icons/categories#${categoryName}`)
selectedCategory.value = decodedId.replace('#', '');
const heading = document.querySelector<HTMLAnchorElement>(decodedId);
heading?.focus();
}
}
</script>
<template>
<ul :class="root ? 'root' : 'nested'">
<li v-for="{ children, link, title, iconCount, name } in headers">
<a
class="outline-link"
:href="link"
@click="onClick(name)"
:title="title"
:class="{
inactive: iconCount === 0,
}"
>
<li v-for="{ children, link, title, iconCount } in headers">
<a class="outline-link" :href="link" @click="onClick" :title="title">
<span>
{{ title }}
</span>
@@ -83,10 +84,6 @@ function onClick(categoryName: string) {
transition: color 0.25s;
}
.outline-link.inactive {
color: var(--vp-c-text-4);
}
.outline-link.nested {
padding-left: 13px;
}
@@ -97,8 +94,4 @@ function onClick(categoryName: string) {
font-size: 11px;
font-weight: 400;
}
.outline-link.inactive .icon-count {
opacity: 0;
}
</style>
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue';
import { computed, ref } from 'vue';
import { startCase, camelCase } from 'lodash-es'
import ButtonMenu from '../base/ButtonMenu.vue'
import { useIconStyleContext } from '../../composables/useIconStyle';
@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { IconEntity } from '../../types'
import { computed } from 'vue'
import { computed, ref, watch } from 'vue'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
import IconButton from '../base/IconButton.vue';
import IconContributors from './IconContributors.vue';
@@ -4,7 +4,7 @@ import IconItem from './IconItem.vue'
const emit = defineEmits(['setActiveIcon'])
defineProps<{
const props = defineProps<{
icons: IconEntity[]
activeIcon?: string
overlayMode?: boolean
@@ -19,11 +19,7 @@ function setActiveIcon(name: string) {
<template>
<div class="icons">
<div
class="icon"
v-for="icon in icons"
:key="icon.name"
>
<div class="icon" v-for="icon in icons" :key="icon.name">
<IconItem
v-bind="icon"
@setActiveIcon="setActiveIcon(icon.name)"
@@ -40,6 +36,7 @@ function setActiveIcon(name: string) {
.icons {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
/* padding: 32px 32px 96px; */
gap: 8px;
width: 100%;
}
@@ -1,5 +1,4 @@
<script setup lang="ts">
import { computed } from 'vue';
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
import { useMediaQuery } from '@vueuse/core';
import { useRouter } from 'vitepress';
@@ -28,10 +27,7 @@ const showOverlay = useMediaQuery('(min-width: 860px)');
const { animate, confetti, confettiText } = useConfetti()
const icon = computed(() => {
if (!props.name || !props.iconNode) return null
return createLucideIcon(props.name, props.iconNode)
})
const icon = createLucideIcon(props.name, props.iconNode)
async function navigateToIcon(event) {
@@ -1,53 +1,56 @@
<script lang="ts">
import { IconEntity } from '../../types';
type CategoryNameRow = {
type: 'category';
title: string;
name: string;
};
type CategoryIconsRow = {
type: 'icons';
icons: IconEntity[];
};
export type CategoryRow = CategoryNameRow | CategoryIconsRow;
</script>
<script setup lang="ts">
import { ref } from 'vue';
import { Category } from '../../types';
import IconGrid from './IconGrid.vue'
import { vIntersectionObserver } from '@vueuse/components'
defineProps<{
activeIconName: string
categoryRow: CategoryRow
category: Category
}>()
const emit = defineEmits(['setActiveIcon'])
const showIcons = ref(false)
// Added intersection observer to improve performance
const onIntersectionObserver: IntersectionObserverCallback = ([{ isIntersecting }]) => {
showIcons.value = isIntersecting
}
</script>
<template>
<h2
v-if="categoryRow.type === 'category'"
class="title"
<section
class="category"
:key="category.name"
:id="category.name"
v-intersection-observer="onIntersectionObserver"
>
<a class="header-anchor" :href="`#${categoryRow.name}`" :aria-label="`Permalink to &quot;${categoryRow.title}&quot;`">&ZeroWidthSpace;</a>
{{ categoryRow.title }}
</h2>
<IconGrid
v-else-if="categoryRow.type === 'icons'"
:activeIcon="activeIconName"
:icons="categoryRow.icons"
@setActiveIcon="$event => $emit('setActiveIcon', $event)"
overlayMode
/>
<h2 class="title" >
<a class="header-anchor" :href="`#${category.name}`" :aria-label="`Permalink to &quot;${category.title}&quot;`">&ZeroWidthSpace;</a>
{{ category.title }}
</h2>
<IconGrid
:activeIcon="activeIconName"
:icons="category.icons"
@setActiveIcon="$event => $emit('setActiveIcon', $event)"
overlayMode
:hideIcons="!showIcons"
/>
</section>
</template>
<style scoped>
.title {
margin-bottom: 8px;
margin-bottom: 24px;
font-size: 19px;
font-weight: 500;
padding: 24px 0 8px;
padding-top: 86px;
/* scroll-padding-top: 240px; */
}
.category {
margin-bottom: calc(-86px + 32px);
}
</style>
@@ -1,20 +1,14 @@
<script setup lang="ts">
import { ref, computed, defineAsyncComponent, onMounted } from 'vue';
import { ref, computed, defineAsyncComponent } from 'vue';
import type { IconEntity, Category } from '../../types';
import useSearch from '../../composables/useSearch';
import InputSearch from '../base/InputSearch.vue';
import useSearchInput from '../../composables/useSearchInput';
import StickyBar from './StickyBar.vue';
import IconsCategory from './IconsCategory.vue';
import { useFetch } from '@vueuse/core';
import useFetchTags from '../../composables/useFetchTags';
import useFetchCategories from '../../composables/useFetchCategories';
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
import chunkArray from '../../utils/chunkArray';
import { CategoryRow } from './IconsCategory.vue';
import useScrollToCategory from '../../composables/useScrollToCategory';
const ICON_SIZE = 56;
const ICON_GRID_GAP = 8;
const props = defineProps<{
icons: IconEntity[];
@@ -24,6 +18,7 @@ const props = defineProps<{
const activeIconName = ref(null);
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
const isSearching = computed(() => !!searchQuery.value);
function setActiveIconName(name: string) {
@@ -33,13 +28,6 @@ function setActiveIconName(name: string) {
const { execute: fetchTags, data: tags } = useFetchTags();
const { execute: fetchCategories, data: categoriesMap } = useFetchCategories();
const overviewEl = ref<HTMLElement | null>(null);
const { width: containerWidth } = useElementSize(overviewEl)
const columnSize = computed(() => {
return Math.floor((containerWidth.value) / ((ICON_SIZE + ICON_GRID_GAP)));
});
const mappedIcons = computed(() => {
if (tags.value == null) {
return props.icons;
@@ -56,10 +44,9 @@ const mappedIcons = computed(() => {
});
});
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
{ name: 'name', weight: 3 },
{ name: 'aliases', weight: 3 },
{ name: 'tags', weight: 2 },
const searchResults = useSearch(searchQuery, mappedIcons, [
{ name: 'name', weight: 2 },
{ name: 'tags', weight: 1 },
]);
const categories = computed(() => {
@@ -85,43 +72,9 @@ const categories = computed(() => {
icons: searchedCategoryIcons,
};
})
.filter(({ icons }) => icons.length);
});
const categoriesList = computed(() => {
return categories.value
.filter(({ icons }) => icons.length)
.reduce<CategoryRow[]>((acc, category) => {
acc.push({ type: 'category', title: category.title, name: category.name });
const categoryIcons = chunkArray(category.icons, columnSize.value);
categoryIcons.forEach((icons) => {
acc.push({ type: 'icons', icons });
});
return acc;
}, []);
});
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
categoriesList,
{
itemHeight: ICON_SIZE + ICON_GRID_GAP,
overscan: 10
},
)
useScrollToCategory({
categories,
categoriesList,
scrollTo,
searchQueryDebounced,
})
onMounted(() => {
containerProps.ref.value = document.documentElement;
useEventListener(window, 'scroll', containerProps.onScroll)
})
function onFocusSearchInput() {
if (tags.value == null) {
fetchTags();
@@ -132,35 +85,28 @@ function onFocusSearchInput() {
}
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
</script>
<template>
<div ref="overviewEl" class="overview-container">
<StickyBar class="category-search">
<InputSearch
:placeholder="`Search ${icons.length} icons ...`"
v-model="searchQuery"
class="input-wrapper"
ref="searchInput"
@focus="onFocusSearchInput"
/>
</StickyBar>
<NoResults
v-if="categories.length === 0"
:searchQuery="searchQuery"
@clear="searchQuery = ''"
<StickyBar class="search-bar category-search">
<InputSearch
:placeholder="`Search ${icons.length} icons ...`"
v-model="searchQuery"
class="input-wrapper"
ref="searchInput"
@focus="onFocusSearchInput"
/>
<div v-bind="wrapperProps">
<IconsCategory
v-for="{ index, data } in list"
:categoryRow="data"
:activeIconName="activeIconName"
@setActiveIcon="setActiveIconName"
:key="index"
/>
</div>
</div>
</StickyBar>
<NoResults v-if="categories.length === 0" :searchQuery="searchQuery" @clear="searchQuery = ''" />
<IconsCategory
v-for="category in categories"
:key="category.name"
:category="category"
:activeIconName="activeIconName"
@setActiveIcon="setActiveIconName"
/>
<IconDetailOverlay
v-if="activeIconName != null"
:iconName="activeIconName"
@@ -174,21 +120,6 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
}
.search-bar.category-search {
margin-bottom: 10px;
}
.title {
margin-bottom: 8px;
font-size: 19px;
font-weight: 500;
padding: 24px 0 8px;
}
.icons {
margin-bottom: 8px;
}
.overview-container {
padding-bottom: 288px;
margin-bottom: -54px;
}
</style>

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