Improve formatting (#1814)

* Ignore linting for examples in docs

* Formatting JSX single attribute per line

* Separte `format` and `lint:format` in package.json

* Bump prettier version

* Run format
This commit is contained in:
Han Yeong-woo
2024-02-01 22:38:21 +09:00
committed by GitHub
parent b96e6acd2e
commit eb035fe370
1520 changed files with 3644 additions and 3204 deletions

View File

@@ -6,6 +6,5 @@ tests
node_modules node_modules
.eslintrc.js .eslintrc.js
docs/images docs/images
docs/guide/basics/examples docs/**/examples/
docs/guide/advanced/examples
packages/lucide-react/dynamicIconImports.js packages/lucide-react/dynamicIconImports.js

View File

@@ -42,12 +42,15 @@ module.exports = {
'@html-eslint/no-duplicate-attrs': 'error', '@html-eslint/no-duplicate-attrs': 'error',
'@html-eslint/no-inline-styles': 'error', '@html-eslint/no-inline-styles': 'error',
'@html-eslint/require-attrs': [ '@html-eslint/require-attrs': [
'error', 'error',
...Object.entries(DEFAULT_ATTRS) ...Object.entries(DEFAULT_ATTRS).map(([attr, value]) => ({
.map(([attr, value]) => ({ tag: 'svg', attr, value: String(value) })) tag: 'svg',
attr,
value: String(value),
})),
], ],
'@html-eslint/indent': ['error', 2], '@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': [ '@html-eslint/no-extra-spacing-attrs': [
'error', 'error',
{ {
@@ -64,7 +67,7 @@ module.exports = {
'@html-eslint/element-newline': 'error', '@html-eslint/element-newline': 'error',
'@html-eslint/no-trailing-spaces': 'error', '@html-eslint/no-trailing-spaces': 'error',
'@html-eslint/quotes': 'error', '@html-eslint/quotes': 'error',
} },
}, },
], ],
}; };

View File

@@ -1,5 +1,5 @@
name: "Build and Test" name: 'Build and Test'
description: "Builds and test a package" description: 'Builds and test a package'
inputs: inputs:
name: name:
@@ -7,7 +7,7 @@ inputs:
required: true required: true
runs: runs:
using: "composite" using: 'composite'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4

View File

@@ -1,5 +1,5 @@
name: "Check icons" name: 'Check icons'
description: "Cross-checks icon and category references in JSON descriptors" description: 'Cross-checks icon and category references in JSON descriptors'
inputs: inputs:
name: name:
@@ -7,7 +7,7 @@ inputs:
required: true required: true
runs: runs:
using: "composite" using: 'composite'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4

96
.github/labeler.yml vendored
View File

@@ -1,92 +1,92 @@
# For changed dependencies # For changed dependencies
📦 dependencies: 📦 dependencies:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- pnpm-lock.yaml - pnpm-lock.yaml
# For changes in documentation # For changes in documentation
📖 documentation: 📖 documentation:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- docs/*.md - docs/*.md
- docs/**/*.md - docs/**/*.md
# For changes in the site, but not markdown files # For changes in the site, but not markdown files
🌍 site: 🌍 site:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'docs/**' - 'docs/**'
# For changes in the metadata # For changes in the metadata
🫧 metadata: 🫧 metadata:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'icons/*.json' - 'icons/*.json'
- categories/* - categories/*
# For changes or added icons # For changes or added icons
🎨 icon: 🎨 icon:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'icons/*.svg' - 'icons/*.svg'
# For changes in the lucide package # For changes in the lucide package
🧳 lucide package: 🧳 lucide package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide/*' - 'packages/lucide/*'
# For changes in the lucide React package # For changes in the lucide React package
⚛️ react package: ⚛️ react package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-react/*' - 'packages/lucide-react/*'
# For changes in the lucide React Native package # For changes in the lucide React Native package
⚛️ react native package: ⚛️ react native package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-react-native/*' - 'packages/lucide-react-native/*'
# For changes in the lucide vue packages # For changes in the lucide vue packages
💎 vue package: 💎 vue package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-vue/*' - 'packages/lucide-vue/*'
- 'packages/lucide-vue-next/*' - 'packages/lucide-vue-next/*'
# For changes in the lucide angular package # For changes in the lucide angular package
🅰️ angular package: 🅰️ angular package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-angular/*' - 'packages/lucide-angular/*'
# For changes in the lucide preact package # For changes in the lucide preact package
⚛️ preact package: ⚛️ preact package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-preact/*' - 'packages/lucide-preact/*'
# For changes in the lucide svelte package # For changes in the lucide svelte package
🧣 svelte package: 🧣 svelte package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-svelte/*' - 'packages/lucide-svelte/*'
# For changes in the lucide solid package # For changes in the lucide solid package
🪝 solid package: 🪝 solid package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-solid/*' - 'packages/lucide-solid/*'
# For changes in the lucide static package # For changes in the lucide static package
🪨 static package: 🪨 static package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-static/*' - 'packages/lucide-static/*'
# For changes in the lucide flutter package # For changes in the lucide flutter package
🏹 flutter package: 🏹 flutter package:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- 'packages/lucide-flutter/*' - 'packages/lucide-flutter/*'

View File

@@ -1,7 +1,7 @@
name: Close stale issues and PR name: Close stale issues and PR
on: on:
schedule: schedule:
- cron: "45 1 * * *" - cron: '45 1 * * *'
jobs: jobs:
stale: stale:

View File

@@ -1,6 +1,6 @@
name: "Pull Request Labeler" name: 'Pull Request Labeler'
on: on:
- pull_request_target - pull_request_target
jobs: jobs:
triage: triage:
@@ -9,4 +9,4 @@ jobs:
pull-requests: write pull-requests: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/labeler@v5 - uses: actions/labeler@v5

View File

@@ -29,7 +29,7 @@ jobs:
- name: Create font in ./lucide-font - name: Create font in ./lucide-font
run: pnpm build:font run: pnpm build:font
- name: "Upload to Artifacts" - name: 'Upload to Artifacts'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: lucide-font name: lucide-font

View File

@@ -41,17 +41,18 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
package: [ package:
'lucide', [
'lucide-react', 'lucide',
'lucide-react-native', 'lucide-react',
'lucide-vue', 'lucide-react-native',
'lucide-vue-next', 'lucide-vue',
'lucide-angular', 'lucide-vue-next',
'lucide-preact', 'lucide-angular',
'lucide-solid', 'lucide-preact',
'lucide-svelte', 'lucide-solid',
] 'lucide-svelte',
]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
@@ -136,7 +137,7 @@ jobs:
- name: Create font in ./lucide-font - name: Create font in ./lucide-font
run: pnpm build:font run: pnpm build:font
- name: "Upload to Artifacts" - name: 'Upload to Artifacts'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: lucide-font name: lucide-font
@@ -145,10 +146,7 @@ jobs:
post-release: post-release:
if: github.repository == 'lucide-icons/lucide' if: github.repository == 'lucide-icons/lucide'
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ needs: [pre-release, lucide-font]
pre-release,
lucide-font,
]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View File

@@ -1,5 +1,8 @@
pnpm-lock.yaml pnpm-lock.yaml
# docs examples
docs/**/examples/
# lucide-angular # lucide-angular
packages/lucide-angular/.angular/cache packages/lucide-angular/.angular/cache

11
.vscode/settings.json vendored
View File

@@ -1,13 +1,6 @@
{ {
"cSpell.words": [ "cSpell.words": ["devs", "preact", "Preact"],
"devs",
"preact",
"Preact"
],
"eslint.enable": true, "eslint.enable": true,
"eslint.validate": [ "eslint.validate": ["javascript", "svg"],
"javascript",
"svg"
],
"svg.preview.background": "transparent" "svg.preview.background": "transparent"
} }

View File

@@ -1,11 +1,11 @@
import { eventHandler, setResponseHeader } from 'h3' import { eventHandler, setResponseHeader } from 'h3';
import iconMetaData from '../../data/iconMetaData' import iconMetaData from '../../data/iconMetaData';
export default eventHandler((event) => { export default eventHandler((event) => {
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400') setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*') setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
return Object.fromEntries( return Object.fromEntries(
Object.entries(iconMetaData).map(([name, { categories }]) => [ name, categories ]) Object.entries(iconMetaData).map(([name, { categories }]) => [name, categories]),
) );
}) });

View File

@@ -37,13 +37,13 @@ export default eventHandler((event) => {
backdropString, backdropString,
src, src,
color: name in iconNodes ? 'red' : '#777', color: name in iconNodes ? 'red' : '#777',
}) }),
); );
} }
const svg = Buffer.from( const svg = Buffer.from(
// We can't use jsx here, is not supported here by nitro. // 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'); ).toString('utf8');
defaultContentType(event, 'image/svg+xml'); defaultContentType(event, 'image/svg+xml');

View File

@@ -28,7 +28,7 @@ export default eventHandler(async (event) => {
stroke-width="2" stroke-width="2"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
` `,
); );
const resvg = new Resvg(svg, { background: '#000' }); const resvg = new Resvg(svg, { background: '#000' });

View File

@@ -1,12 +1,12 @@
import { eventHandler, setResponseHeader, defaultContentType } from 'h3' import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
import { renderToString } from 'react-dom/server' import { renderToString } from 'react-dom/server';
import { createElement } from 'react' import { createElement } from 'react';
import SvgPreview from '../../../lib/SvgPreview/index.tsx'; 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'; import { parseSync } from 'svgson';
export default eventHandler((event) => { export default eventHandler((event) => {
const { params } = event.context const { params } = event.context;
const [strokeWidth, svgData] = params.data.split('/'); const [strokeWidth, svgData] = params.data.split('/');
const data = svgData.slice(0, -4); const data = svgData.slice(0, -4);
@@ -16,8 +16,8 @@ export default eventHandler((event) => {
const Icon = createLucideIcon( const Icon = createLucideIcon(
'icon', 'icon',
parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`).children.map( parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`).children.map(
({ name, attributes }) => [name, attributes] ({ name, attributes }) => [name, attributes],
) as IconNode ) as IconNode,
); );
const svg = Buffer.from( const svg = Buffer.from(
@@ -33,12 +33,12 @@ export default eventHandler((event) => {
@media screen and (prefers-color-scheme: dark) { @media screen and (prefers-color-scheme: dark) {
svg { stroke: #fff; fill: transparent !important; } svg { stroke: #fff; fill: transparent !important; }
} }
</style>` </style>`,
) ),
).toString('utf8'); ).toString('utf8');
defaultContentType(event, 'image/svg+xml') defaultContentType(event, 'image/svg+xml');
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000') setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
return svg return svg;
}) });

View File

@@ -1,30 +1,30 @@
import { eventHandler, getQuery, setResponseHeader } from 'h3' import { eventHandler, getQuery, setResponseHeader } from 'h3';
import iconNodes from '../../data/iconNodes' import iconNodes from '../../data/iconNodes';
import { IconNodeWithKeys } from '../../theme/types' import { IconNodeWithKeys } from '../../theme/types';
export default eventHandler((event) => { 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, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*') setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
if (withUniqueKeys) { if (withUniqueKeys) {
return iconNodes return iconNodes;
} }
return Object.entries(iconNodes).reduce((acc, [name, iconNode]) => { return Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
if (withUniqueKeys) { if (withUniqueKeys) {
return [name, iconNode] return [name, iconNode];
} }
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs}]) => { const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
return [name, attrs] return [name, attrs];
}) });
acc[name] = newIconNode acc[name] = newIconNode;
return acc return acc;
}, {}) }, {});
}) });

View File

@@ -1,29 +1,29 @@
import { eventHandler, getQuery, setResponseHeader, createError } from 'h3' import { eventHandler, getQuery, setResponseHeader, createError } from 'h3';
import iconNodes from '../../data/iconNodes' import iconNodes from '../../data/iconNodes';
import createLucideIcon from 'lucide-react/src/createLucideIcon' import createLucideIcon from 'lucide-react/src/createLucideIcon';
import { renderToString } from 'react-dom/server' import { renderToString } from 'react-dom/server';
import { createElement } from 'react' import { createElement } from 'react';
export default eventHandler((event) => { 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) { if (iconNode == null) {
const error = createError({ const error = createError({
statusCode: 404, statusCode: 404,
message: `Icon "${params.iconName}" not found`, message: `Icon "${params.iconName}" not found`,
}) });
return sendError(event, error) return sendError(event, error);
} }
const width = getQuery(event).width || undefined const width = getQuery(event).width || undefined;
const height = getQuery(event).height || undefined const height = getQuery(event).height || undefined;
const color = getQuery(event).color || undefined const color = getQuery(event).color || undefined;
const strokeWidth = getQuery(event).strokeWidth || undefined const strokeWidth = getQuery(event).strokeWidth || undefined;
const LucideIcon = createLucideIcon(params.iconName, iconNode) const LucideIcon = createLucideIcon(params.iconName, iconNode);
const svg = Buffer.from( const svg = Buffer.from(
renderToString( renderToString(
@@ -32,14 +32,13 @@ export default eventHandler((event) => {
height, height,
color: color ? `#${color}` : undefined, color: color ? `#${color}` : undefined,
strokeWidth, strokeWidth,
} }),
)) ),
).toString('utf8'); ).toString('utf8');
defaultContentType(event, 'image/svg+xml') defaultContentType(event, 'image/svg+xml');
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000') setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*') setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
return svg return svg;
});
})

View File

@@ -1,11 +1,9 @@
import { eventHandler, setResponseHeader } from 'h3' import { eventHandler, setResponseHeader } from 'h3';
import iconMetaData from '../../data/iconMetaData' import iconMetaData from '../../data/iconMetaData';
export default eventHandler((event) => { export default eventHandler((event) => {
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400') setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*') setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
return Object.fromEntries( return Object.fromEntries(Object.entries(iconMetaData).map(([name, { tags }]) => [name, tags]));
Object.entries(iconMetaData).map(([name, { tags }]) => [ name, tags ]) });
)
})

View File

@@ -1,3 +1,3 @@
export default eventHandler(() => { export default eventHandler(() => {
return { nitro: 'Is Awesome! asda' } return { nitro: 'Is Awesome! asda' };
}) });

View File

@@ -1,10 +1,10 @@
import { fileURLToPath, URL } from 'node:url' import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vitepress' import { defineConfig } from 'vitepress';
import sidebar from './sidebar'; import sidebar from './sidebar';
const title = "Lucide"; const title = 'Lucide';
const socialTitle = "Lucide Icons"; const socialTitle = 'Lucide Icons';
const description = "Beautiful & consistent icon toolkit made by the community." const description = 'Beautiful & consistent icon toolkit made by the community.';
// https://vitepress.dev/reference/site-config // https://vitepress.dev/reference/site-config
export default defineConfig({ export default defineConfig({
@@ -19,86 +19,131 @@ export default defineConfig({
{ {
find: /^.*\/VPIconAlignLeft\.vue$/, find: /^.*\/VPIconAlignLeft\.vue$/,
replacement: fileURLToPath( 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$/, find: /^.*\/VPFooter\.vue$/,
replacement: fileURLToPath( replacement: fileURLToPath(
new URL('./theme/components/overrides/VPFooter.vue', import.meta.url) new URL('./theme/components/overrides/VPFooter.vue', import.meta.url),
) ),
} },
] ],
}, },
}, },
head: [ head: [
[ 'script', { [
src: 'https://analytics.lucide.dev/js/script.js', 'script',
'data-domain': 'lucide.dev', {
defer: '' src: 'https://analytics.lucide.dev/js/script.js',
}], 'data-domain': 'lucide.dev',
[ 'meta', { defer: '',
property:"og:locale", },
content:"en_US" ],
}], [
[ 'meta', { 'meta',
property:"og:type", {
content:"website" property: 'og:locale',
}], content: 'en_US',
[ 'meta', { },
property:"og:site_name", ],
content: title, [
}], 'meta',
[ 'meta', { {
property:"og:title", property: 'og:type',
content: socialTitle, content: 'website',
}], },
[ 'meta', { ],
property:"og:description", [
content: description 'meta',
}], {
[ 'meta', { property: 'og:site_name',
property:"og:url", content: title,
content:"https://lucide.dev" },
}], ],
[ 'meta', { [
property:"og:image", 'meta',
content: "https://lucide.dev/og.png" {
}], property: 'og:title',
[ 'meta', { content: socialTitle,
property:"og:image:width", },
content:"1200" ],
}], [
[ 'meta', { 'meta',
property:"og:image:height", {
content:"630" property: 'og:description',
}], content: description,
[ 'meta', { },
property:"og:image:type", ],
content:"image/png" [
}], 'meta',
[ 'meta', { {
property:"twitter:card", property: 'og:url',
content:"summary_large_image" content: 'https://lucide.dev',
}], },
[ 'meta', { ],
property:"twitter:title", [
content: socialTitle, 'meta',
}], {
[ 'meta', { property: 'og:image',
property:"twitter:description", content: 'https://lucide.dev/og.png',
content: description },
}], ],
[ 'meta', { [
property:"twitter:image", 'meta',
content:"https://lucide.dev/og.png" {
}], 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: { themeConfig: {
// https://vitepress.dev/reference/default-theme-config // https://vitepress.dev/reference/default-theme-config
logo: { logo: {
light: '/logo.light.svg', light: '/logo.light.svg',
dark: '/logo.dark.svg' dark: '/logo.dark.svg',
}, },
nav: [ nav: [
{ text: 'Icons', link: '/icons/' }, { text: 'Icons', link: '/icons/' },
@@ -110,21 +155,21 @@ export default defineConfig({
sidebar, sidebar,
socialLinks: [ socialLinks: [
{ icon: 'github', link: 'https://github.com/lucide-icons/lucide' }, { icon: 'github', link: 'https://github.com/lucide-icons/lucide' },
{ icon: 'discord', link: 'https://discord.gg/EH6nSts' } { icon: 'discord', link: 'https://discord.gg/EH6nSts' },
], ],
footer: { footer: {
message: 'Released under the ISC License.', message: 'Released under the ISC License.',
copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors` copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`,
}, },
editLink: { editLink: {
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path' pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path',
}, },
carbonAds: { carbonAds: {
code: 'CWYIC53U', code: 'CWYIC53U',
placement: 'lucidedev' placement: 'lucidedev',
} },
}, },
sitemap: { sitemap: {
hostname: 'https://lucide.dev/' hostname: 'https://lucide.dev/',
} },
}) });

View File

@@ -3,87 +3,171 @@
"order": 0, "order": 0,
"icon": "js", "icon": "js",
"shields": [ "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": { "lucide-react": {
"order": 1, "order": 1,
"icon": "react", "icon": "react",
"shields": [ "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": { "lucide-vue": {
"order": 2, "order": 2,
"icon": "vue", "icon": "vue",
"shields": [ "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": { "lucide-vue-next": {
"order": 3, "order": 3,
"icon": "vue-next", "icon": "vue-next",
"shields": [ "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": { "lucide-svelte": {
"order": 4, "order": 4,
"icon": "svelte", "icon": "svelte",
"shields": [ "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": { "lucide-solid": {
"order": 4, "order": 4,
"icon": "solid", "icon": "solid",
"shields": [ "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": { "lucide-preact": {
"order": 5, "order": 5,
"icon": "preact", "icon": "preact",
"shields": [ "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": { "lucide-react-native": {
"order": 6, "order": 6,
"icon": "react-native", "icon": "react-native",
"shields": [ "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": { "lucide-angular": {
"order": 7, "order": 7,
"icon": "angular", "icon": "angular",
"shields": [ "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": { "lucide-static": {
"order": 8, "order": 8,
"icon": "svg", "icon": "svg",
"shields": [ "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": { "lucide-flutter": {
"order": 9, "order": 9,
"icon": "flutter", "icon": "flutter",
"shields": [ "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"
}
] ]
} }
} }

View File

@@ -17,21 +17,62 @@ const Backdrop = ({ src, color = 'red', backdropString }: BackdropProps): JSX.El
patternUnits="userSpaceOnUse" patternUnits="userSpaceOnUse"
patternTransform="rotate(45 50 50)" patternTransform="rotate(45 50 50)"
> >
<line stroke={color} strokeWidth={0.1} y2={1} /> <line
<line stroke={color} strokeWidth={0.1} y2={1} /> stroke={color}
strokeWidth={0.1}
y2={1}
/>
<line
stroke={color}
strokeWidth={0.1}
y2={1}
/>
</pattern> </pattern>
</defs> </defs>
<mask id="svg-preview-backdrop-mask-outline" maskUnits="userSpaceOnUse"> <mask
<g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} /> id="svg-preview-backdrop-mask-outline"
<g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} /> maskUnits="userSpaceOnUse"
>
<g
stroke="#fff"
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
<g
dangerouslySetInnerHTML={{ __html: src }}
strokeWidth={2.05}
/>
</mask> </mask>
<mask id="svg-preview-backdrop-mask-fill" maskUnits="userSpaceOnUse"> <mask
<g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} /> id="svg-preview-backdrop-mask-fill"
<g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} /> maskUnits="userSpaceOnUse"
<g strokeWidth={1.75} dangerouslySetInnerHTML={{ __html: backdropString }} /> >
<g
stroke="#fff"
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
<g
dangerouslySetInnerHTML={{ __html: src }}
strokeWidth={2.05}
/>
<g
strokeWidth={1.75}
dangerouslySetInnerHTML={{ __html: backdropString }}
/>
</mask> </mask>
<g strokeWidth={2.25} stroke="url(#pattern)" mask={'url(#svg-preview-backdrop-mask-outline)'}> <g
<rect x="0" y="0" width="24" height="24" fill="url(#pattern)" opacity={0.5} stroke="none" /> 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> </g>
<rect <rect
x="0" x="0"

View File

@@ -10,7 +10,11 @@ const Grid = ({
strokeWidth: number; strokeWidth: number;
radius: number; radius: number;
} & PathProps<'stroke', 'strokeWidth'>) => ( } & PathProps<'stroke', 'strokeWidth'>) => (
<g className="svg-preview-grid-group" strokeLinecap="butt" {...props}> <g
className="svg-preview-grid-group"
strokeLinecap="butt"
{...props}
>
<rect <rect
className="svg-preview-grid-rect" className="svg-preview-grid-rect"
width={24 - props.strokeWidth} width={24 - props.strokeWidth}
@@ -44,15 +48,21 @@ const Shadow = ({
paths: Path[]; paths: Path[];
} & PathProps<'stroke' | 'strokeWidth' | 'strokeOpacity', 'd'>) => { } & PathProps<'stroke' | 'strokeWidth' | 'strokeOpacity', 'd'>) => {
const groupedPaths = Object.entries( const groupedPaths = Object.entries(
paths.reduce((groups, val) => { paths.reduce(
const key = val.c.id; (groups, val) => {
groups[key] = [...(groups[key] || []), val]; const key = val.c.id;
return groups; groups[key] = [...(groups[key] || []), val];
}, {} as Record<number, Path[]>) return groups;
},
{} as Record<number, Path[]>,
),
); );
return ( return (
<> <>
<g className="svg-preview-shadow-mask-group" {...props}> <g
className="svg-preview-shadow-mask-group"
{...props}
>
{groupedPaths.map(([id, paths]) => ( {groupedPaths.map(([id, paths]) => (
<mask <mask
id={`svg-preview-shadow-mask-${id}`} id={`svg-preview-shadow-mask-${id}`}
@@ -61,7 +71,15 @@ const Shadow = ({
strokeWidth={props.strokeWidth} strokeWidth={props.strokeWidth}
stroke="#000" 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 <path
d={paths d={paths
.flatMap(({ prev, next }) => [ .flatMap(({ prev, next }) => [
@@ -74,9 +92,16 @@ const Shadow = ({
</mask> </mask>
))} ))}
</g> </g>
<g className="svg-preview-shadow-group" {...props}> <g
className="svg-preview-shadow-group"
{...props}
>
{paths.map(({ d, c: { id } }, i) => ( {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 <path
d={paths d={paths
@@ -94,9 +119,16 @@ const ColoredPath = ({
paths, paths,
...props ...props
}: { paths: Path[]; colors: string[] } & PathProps<never, 'd' | 'stroke'>) => ( }: { 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) => ( {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> </g>
); );
@@ -138,7 +170,15 @@ const ControlPath = ({
key={i} key={i}
maskUnits="userSpaceOnUse" 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${prev.x} ${prev.y}h.01`} />
<path d={`M${next.x} ${next.y}h.01`} /> <path d={`M${next.x} ${next.y}h.01`} />
</mask> </mask>
@@ -146,7 +186,10 @@ const ControlPath = ({
); );
})} })}
</g> </g>
<g className="svg-preview-control-path-group" {...props}> <g
className="svg-preview-control-path-group"
{...props}
>
{controlPaths.map(({ d, showMarker }, i) => ( {controlPaths.map(({ d, showMarker }, i) => (
<path <path
key={i} key={i}
@@ -155,18 +198,33 @@ const ControlPath = ({
/> />
))} ))}
</g> </g>
<g className="svg-preview-control-path-marker-group" {...props}> <g
className="svg-preview-control-path-marker-group"
{...props}
>
<path <path
d={controlPaths d={controlPaths
.flatMap(({ prev, next, showMarker }) => .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('')} .join('')}
/> />
{controlPaths.map(({ d, prev, next, startMarker, endMarker }, i) => ( {controlPaths.map(({ d, prev, next, startMarker, endMarker }, i) => (
<React.Fragment key={i}> <React.Fragment key={i}>
{startMarker && <circle cx={prev.x} cy={prev.y} r={pointSize / 2} />} {startMarker && (
{endMarker && <circle cx={next.x} cy={next.y} r={pointSize / 2} />} <circle
cx={prev.x}
cy={prev.y}
r={pointSize / 2}
/>
)}
{endMarker && (
<circle
cx={next.x}
cy={next.y}
r={pointSize / 2}
/>
)}
</React.Fragment> </React.Fragment>
))} ))}
</g> </g>
@@ -182,15 +240,16 @@ const Radii = ({
any any
>) => { >) => {
return ( return (
<g className="svg-preview-radii-group" {...props}> <g
className="svg-preview-radii-group"
{...props}
>
{paths.map( {paths.map(
({ c, prev, next, circle }, i) => ({ c, prev, next, circle }, i) =>
circle && ( circle && (
<React.Fragment key={i}> <React.Fragment key={i}>
{c.name !== "circle" && ( {c.name !== 'circle' && (
<path <path d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`} />
d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`}
/>
)} )}
<circle <circle
cy={circle.y} cy={circle.y}
@@ -200,7 +259,7 @@ const Radii = ({
stroke={ stroke={
(Math.round(circle.x * 100) / 100) % 1 !== 0 || (Math.round(circle.x * 100) / 100) % 1 !== 0 ||
(Math.round(circle.y * 100) / 100) % 1 !== 0 (Math.round(circle.y * 100) / 100) % 1 !== 0
? "red" ? 'red'
: undefined : undefined
} }
/> />
@@ -208,11 +267,7 @@ const Radii = ({
cy={circle.y} cy={circle.y}
cx={circle.x} cx={circle.x}
r={circle.r} r={circle.r}
stroke={ stroke={(Math.round(circle.r * 1000) / 1000) % 1 !== 0 ? 'red' : undefined}
(Math.round(circle.r * 1000) / 1000) % 1 !== 0
? "red"
: undefined
}
/> />
</React.Fragment> </React.Fragment>
), ),
@@ -230,13 +285,28 @@ const Handles = ({
>) => { >) => {
console.log(paths); console.log(paths);
return ( return (
<g className="svg-preview-handles-group" {...props}> <g
className="svg-preview-handles-group"
{...props}
>
{paths.map(({ c, prev, next, cp1, cp2 }) => ( {paths.map(({ c, prev, next, cp1, cp2 }) => (
<> <>
{cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />} {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 && <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> </g>
@@ -280,9 +350,27 @@ const SvgPreview = React.forwardRef<
{...props} {...props}
> >
<style>{darkModeCss}</style> <style>{darkModeCss}</style>
{showGrid && <Grid strokeWidth={0.1} stroke="#777" strokeOpacity={0.3} radius={1} />} {showGrid && (
<Shadow paths={paths} strokeWidth={4} stroke="#777" radius={1} strokeOpacity={0.15} /> <Grid
<Handles paths={paths} strokeWidth={0.12} stroke="#777" strokeOpacity={0.6} /> 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 <ColoredPath
paths={paths} paths={paths}
colors={[ colors={[
@@ -307,8 +395,19 @@ const SvgPreview = React.forwardRef<
stroke="#777" stroke="#777"
strokeOpacity={0.3} strokeOpacity={0.3}
/> />
<ControlPath radius={1} paths={paths} pointSize={1} stroke="#fff" strokeWidth={0.125} /> <ControlPath
<Handles paths={paths} strokeWidth={0.12} stroke="#FFF" strokeOpacity={0.3} /> radius={1}
paths={paths}
pointSize={1}
stroke="#fff"
strokeWidth={0.125}
/>
<Handles
paths={paths}
strokeWidth={0.12}
stroke="#FFF"
strokeOpacity={0.3}
/>
{children} {children}
</svg> </svg>
); );

View File

@@ -16,7 +16,7 @@ export type Path = {
export type PathProps< export type PathProps<
RequiredProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>, 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>> & > = Required<Pick<React.SVGProps<SVGElement & SVGRectElement & SVGCircleElement>, RequiredProps>> &
Omit< Omit<
React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>, React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>,

View File

@@ -51,7 +51,7 @@ export const getCommands = (src: string) =>
getNodes(src) getNodes(src)
.map(convertToPathNode) .map(convertToPathNode)
.flatMap(({ d, name }, idx) => .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) => { export const getPaths = (src: string) => {
@@ -60,10 +60,10 @@ export const getPaths = (src: string) => {
let prev: Point | undefined = undefined; let prev: Point | undefined = undefined;
let start: Point | undefined = undefined; let start: Point | undefined = undefined;
const addPath = ( const addPath = (
c: typeof commands[number], c: (typeof commands)[number],
next: Point, next: Point,
d?: string, d?: string,
extras?: { circle?: Path['circle']; cp1?: Path['cp1']; cp2?: Path['cp2'] } extras?: { circle?: Path['circle']; cp1?: Path['cp1']; cp2?: Path['cp2'] },
) => { ) => {
assert(prev); assert(prev);
paths.push({ paths.push({
@@ -153,7 +153,7 @@ export const getPaths = (src: string) => {
{ {
cp1: { x: prev.x - reflectedCp1.x, y: prev.y - reflectedCp1.y }, cp1: { x: prev.x - reflectedCp1.x, y: prev.y - reflectedCp1.y },
cp2: { x: c.x2, y: c.y2 }, cp2: { x: c.x2, y: c.y2 },
} },
); );
break; break;
} }
@@ -169,7 +169,7 @@ export const getPaths = (src: string) => {
assert(prev); assert(prev);
const backTrackCP = ( const backTrackCP = (
index: number, index: number,
currentPoint: { x: number; y: number } currentPoint: { x: number; y: number },
): { x: number; y: number } => { ): { x: number; y: number } => {
const previousCommand = commands[index - 1]; const previousCommand = commands[index - 1];
if (!previousCommand) { if (!previousCommand) {
@@ -211,7 +211,7 @@ export const getPaths = (src: string) => {
{ {
cp1: { x: prevCP.x, y: prevCP.y }, cp1: { x: prevCP.x, y: prevCP.y },
cp2: { x: prevCP.x, y: prevCP.y }, cp2: { x: prevCP.x, y: prevCP.y },
} },
); );
break; break;
} }
@@ -226,13 +226,13 @@ export const getPaths = (src: string) => {
c.lArcFlag, c.lArcFlag,
c.sweepFlag, c.sweepFlag,
c.x, c.x,
c.y c.y,
); );
addPath( addPath(
c, c,
c, c,
`M ${prev.x} ${prev.y} A${c.rX} ${c.rY} ${c.xRot} ${c.lArcFlag} ${c.sweepFlag} ${c.x} ${c.y}`, `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; break;
} }
@@ -253,7 +253,7 @@ export const arcEllipseCenter = (
fa: number, fa: number,
fs: number, fs: number,
x2: number, x2: number,
y2: number y2: number,
) => { ) => {
const phi = (a * Math.PI) / 180; const phi = (a * Math.PI) / 180;
@@ -280,7 +280,7 @@ export const arcEllipseCenter = (
sign * sign *
Math.sqrt( Math.sqrt(
Math.max(rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p, 0) / 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]; const V2 = [(rx * y1p) / ry, (-ry * x1p) / rx];

View File

@@ -1,28 +1,34 @@
import fs from "fs"; import fs from 'fs';
import path from "path"; import path from 'path';
import {Category, IconEntity} from "../theme/types"; import { Category, IconEntity } from '../theme/types';
const directory = path.join(process.cwd(), "../categories"); const directory = path.join(process.cwd(), '../categories');
export function getAllCategoryFiles(): Category[] { export function getAllCategoryFiles(): Category[] {
const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json'); const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json');
return fileNames.map((fileName) => { return fileNames.map((fileName) => {
const name = path.basename(fileName, '.json') const name = path.basename(fileName, '.json');
const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8') const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8');
const parsedFileContent = JSON.parse(fileContent) const parsedFileContent = JSON.parse(fileContent);
return { return {
name, name,
title: parsedFileContent.title, 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) => ({ return categories.map((category) => ({
...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,
),
}));
} }

View File

@@ -1,24 +1,18 @@
import { import { bundledLanguages, type ThemeRegistration } from 'shikiji';
bundledLanguages, import { getHighlighter } from 'shikiji';
type ThemeRegistration
} from 'shikiji'
import {
getHighlighter,
} from 'shikiji'
type CodeExampleType = { type CodeExampleType = {
title: string, title: string;
language: string, language: string;
code: string, code: string;
}[] }[];
const getIconCodes = (): CodeExampleType => { const getIconCodes = (): CodeExampleType => {
return [ return [
{ {
language: 'html', language: 'html',
title: 'HTML', title: 'HTML',
code: `<i data-lucide="Name"></i>` code: `<i data-lucide="Name"></i>`,
}, },
{ {
language: 'tsx', language: 'tsx',
@@ -109,36 +103,37 @@ import { LucideAngularModule, PascalCase } from 'lucide-angular';
<div class="icon-Name"></div> <div class="icon-Name"></div>
`, `,
} },
] ];
} };
export type ThemeOptions = export type ThemeOptions =
| ThemeRegistration | ThemeRegistration
| { light: ThemeRegistration; dark: ThemeRegistration } | { light: ThemeRegistration; dark: ThemeRegistration };
const highLightCode = async (code: string, lang: string, active?: boolean) => { const highLightCode = async (code: string, lang: string, active?: boolean) => {
const highlighter = await getHighlighter({ const highlighter = await getHighlighter({
themes: ['github-light', 'github-dark'], themes: ['github-light', 'github-dark'],
langs: Object.keys(bundledLanguages) langs: Object.keys(bundledLanguages),
}) });
const highlightedCode = highlighter.codeToHtml(code, { const highlightedCode = highlighter
lang, .codeToHtml(code, {
themes: { lang,
light: 'github-light', themes: {
dark: 'github-dark' light: 'github-light',
}, dark: 'github-dark',
defaultColor: false },
}).replace('shiki-themes', 'shiki-themes vp-code') defaultColor: false,
})
.replace('shiki-themes', 'shiki-themes vp-code');
return `<div class="language-${lang} ${active ? 'active' : ''}"> return `<div class="language-${lang} ${active ? 'active' : ''}">
<button title="Copy Code" class="copy"></button> <button title="Copy Code" class="copy"></button>
<span class="lang">${lang}</span> <span class="lang">${lang}</span>
${highlightedCode} ${highlightedCode}
</div>` </div>`;
} };
export default async function createCodeExamples() { export default async function createCodeExamples() {
const codes = getIconCodes(); const codes = getIconCodes();
@@ -153,7 +148,7 @@ export default async function createCodeExamples() {
language: language, language: language,
code: codeString, code: codeString,
}; };
}) });
return Promise.all(codeExamplePromises); return Promise.all(codeExamplePromises);
} }

View File

@@ -1,38 +1,42 @@
import { promises as fs, constants } from 'fs'; import { promises as fs, constants } from 'fs';
import path from 'path'; import path from 'path';
import yaml from 'js-yaml' import yaml from 'js-yaml';
import { PackageItem } from '../theme/types'; 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 fetchPackages = async (): Promise<PackageItem[]> => {
const docsDir = path.resolve(process.cwd(), '../packages'); 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 packageJsons = await Promise.all(
const filePath = path.resolve(directory, filename) fileNames.map(async ({ filename, directory }) => {
const fileStat = await fs.lstat(filePath); 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') const jsonFilePath = path.resolve(filePath, 'package.json');
if (await fileExist(jsonFilePath)) { if (await fileExist(jsonFilePath)) {
return JSON.parse( return JSON.parse(await fs.readFile(jsonFilePath, 'utf-8'));
await fs.readFile(jsonFilePath, 'utf-8') }
)
}
const ymlFilePath = path.resolve(filePath, 'pubspec.yaml') const ymlFilePath = path.resolve(filePath, 'pubspec.yaml');
if(await fileExist(ymlFilePath)) { if (await fileExist(ymlFilePath)) {
return yaml.load( return yaml.load(await fs.readFile(ymlFilePath, 'utf-8'));
await fs.readFile(ymlFilePath, 'utf-8') }
);
}
return null return null;
})) }),
);
return packageJsons return packageJsons;
} };
export default fetchPackages; export default fetchPackages;

View File

@@ -1,17 +1,15 @@
export type IconContent = [icon: string, src:string]; export type IconContent = [icon: string, src: string];
async function generateZip(icons: IconContent[]) { async function generateZip(icons: IconContent[]) {
const JSZip = (await import('jszip')).default const JSZip = (await import('jszip')).default;
const zip = new JSZip(); const zip = new JSZip();
const addingZipPromises = icons.map(([name, src]) => const addingZipPromises = icons.map(([name, src]) => zip.file(`${name}.svg`, src));
zip.file(`${name}.svg`, src),
);
await Promise.all(addingZipPromises) await Promise.all(addingZipPromises);
return zip.generateAsync({ type: 'blob' }); return zip.generateAsync({ type: 'blob' });
} }
export default generateZip export default generateZip;

View File

@@ -1,17 +1,15 @@
import { createLucideIcon } from "lucide-react/src/lucide-react" import { createLucideIcon } from 'lucide-react/src/lucide-react';
import { type LucideProps, type IconNode } from "lucide-react/src/createLucideIcon" import { type LucideProps, type IconNode } from 'lucide-react/src/createLucideIcon';
import { IconEntity } from "../theme/types" import { IconEntity } from '../theme/types';
import { renderToStaticMarkup } from 'react-dom/server'; import { renderToStaticMarkup } from 'react-dom/server';
import { IconContent } from "./generateZip"; import { IconContent } from './generateZip';
const getFallbackZip = (icons: IconEntity[], params: LucideProps) => { const getFallbackZip = (icons: IconEntity[], params: LucideProps) => {
return icons return icons.map<IconContent>((icon) => {
.map<IconContent>((icon) => { const Icon = createLucideIcon(icon.name, icon.iconNode as IconNode);
const Icon = createLucideIcon(icon.name, icon.iconNode as IconNode) const src = renderToStaticMarkup(<Icon {...params} />);
const src = renderToStaticMarkup(<Icon {...params} />) return [icon.name, src];
return [icon.name, src] });
}) };
}
export default getFallbackZip;
export default getFallbackZip

View File

@@ -1,34 +1,34 @@
import fs from "fs"; import fs from 'fs';
import path from "path"; import path from 'path';
import { IconNodeWithKeys } from "../theme/types"; import { IconNodeWithKeys } from '../theme/types';
import iconNodes from '../data/iconNodes' import iconNodes from '../data/iconNodes';
import releaseMeta from "../data/releaseMetaData.json"; import releaseMeta from '../data/releaseMetaData.json';
const DATE_OF_FORK = '2020-06-08T16:39:52+0100'; 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 { export interface GetDataOptions {
withChildKeys?: boolean withChildKeys?: boolean;
} }
export async function getData(name: string) { export async function getData(name: string) {
const jsonPath = path.join(directory, `${name}.json`); 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 { tags, categories, contributors } = JSON.parse(jsonContent);
const iconNode = iconNodes[name] const iconNode = iconNodes[name];
const releaseData = releaseMeta?.[name] ?? { const releaseData = releaseMeta?.[name] ?? {
"createdRelease": { createdRelease: {
"version": "0.0.0", version: '0.0.0',
"date": DATE_OF_FORK date: DATE_OF_FORK,
}, },
"changedRelease": { changedRelease: {
"version": "0.0.0", version: '0.0.0',
"date": DATE_OF_FORK date: DATE_OF_FORK,
} },
} };
return { return {
name, name,
@@ -36,11 +36,11 @@ export async function getData(name: string) {
categories, categories,
iconNode, iconNode,
contributors, 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); const names = Object.keys(iconNodes);
return Promise.all(names.map((name) => getData(name))); return Promise.all(names.map((name) => getData(name)));

View File

@@ -1,31 +1,31 @@
import { DefaultTheme, UserConfig } from "vitepress" import { DefaultTheme, UserConfig } from 'vitepress';
const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = { const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
'guide':[ guide: [
{ {
text: 'Introduction', text: 'Introduction',
items: [ items: [
{ text: 'What is lucide?', link: '/guide/' }, { text: 'What is lucide?', link: '/guide/' },
{ text: 'Installation', link: '/guide/installation' }, { text: 'Installation', link: '/guide/installation' },
{ text: 'Comparison', link: '/guide/comparison' } { text: 'Comparison', link: '/guide/comparison' },
] ],
}, },
{ {
text: 'Basics', text: 'Basics',
items: [ items: [
{ {
text: 'Color', text: 'Color',
link: '/guide/basics/color' link: '/guide/basics/color',
}, },
{ {
text: 'Sizing', text: 'Sizing',
link: '/guide/basics/sizing' link: '/guide/basics/sizing',
}, },
{ {
text: 'Stroke width', text: 'Stroke width',
link: '/guide/basics/stroke-width' link: '/guide/basics/stroke-width',
}, },
] ],
}, },
// TODO: Add this section // TODO: Add this section
{ {
@@ -37,14 +37,14 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
// }, // },
{ {
text: 'Global styling', text: 'Global styling',
link: '/guide/advanced/global-styling' link: '/guide/advanced/global-styling',
}, },
// { // {
// text: 'Animations', // text: 'Animations',
// }, // },
{ {
text: 'Filled icons', text: 'Filled icons',
link: '/guide/advanced/filled-icons' link: '/guide/advanced/filled-icons',
}, },
// { // {
// text: 'Combining icons', // text: 'Combining icons',
@@ -55,75 +55,73 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
// { // {
// text: 'Auto importing' // text: 'Auto importing'
// }, // },
] ],
}, },
{ {
text: 'Packages', text: 'Packages',
items: [ items: [
{ {
text: 'Lucide', text: 'Lucide',
link: '/guide/packages/lucide' link: '/guide/packages/lucide',
}, },
{ {
text: 'Lucide React', text: 'Lucide React',
link: '/guide/packages/lucide-react' link: '/guide/packages/lucide-react',
}, },
{ {
text: 'Lucide React Native', text: 'Lucide React Native',
link: '/guide/packages/lucide-react-native' link: '/guide/packages/lucide-react-native',
}, },
{ {
text: 'Lucide Vue', text: 'Lucide Vue',
link: '/guide/packages/lucide-vue-next' link: '/guide/packages/lucide-vue-next',
}, },
{ {
text: 'Lucide Svelte', text: 'Lucide Svelte',
link: '/guide/packages/lucide-svelte' link: '/guide/packages/lucide-svelte',
}, },
{ {
text: 'Lucide Solid', text: 'Lucide Solid',
link: '/guide/packages/lucide-solid' link: '/guide/packages/lucide-solid',
}, },
{ {
text: 'Lucide Preact', text: 'Lucide Preact',
link: '/guide/packages/lucide-preact' link: '/guide/packages/lucide-preact',
}, },
{ {
text: 'Lucide Angular', text: 'Lucide Angular',
link: '/guide/packages/lucide-angular' link: '/guide/packages/lucide-angular',
}, },
{ {
text: 'Lucide Static', text: 'Lucide Static',
link: '/guide/packages/lucide-static' link: '/guide/packages/lucide-static',
}, },
] ],
}, },
{ {
text: 'Contributing', text: 'Contributing',
items: [ items: [
{ {
text: 'Icon Design Principles', text: 'Icon Design Principles',
link: '/guide/design/icon-design-guide' link: '/guide/design/icon-design-guide',
}, },
{ {
text: 'Designing in Illustrator', text: 'Designing in Illustrator',
link: '/guide/design/illustrator-guide' link: '/guide/design/illustrator-guide',
}, },
{ {
text: 'Designing in InkScape', text: 'Designing in InkScape',
link: '/guide/design/inkscape-guide' link: '/guide/design/inkscape-guide',
}, },
{ {
text: 'Designing in Figma', 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 // This should be here to keep the sidebar shown on the icons page
'icons': [ icons: [{ text: '', link: '/' }],
{ text: '', link: '/' }, };
],
}
export default sidebar export default sidebar;

View File

@@ -1,16 +1,18 @@
export default { export default {
async load() { async load() {
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest').then(res => { const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest')
if (res.ok) { .then((res) => {
const releaseData = res.json() as Promise<{ tag_name: string }> if (res.ok) {
const releaseData = res.json() as Promise<{ tag_name: string }>;
return releaseData return releaseData;
} }
return null return null;
}).then(res => res.tag_name) })
.then((res) => res.tag_name);
return { return {
version version,
} };
} },
} };

View File

@@ -1,16 +1,17 @@
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 { export default {
async load() { 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 { return {
icons: randomIcons, icons: randomIcons,
iconsCount: icons.length, iconsCount: icons.length,
} };
} },
} };

View File

@@ -47,7 +47,7 @@ export default {
logo: '/framework-logos/flutter.svg', logo: '/framework-logos/flutter.svg',
label: 'Lucide documentation for Flutter', label: 'Lucide documentation for Flutter',
}, },
] ],
} };
} },
} };

View File

@@ -1,16 +1,15 @@
import { getAllData } from '../../../lib/icons'; import { getAllData } from '../../../lib/icons';
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories'; import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
import iconsMetaData from '../../../data/iconMetaData' import iconsMetaData from '../../../data/iconMetaData';
export default { export default {
async load() { async load() {
let categories = getAllCategoryFiles() let categories = getAllCategoryFiles();
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData)) categories = mapCategoryIconCount(categories, Object.values(iconsMetaData));
return { return {
categories, categories,
} };
} },
} };

View File

@@ -12,7 +12,7 @@
.confetti-button:before, .confetti-button:before,
.confetti-button:after { .confetti-button:after {
position: absolute; position: absolute;
content: ""; content: '';
display: block; display: block;
width: 140%; width: 140%;
max-width: 160px; max-width: 160px;
@@ -41,8 +41,16 @@
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%), radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%), radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%); radial-gradient(circle, var(--confetti-color) 20%, transparent 20%);
background-size: 10% 10%, 20% 20%, 15% 15%, 20% 20%, 18% 18%, 10% 10%, 15% 15%, background-size:
10% 10%, 18% 18%; 10% 10%,
20% 20%,
15% 15%,
20% 20%,
18% 18%,
10% 10%,
15% 15%,
10% 10%,
18% 18%;
} }
.confetti-button:after { .confetti-button:after {
@@ -55,7 +63,14 @@
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%), radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%), radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%); radial-gradient(circle, var(--confetti-color) 20%, transparent 20%);
background-size: 15% 15%, 20% 20%, 18% 18%, 20% 20%, 15% 15%, 10% 10%, 20% 20%; background-size:
15% 15%,
20% 20%,
18% 18%,
20% 20%,
15% 15%,
10% 10%,
20% 20%;
} }
.confetti-button.animate:before { .confetti-button.animate:before {
@@ -70,35 +85,89 @@
@keyframes topBubbles { @keyframes topBubbles {
0% { 0% {
color: rgb(var(--text-color) / 0); color: rgb(var(--text-color) / 0);
background-position: 5% 90%, 10% 90%, 10% 90%, 15% 90%, 25% 90%, 25% 90%, background-position:
40% 90%, 55% 90%, 70% 90%; 5% 90%,
10% 90%,
10% 90%,
15% 90%,
25% 90%,
25% 90%,
40% 90%,
55% 90%,
70% 90%;
} }
30% { 30% {
color: rgb(var(--text-color) / 1); color: rgb(var(--text-color) / 1);
} }
50% { 50% {
background-position: 0% 80%, 0% 20%, 10% 40%, 20% 0%, 30% 30%, 22% 50%, background-position:
50% 50%, 65% 20%, 90% 30%; 0% 80%,
0% 20%,
10% 40%,
20% 0%,
30% 30%,
22% 50%,
50% 50%,
65% 20%,
90% 30%;
} }
100% { 100% {
background-position: 0% 70%, 0% 10%, 10% 30%, 20% -10%, 30% 20%, 22% 40%, background-position:
50% 40%, 65% 10%, 90% 20%; 0% 70%,
background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%; 0% 10%,
10% 30%,
20% -10%,
30% 20%,
22% 40%,
50% 40%,
65% 10%,
90% 20%;
background-size:
0% 0%,
0% 0%,
0% 0%,
0% 0%,
0% 0%,
0% 0%;
color: rgb(var(--text-color) / 0); color: rgb(var(--text-color) / 0);
} }
} }
@keyframes bottomBubbles { @keyframes bottomBubbles {
0% { 0% {
background-position: 10% -10%, 30% 10%, 55% -10%, 70% -10%, 85% -10%, background-position:
70% -10%, 70% 0%; 10% -10%,
30% 10%,
55% -10%,
70% -10%,
85% -10%,
70% -10%,
70% 0%;
} }
50% { 50% {
background-position: 0% 80%, 20% 80%, 45% 60%, 60% 100%, 75% 70%, 95% 60%, background-position:
0% 80%,
20% 80%,
45% 60%,
60% 100%,
75% 70%,
95% 60%,
105% 0%; 105% 0%;
} }
100% { 100% {
background-position: 0% 90%, 20% 90%, 45% 70%, 60% 110%, 75% 80%, 95% 70%, background-position:
0% 90%,
20% 90%,
45% 70%,
60% 110%,
75% 80%,
95% 70%,
110% 10%; 110% 10%;
background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%; background-size:
0% 0%,
0% 0%,
0% 0%,
0% 0%,
0% 0%,
0% 0%;
} }
} }

View File

@@ -1,21 +1,22 @@
import packageData from '../../../data/packageData.json'; import packageData from '../../../data/packageData.json';
import thirdPartyPackages from '../../../data/packageData.thirdParty.json'; import thirdPartyPackages from '../../../data/packageData.thirdParty.json';
import fetchPackages from "../../../lib/fetchPackages"; import fetchPackages from '../../../lib/fetchPackages';
export default { export default {
async load() { async load() {
const packages = await fetchPackages(); const packages = await fetchPackages();
return { return {
packages: packages packages: packages
.filter(p => p.name in packageData) .filter((p) => p.name in packageData)
.map((pData) => ({ .map((pData) => ({
...pData, ...pData,
...packageData[pData.name], ...packageData[pData.name],
documentation: `/guide/packages/${pData.name}`, documentation: `/guide/packages/${pData.name}`,
source: `https://github.com/lucide-icons/lucide/tree/main/packages/${pData.name}`, source: `https://github.com/lucide-icons/lucide/tree/main/packages/${pData.name}`,
icon: `/framework-logos/${packageData[pData.name].icon}.svg`, icon: `/framework-logos/${packageData[pData.name].icon}.svg`,
})).sort((a, b) => a.order - b.order), }))
.sort((a, b) => a.order - b.order),
thirdPartyPackages, thirdPartyPackages,
}; };
} },
} };

View File

@@ -1,69 +1,68 @@
import { onMounted, onUpdated, onUnmounted } from 'vue'; import { onMounted, onUpdated, onUnmounted } from 'vue';
import { throttleAndDebounce } from 'vitepress/dist/client/theme-default/support/utils' import { throttleAndDebounce } from 'vitepress/dist/client/theme-default/support/utils';
/* /*
* This file is compied and adjusted from vitepress/dist/client/theme-default/composables/useActiveAnchor.ts * This file is compied and adjusted from vitepress/dist/client/theme-default/composables/useActiveAnchor.ts
*/ */
export function useActiveAnchor(container, marker) { export function useActiveAnchor(container, marker) {
const onScroll = throttleAndDebounce(setActiveLink, 100); const onScroll = throttleAndDebounce(setActiveLink, 100);
let prevActiveLink = null; let prevActiveLink = null;
onMounted(() => { onMounted(() => {
requestAnimationFrame(setActiveLink); requestAnimationFrame(setActiveLink);
window.addEventListener('scroll', onScroll); window.addEventListener('scroll', onScroll);
}); });
onUpdated(() => { onUpdated(() => {
// sidebar update means a route change // sidebar update means a route change
activateLink(location.hash); activateLink(location.hash);
}); });
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('scroll', onScroll); window.removeEventListener('scroll', onScroll);
}); });
function setActiveLink() { function setActiveLink() {
const links = [].slice.call(container.value.querySelectorAll('.outline-link')); const links = [].slice.call(container.value.querySelectorAll('.outline-link'));
const anchors = [].slice const anchors = [].slice
.call(document.querySelectorAll('.content .header-anchor')) .call(document.querySelectorAll('.content .header-anchor'))
.filter((anchor) => { .filter((anchor) => {
return links.some((link) => { return links.some((link) => {
return link.hash === anchor.hash && anchor.offsetParent !== null; return link.hash === anchor.hash && anchor.offsetParent !== null;
}); });
}); });
const scrollY = window.scrollY; const scrollY = window.scrollY;
const innerHeight = window.innerHeight; const innerHeight = window.innerHeight;
const offsetHeight = document.body.offsetHeight; const offsetHeight = document.body.offsetHeight;
const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1; const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1;
// page bottom - highlight last one // page bottom - highlight last one
if (anchors.length && isBottom) { if (anchors.length && isBottom) {
activateLink(anchors[anchors.length - 1].hash); activateLink(anchors[anchors.length - 1].hash);
return; return;
} }
for (let i = 0; i < anchors.length; i++) { for (let i = 0; i < anchors.length; i++) {
const anchor = anchors[i]; const anchor = anchors[i];
const nextAnchor = anchors[i + 1]; const nextAnchor = anchors[i + 1];
const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor); const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor);
if (isActive) { if (isActive) {
activateLink(hash); activateLink(hash);
return; return;
}
} }
}
} }
function activateLink(hash) { function activateLink(hash) {
if (prevActiveLink) { if (prevActiveLink) {
prevActiveLink.classList.remove('active'); prevActiveLink.classList.remove('active');
} }
if (hash !== null) { if (hash !== null) {
prevActiveLink = container.value.querySelector(`a[href="${decodeURIComponent(hash)}"]`); prevActiveLink = container.value.querySelector(`a[href="${decodeURIComponent(hash)}"]`);
} }
const activeLink = prevActiveLink; const activeLink = prevActiveLink;
if (activeLink) { if (activeLink) {
activeLink.classList.add('active'); activeLink.classList.add('active');
marker.value.style.top = activeLink.offsetTop + 5 + 'px'; marker.value.style.top = activeLink.offsetTop + 5 + 'px';
marker.value.style.opacity = '1'; marker.value.style.opacity = '1';
} } else {
else { marker.value.style.top = '33px';
marker.value.style.top = '33px'; marker.value.style.opacity = '0';
marker.value.style.opacity = '0'; }
}
} }
} }
@@ -75,13 +74,13 @@ function getAnchorTop(anchor) {
function isAnchorActive(index, anchor, nextAnchor) { function isAnchorActive(index, anchor, nextAnchor) {
const scrollTop = window.scrollY; const scrollTop = window.scrollY;
if (index === 0 && scrollTop === 0) { if (index === 0 && scrollTop === 0) {
return [true, null]; return [true, null];
} }
if (scrollTop < getAnchorTop(anchor)) { if (scrollTop < getAnchorTop(anchor)) {
return [false, null]; return [false, null];
} }
if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor)) { if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor)) {
return [true, anchor.hash]; return [true, anchor.hash];
} }
return [false, null]; return [false, null];
} }

View File

@@ -1,12 +1,10 @@
import { import { ref, inject, Ref } from 'vue';
ref, inject, Ref
} from 'vue';
export const CATEGORY_VIEW_CONTEXT = Symbol('categoryView'); export const CATEGORY_VIEW_CONTEXT = Symbol('categoryView');
interface CategoryViewContext { interface CategoryViewContext {
selectedCategory: Ref<string> selectedCategory: Ref<string>;
categoryCounts: Ref<Record<string, number>> categoryCounts: Ref<Record<string, number>>;
} }
export const categoryViewContext = { export const categoryViewContext = {

View File

@@ -1,8 +1,8 @@
import { ref } from "vue"; import { ref } from 'vue';
export default function useConfetti() { export default function useConfetti() {
const animate = ref(false) const animate = ref(false);
const confettiText = ref('confetti!') const confettiText = ref('confetti!');
function confetti() { function confetti() {
animate.value = true; animate.value = true;
@@ -15,6 +15,6 @@ export default function useConfetti() {
return { return {
animate, animate,
confetti, confetti,
confettiText confettiText,
} };
} }

View File

@@ -1,12 +1,12 @@
import { useFetch } from "@vueuse/core" import { useFetch } from '@vueuse/core';
const useFetchCategories = () => useFetch<Record<string, string[]>>( const useFetchCategories = () =>
`${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/categories`, useFetch<Record<string, string[]>>(
{ `${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/categories`,
immediate: {
typeof window !== 'undefined' immediate:
&& new URLSearchParams(window.location.search).has('search'), typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('search'),
} },
).json() ).json();
export default useFetchCategories export default useFetchCategories;

View File

@@ -1,12 +1,12 @@
import { useFetch } from "@vueuse/core" import { useFetch } from '@vueuse/core';
const useFetchTags = () => useFetch<Record<string, string[]>>( const useFetchTags = () =>
`${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/tags`, useFetch<Record<string, string[]>>(
{ `${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/tags`,
immediate: {
typeof window !== 'undefined' immediate:
&& new URLSearchParams(window.location.search).has('search'), typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('search'),
} },
).json() ).json();
export default useFetchTags export default useFetchTags;

View File

@@ -1,16 +1,14 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import { import { ref, inject, Ref } from 'vue';
ref, inject, Ref
} from 'vue';
export const ICON_STYLE_CONTEXT = Symbol('size'); export const ICON_STYLE_CONTEXT = Symbol('size');
interface IconSizeContext { interface IconSizeContext {
size: Ref<number> size: Ref<number>;
strokeWidth: Ref<number> strokeWidth: Ref<number>;
color: Ref<string> color: Ref<string>;
absoluteStrokeWidth: Ref<boolean> absoluteStrokeWidth: Ref<boolean>;
} }
export const STYLE_DEFAULTS = { export const STYLE_DEFAULTS = {
@@ -27,7 +25,7 @@ export const iconStyleContext = {
absoluteStrokeWidth: ref(false), absoluteStrokeWidth: ref(false),
}; };
export function useIconStyleContext(): IconSizeContext{ export function useIconStyleContext(): IconSizeContext {
const context = inject<IconSizeContext>(ICON_STYLE_CONTEXT); const context = inject<IconSizeContext>(ICON_STYLE_CONTEXT);
if (!context) { if (!context) {

View File

@@ -1,13 +1,17 @@
import Fuse from 'fuse.js'; import Fuse from 'fuse.js';
import { shallowRef, computed, Ref } from 'vue'; import { shallowRef, computed, Ref } from 'vue';
const useSearch = <T>(query: Ref<string>, collection: Ref<T[]>, keys: Fuse.FuseOptionKey<T>[] = []) => { const useSearch = <T>(
query: Ref<string>,
collection: Ref<T[]>,
keys: Fuse.FuseOptionKey<T>[] = [],
) => {
const index = shallowRef( const index = shallowRef(
new Fuse(collection.value, { new Fuse(collection.value, {
threshold: 0.2, threshold: 0.2,
keys, keys,
}) }),
) );
const results = computed(() => { const results = computed(() => {
index.value.setCollection(collection.value); index.value.setCollection(collection.value);

View File

@@ -1,12 +1,12 @@
import { h } from 'vue' import { h } from 'vue';
import DefaultTheme from 'vitepress/theme' import DefaultTheme from 'vitepress/theme';
import './style.css' import './style.css';
import { Theme } from 'vitepress' import { Theme } from 'vitepress';
import IconsSidebarNavAfter from './layouts/IconsSidebarNavAfter.vue' import IconsSidebarNavAfter from './layouts/IconsSidebarNavAfter.vue';
import HomeHeroIconsCard from './components/home/HomeHeroIconsCard.vue' import HomeHeroIconsCard from './components/home/HomeHeroIconsCard.vue';
import HomeHeroBefore from "./components/home/HomeHeroBefore.vue"; import HomeHeroBefore from './components/home/HomeHeroBefore.vue';
import { ICON_STYLE_CONTEXT, iconStyleContext } from './composables/useIconStyle' import { ICON_STYLE_CONTEXT, iconStyleContext } from './composables/useIconStyle';
import { CATEGORY_VIEW_CONTEXT, categoryViewContext } from './composables/useCategoryView' import { CATEGORY_VIEW_CONTEXT, categoryViewContext } from './composables/useCategoryView';
const theme: Partial<Theme> = { const theme: Partial<Theme> = {
extends: DefaultTheme, extends: DefaultTheme,
@@ -15,12 +15,12 @@ const theme: Partial<Theme> = {
'home-hero-before': () => h(HomeHeroBefore), 'home-hero-before': () => h(HomeHeroBefore),
'sidebar-nav-after': () => h(IconsSidebarNavAfter), 'sidebar-nav-after': () => h(IconsSidebarNavAfter),
'home-hero-image': () => h(HomeHeroIconsCard), 'home-hero-image': () => h(HomeHeroIconsCard),
}) });
}, },
enhanceApp({ app }) { enhanceApp({ app }) {
app.provide(ICON_STYLE_CONTEXT, iconStyleContext) app.provide(ICON_STYLE_CONTEXT, iconStyleContext);
app.provide(CATEGORY_VIEW_CONTEXT, categoryViewContext) app.provide(CATEGORY_VIEW_CONTEXT, categoryViewContext);
} },
} };
export default theme export default theme;

View File

@@ -1,43 +1,43 @@
:root { :root {
--vp-c-brand: #F56565; --vp-c-brand: #f56565;
--vp-c-brand-light: #F67373; --vp-c-brand-light: #f67373;
--vp-c-brand-lighter: #F89191; --vp-c-brand-lighter: #f89191;
--vp-c-brand-dark: #DC5A5A; --vp-c-brand-dark: #dc5a5a;
--vp-c-brand-darker: #C45050; --vp-c-brand-darker: #c45050;
--vp-c-brand-1: #F67373; --vp-c-brand-1: #f67373;
--vp-c-brand-2: #FF7070; --vp-c-brand-2: #ff7070;
--vp-c-brand-3: #F56565; --vp-c-brand-3: #f56565;
--vp-c-brand-4: #DC5A5A; --vp-c-brand-4: #dc5a5a;
--vp-c-brand-5: #C45050; --vp-c-brand-5: #c45050;
--vp-c-bg-alt-up: #fff; --vp-c-bg-alt-up: #fff;
--vp-c-bg-alt-down: #fff; --vp-c-bg-alt-down: #fff;
--vp-code-editor-plain: #24292E; --vp-code-editor-plain: #24292e;
--vp-code-editor-comment: #6A737D; --vp-code-editor-comment: #6a737d;
--vp-code-editor-keyword: #D73A49; --vp-code-editor-keyword: #d73a49;
--vp-code-editor-tag: #22863A; --vp-code-editor-tag: #22863a;
--vp-code-editor-punctuation: #24292E; --vp-code-editor-punctuation: #24292e;
--vp-code-editor-definition: #6F42C1; --vp-code-editor-definition: #6f42c1;
--vp-code-editor-property: #005CC5; --vp-code-editor-property: #005cc5;
--vp-code-editor-static: #F78C6C; --vp-code-editor-static: #f78c6c;
--vp-code-editor-string: #032F62; --vp-code-editor-string: #032f62;
} }
.dark { .dark {
--vp-c-bg-alt-up: #1B1B1D; --vp-c-bg-alt-up: #1b1b1d;
--vp-c-bg-alt-down: #0F0F10; --vp-c-bg-alt-down: #0f0f10;
--vp-code-editor-plain: #E1E4E8; --vp-code-editor-plain: #e1e4e8;
--vp-code-editor-comment: #6A737D; --vp-code-editor-comment: #6a737d;
--vp-code-editor-keyword: #F97583; --vp-code-editor-keyword: #f97583;
--vp-code-editor-tag: #85E89D; --vp-code-editor-tag: #85e89d;
--vp-code-editor-punctuation: #9ECBFF; --vp-code-editor-punctuation: #9ecbff;
--vp-code-editor-definition: #B392F0; --vp-code-editor-definition: #b392f0;
--vp-code-editor-property: #79B8FF; --vp-code-editor-property: #79b8ff;
--vp-code-editor-static: #F78C6C; --vp-code-editor-static: #f78c6c;
--vp-code-editor-string: #9ECBFF; --vp-code-editor-string: #9ecbff;
} }
.VPNavBarTitle .logo { .VPNavBarTitle .logo {
@@ -69,7 +69,6 @@
.VPHomeHero .container .main h1.name { .VPHomeHero .container .main h1.name {
color: var(--vp-c-text); color: var(--vp-c-text);
} }
.VPHomeHero .container .main h1.name .clip { .VPHomeHero .container .main h1.name .clip {
color: inherit; color: inherit;
@@ -82,7 +81,6 @@
color: var(--vp-c-brand); color: var(--vp-c-brand);
} }
/* */ /* */
.VPHomeHero .container .image { .VPHomeHero .container .image {
margin: 0; margin: 0;
@@ -101,14 +99,14 @@
} }
.VPFeature .icon { .VPFeature .icon {
background-color: var(--vp-c-bg);; background-color: var(--vp-c-bg);
} }
.vp-doc[class*=" _icons_"] > div { .vp-doc[class*=' _icons_'] > div {
max-width: 100%; max-width: 100%;
} }
.VPDoc:has(.vp-doc[class*=" _icons_"]) > .container > .content{ .VPDoc:has(.vp-doc[class*=' _icons_']) > .container > .content {
padding-right: 0; padding-right: 0;
padding-left: 0; padding-left: 0;
} }
@@ -120,7 +118,6 @@
} }
@media (min-width: 960px) { @media (min-width: 960px) {
.VPHomeHero .container .image { .VPHomeHero .container .image {
order: 1; order: 1;
margin-bottom: auto; margin-bottom: auto;
@@ -141,7 +138,6 @@
} }
.VPHomeHero .container .main h1.name { .VPHomeHero .container .main h1.name {
} }
} }
@@ -198,10 +194,10 @@ html:has(* .outline-link:target) {
transition: background-color 0.25s; transition: background-color 0.25s;
} }
.sp-wrapper .sp-tabs .sp-tab-button[data-active="true"] { .sp-wrapper .sp-tabs .sp-tab-button[data-active='true'] {
color: var(--vp-code-tab-active-text-color); color: var(--vp-code-tab-active-text-color);
} }
.sp-wrapper .sp-tabs .sp-tab-button[data-active="true"]:after { .sp-wrapper .sp-tabs .sp-tab-button[data-active='true']:after {
background-color: var(--vp-code-tab-active-bar-color); background-color: var(--vp-code-tab-active-bar-color);
} }

View File

@@ -1,5 +1,5 @@
export type IconNode = [elementName: string, attrs: Record<string, string>][] export type IconNode = [elementName: string, attrs: Record<string, string>][];
export type IconNodeWithKeys = [elementName: string, attrs: Record<string, string>, key: string][] export type IconNodeWithKeys = [elementName: string, attrs: Record<string, string>, key: string][];
export interface IconEntity { export interface IconEntity {
name: string; name: string;
@@ -13,45 +13,44 @@ export interface IconEntity {
} }
export interface Category { export interface Category {
name: string name: string;
title: string title: string;
icon?: string icon?: string;
iconCount: number iconCount: number;
icons?: IconEntity[] icons?: IconEntity[];
} }
interface Shield { interface Shield {
alt: string alt: string;
src: string src: string;
href: string href: string;
} }
export interface PackageItem { export interface PackageItem {
name: string name: string;
description: string description: string;
icon: string icon: string;
iconDark: string iconDark: string;
shields: Shield[] shields: Shield[];
source: string source: string;
documentation: string documentation: string;
order?: number order?: number;
private?: boolean private?: boolean;
flutter?: object flutter?: object;
} }
export interface Release { export interface Release {
version: string version: string;
date: string date: string;
} }
interface ShowcaseItemImage { interface ShowcaseItemImage {
light: string light: string;
dark: string dark: string;
} }
export interface ShowcaseItem { export interface ShowcaseItem {
name: string name: string;
url: string url: string;
image: ShowcaseItemImage image: ShowcaseItemImage;
} }

View File

@@ -1,6 +1,6 @@
export default function downloadData(filename:string, data:string) { export default function downloadData(filename: string, data: string) {
const link = document.createElement('a'); const link = document.createElement('a');
link.download = filename; link.download = filename;
link.href = data link.href = data;
link.click(); link.click();
} }

View File

@@ -9,26 +9,26 @@ const allowedAttrs = [
'stroke-linecap', 'stroke-linecap',
'stroke-linejoin', 'stroke-linejoin',
'class', 'class',
] ];
export default function getSVGIcon(element?: HTMLElement, attrs?: Record<string, string>) { export default function getSVGIcon(element?: HTMLElement, attrs?: Record<string, string>) {
const svg = element ?? document.querySelector('#previewer svg') const svg = element ?? document.querySelector('#previewer svg');
if (!svg) return if (!svg) return;
const clonedSvg = svg.cloneNode(true) as SVGElement const clonedSvg = svg.cloneNode(true) as SVGElement;
// Filter out attributes that are not allowed in SVGs // Filter out attributes that are not allowed in SVGs
for (const attr of Array.from(clonedSvg.attributes)) { for (const attr of Array.from(clonedSvg.attributes)) {
if (!allowedAttrs.includes(attr.name)) { if (!allowedAttrs.includes(attr.name)) {
clonedSvg.removeAttribute(attr.name) clonedSvg.removeAttribute(attr.name);
} }
} }
for (const [key, value] of Object.entries(attrs ?? {})) { for (const [key, value] of Object.entries(attrs ?? {})) {
clonedSvg.setAttribute(key, value) clonedSvg.setAttribute(key, value);
} }
const svgString = new XMLSerializer().serializeToString(clonedSvg) const svgString = new XMLSerializer().serializeToString(clonedSvg);
return svgString return svgString;
} }

View File

@@ -1,23 +1,22 @@
import relatedIcons from '../.vitepress/data/relatedIcons.json' import relatedIcons from '../.vitepress/data/relatedIcons.json';
import iconNodes from '../.vitepress/data/iconNodes' import iconNodes from '../.vitepress/data/iconNodes';
import * as iconDetails from '../.vitepress/data/iconDetails' import * as iconDetails from '../.vitepress/data/iconDetails';
import { IconEntity } from "../.vitepress/theme/types"; import { IconEntity } from '../.vitepress/theme/types';
export default { export default {
paths: async () => { paths: async () => {
return (Object.values(iconDetails) as unknown as IconEntity[]).map((iconEntity) => { return (Object.values(iconDetails) as unknown as IconEntity[]).map((iconEntity) => {
const params = { const params = {
...iconEntity, ...iconEntity,
relatedIcons: relatedIcons[iconEntity.name].map((name: string) => ({ relatedIcons: relatedIcons[iconEntity.name].map((name: string) => ({
name, name,
iconNode: iconNodes[name], iconNode: iconNodes[name],
})) })),
} };
return { return {
params, params,
} };
}) });
} },
} };

View File

@@ -1,13 +1,13 @@
import { getAllCategoryFiles } from '../.vitepress/lib/categories' import { getAllCategoryFiles } from '../.vitepress/lib/categories';
import iconMetaData from '../.vitepress/data/iconMetaData' import iconMetaData from '../.vitepress/data/iconMetaData';
export default { export default {
async load() { async load() {
return { return {
categories: getAllCategoryFiles(), categories: getAllCategoryFiles(),
iconCategories: Object.fromEntries( iconCategories: Object.fromEntries(
Object.entries(iconMetaData).map(([name, { categories }]) => [name, categories]) Object.entries(iconMetaData).map(([name, { categories }]) => [name, categories]),
), ),
} };
} },
} };

View File

@@ -1,13 +1,13 @@
import createCodeExamples from '../.vitepress/lib/createCodeExamples' import createCodeExamples from '../.vitepress/lib/createCodeExamples';
export default { export default {
async load() { async load() {
const codeExamples = await createCodeExamples() const codeExamples = await createCodeExamples();
// const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons)) // const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons))
return { return {
codeExamples, codeExamples,
} };
} },
} };

View File

@@ -1,9 +1,9 @@
import iconNodes from '../.vitepress/data/iconNodes' import iconNodes from '../.vitepress/data/iconNodes';
export default { export default {
async load() { async load() {
return { return {
icons: Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode })), icons: Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode })),
} };
} },
} };

View File

@@ -4,6 +4,6 @@
"jsx": "react", "jsx": "react",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"noEmit": true "noEmit": true,
} },
} }

View File

@@ -4,7 +4,9 @@
"karsa-mistmere", "karsa-mistmere",
"jguddas" "jguddas"
], ],
"aliases": ["verified"], "aliases": [
"verified"
],
"tags": [ "tags": [
"verified", "verified",
"check" "check"

View File

@@ -1,6 +1,8 @@
{ {
"$schema": "../icon.schema.json", "$schema": "../icon.schema.json",
"contributors": ["danielbayley"], "contributors": [
"danielbayley"
],
"tags": [ "tags": [
"code", "code",
"coding", "coding",

View File

@@ -4,7 +4,9 @@
"danielbayley", "danielbayley",
"karsa-mistmere" "karsa-mistmere"
], ],
"aliases": ["curly-braces"], "aliases": [
"curly-braces"
],
"tags": [ "tags": [
"json", "json",
"code", "code",

View File

@@ -1,17 +1,17 @@
{ {
"$schema": "../icon.schema.json", "$schema": "../icon.schema.json",
"contributors": [ "contributors": [
"danielbayley" "danielbayley"
], ],
"tags": [ "tags": [
"drama", "drama",
"masks", "masks",
"theater", "theater",
"theatre", "theatre",
"entertainment", "entertainment",
"show" "show"
], ],
"categories": [ "categories": [
"multimedia" "multimedia"
] ]
} }

View File

@@ -1,6 +1,8 @@
{ {
"$schema": "../icon.schema.json", "$schema": "../icon.schema.json",
"contributors": ["danielbayley"], "contributors": [
"danielbayley"
],
"tags": [ "tags": [
"projects", "projects",
"manage", "manage",
@@ -20,5 +22,10 @@
"code", "code",
"coding" "coding"
], ],
"categories": ["charts", "time", "development", "design"] "categories": [
"charts",
"time",
"development",
"design"
]
} }

View File

@@ -1,10 +1,10 @@
{ {
"$schema": "../icon.schema.json", "$schema": "../icon.schema.json",
"tags": [ "tags": [
"flag", "flag",
"bullseye" "bullseye"
], ],
"categories": [ "categories": [
"gaming" "gaming"
] ]
} }

View File

@@ -16,7 +16,7 @@
"layout", "layout",
"arrows" "arrows"
], ],
"aliases": [ "aliases": [
"sidebar-close" "sidebar-close"
] ]
} }

View File

@@ -18,7 +18,7 @@
"layout", "layout",
"arrows" "arrows"
], ],
"aliases": [ "aliases": [
"sidebar-open" "sidebar-open"
] ]
} }

View File

@@ -1,6 +1,8 @@
{ {
"$schema": "../icon.schema.json", "$schema": "../icon.schema.json",
"contributors": ["danielbayley"], "contributors": [
"danielbayley"
],
"tags": [ "tags": [
"unshielded", "unshielded",
"cybersecurity", "cybersecurity",

View File

@@ -3,7 +3,9 @@
"contributors": [ "contributors": [
"karsa-mistmere" "karsa-mistmere"
], ],
"aliases": ["stars"], "aliases": [
"stars"
],
"tags": [ "tags": [
"stars", "stars",
"effect", "effect",

View File

@@ -1,18 +1,18 @@
{ {
"$schema": "../icon.schema.json", "$schema": "../icon.schema.json",
"contributors": [ "contributors": [
"danielbayley" "danielbayley"
], ],
"tags": [ "tags": [
"theater", "theater",
"theatre", "theatre",
"entertainment", "entertainment",
"podium", "podium",
"stage", "stage",
"musical" "musical"
], ],
"categories": [ "categories": [
"buildings", "buildings",
"social" "social"
] ]
} }

View File

@@ -30,11 +30,12 @@
"generate:nextJSAliases": "node ./scripts/generateNextJSAliases.mjs", "generate:nextJSAliases": "node ./scripts/generateNextJSAliases.mjs",
"postinstall": "husky install", "postinstall": "husky install",
"lint:es": "eslint .", "lint:es": "eslint .",
"lint:format": "prettier \"**/*.{js,mjs,ts,jsx,tsx,html,css,scss,json,yml,yaml}\" --write", "lint:format": "prettier \"**/*.{js,mjs,ts,jsx,tsx,html,css,scss,json,yml,yaml}\" --check",
"lint:json:icons": "ajv --spec=draft2020 -s icon.schema.json -d 'icons/*.json' > /dev/null", "lint:json:icons": "ajv --spec=draft2020 -s icon.schema.json -d 'icons/*.json' > /dev/null",
"lint:json:categories": "ajv --spec=draft2020 -s category.schema.json -d 'categories/*.json' > /dev/null", "lint:json:categories": "ajv --spec=draft2020 -s category.schema.json -d 'categories/*.json' > /dev/null",
"lint:json": "pnpm run lint:json:icons && pnpm run lint:json:categories", "lint:json": "pnpm run lint:json:icons && pnpm run lint:json:categories",
"lint": "pnpm lint:es && pnpm lint:format && pnpm lint:json", "lint": "pnpm lint:es && pnpm lint:format && pnpm lint:json",
"format": "prettier \"**/*.{js,mjs,ts,jsx,tsx,html,css,scss,json,yml,yaml}\" --write",
"prepare": "husky install", "prepare": "husky install",
"gi": "node ./scripts/generate/generateIcons.mjs" "gi": "node ./scripts/generate/generateIcons.mjs"
}, },
@@ -58,7 +59,7 @@
"minimist": "^1.2.8", "minimist": "^1.2.8",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"p-memoize": "^7.1.1", "p-memoize": "^7.1.1",
"prettier": "3.1.1", "prettier": "3.2.4",
"semver": "^7.5.4", "semver": "^7.5.4",
"simple-git": "^3.21.0", "simple-git": "^3.21.0",
"svgo": "^3.1.0", "svgo": "^3.1.0",

View File

@@ -35,10 +35,7 @@
"lint": { "lint": {
"builder": "@angular-eslint/builder:lint", "builder": "@angular-eslint/builder:lint",
"options": { "options": {
"lintFilePatterns": [ "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
"src/**/*.ts",
"src/**/*.html"
]
} }
} }
} }

View File

@@ -3,7 +3,7 @@
// https://karma-runner.github.io/1.0/config/configuration-file.html // https://karma-runner.github.io/1.0/config/configuration-file.html
process.env.CHROME_BIN = require('puppeteer').executablePath(); process.env.CHROME_BIN = require('puppeteer').executablePath();
module.exports = function(config) { module.exports = function (config) {
config.set({ config.set({
basePath: '', basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'], frameworks: ['jasmine', '@angular-devkit/build-angular'],

View File

@@ -22,5 +22,5 @@ import { LucideIconData } from './types';
const ${componentName}: LucideIconData = ${JSON.stringify(children)}; //eslint-disable-line no-shadow-restricted-names const ${componentName}: LucideIconData = ${JSON.stringify(children)}; //eslint-disable-line no-shadow-restricted-names
export default ${componentName}; export default ${componentName};
` `;
}; };

View File

@@ -63,7 +63,7 @@ describe('LucideAngularComponent', () => {
testHostComponent.setAbsoluteStrokeWidth(true); testHostComponent.setAbsoluteStrokeWidth(true);
testHostFixture.detectChanges(); testHostFixture.detectChanges();
expect(getSvgAttribute('stroke-width')).toBe( expect(getSvgAttribute('stroke-width')).toBe(
formatFixed(strokeWidth / (size / defaultAttributes.height)) formatFixed(strokeWidth / (size / defaultAttributes.height)),
); );
}); });

View File

@@ -49,7 +49,7 @@ export class LucideAngularComponent implements OnChanges {
@Inject(Renderer2) private renderer: Renderer2, @Inject(Renderer2) private renderer: Renderer2,
@Inject(ChangeDetectorRef) private changeDetector: ChangeDetectorRef, @Inject(ChangeDetectorRef) private changeDetector: ChangeDetectorRef,
@Inject(LUCIDE_ICONS) private iconProviders: LucideIconProviderInterface[], @Inject(LUCIDE_ICONS) private iconProviders: LucideIconProviderInterface[],
@Inject(LucideIconConfig) private iconConfig: LucideIconConfig @Inject(LucideIconConfig) private iconConfig: LucideIconConfig,
) { ) {
this.defaultSize = defaultAttributes.height; this.defaultSize = defaultAttributes.height;
} }
@@ -99,7 +99,7 @@ export class LucideAngularComponent implements OnChanges {
this.replaceElement(icoOfName); this.replaceElement(icoOfName);
} else { } else {
throw new Error( throw new Error(
`The "${name}" icon has not been provided by any available icon providers.` `The "${name}" icon has not been provided by any available icon providers.`,
); );
} }
} else if (Array.isArray(name)) { } else if (Array.isArray(name)) {
@@ -132,7 +132,7 @@ export class LucideAngularComponent implements OnChanges {
...this.class ...this.class
.split(/ /) .split(/ /)
.map((a) => a.trim()) .map((a) => a.trim())
.filter((a) => a.length > 0) .filter((a) => a.length > 0),
); );
} }
const childElements = this.elem.nativeElement.childNodes; const childElements = this.elem.nativeElement.childNodes;
@@ -145,7 +145,7 @@ export class LucideAngularComponent implements OnChanges {
toPascalCase(str: string): string { toPascalCase(str: string): string {
return str.replace( return str.replace(
/(\w)([a-z0-9]*)(_|-|\s*)/g, /(\w)([a-z0-9]*)(_|-|\s*)/g,
(g0, g1, g2) => g1.toUpperCase() + g2.toLowerCase() (g0, g1, g2) => g1.toUpperCase() + g2.toLowerCase(),
); );
} }
@@ -174,7 +174,7 @@ export class LucideAngularComponent implements OnChanges {
private createElement([tag, attrs, children = []]: readonly [ private createElement([tag, attrs, children = []]: readonly [
string, string,
SvgAttributes, SvgAttributes,
LucideIconData? LucideIconData?,
]) { ]) {
const element = this.renderer.createElement(tag, 'http://www.w3.org/2000/svg'); const element = this.renderer.createElement(tag, 'http://www.w3.org/2000/svg');

View File

@@ -4,22 +4,23 @@ import 'zone.js';
import 'zone.js/testing'; import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing'; import { getTestBed } from '@angular/core/testing';
import { import {
BrowserDynamicTestingModule, BrowserDynamicTestingModule,
platformBrowserDynamicTesting platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing'; } from '@angular/platform-browser-dynamic/testing';
declare const require: { declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): { context(
<T>(id: string): T; path: string,
keys(): string[]; deep?: boolean,
}; filter?: RegExp,
): {
<T>(id: string): T;
keys(): string[];
};
}; };
// First, initialize the Angular testing environment. // First, initialize the Angular testing environment.
getTestBed().initTestEnvironment( getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
BrowserDynamicTestingModule,
platformBrowserDynamicTesting(),
);
// Then we find all the tests. // Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/); const context = require.context('./', true, /\.spec\.ts$/);

View File

@@ -10,9 +10,7 @@
"noPropertyAccessFromIndexSignature": true, "noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true, "noImplicitReturns": true,
"paths": { "paths": {
"lucide-angular": [ "lucide-angular": ["dist"],
"dist"
]
}, },
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"sourceMap": true, "sourceMap": true,
@@ -23,15 +21,12 @@
"importHelpers": true, "importHelpers": true,
"target": "es2017", "target": "es2017",
"module": "es2020", "module": "es2020",
"lib": [ "lib": ["es2020", "dom"],
"es2020",
"dom"
]
}, },
"angularCompilerOptions": { "angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false, "enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true, "strictInjectionParameters": true,
"strictInputAccessModifiers": true, "strictInputAccessModifiers": true,
"strictTemplates": true "strictTemplates": true,
} },
} }

View File

@@ -8,8 +8,5 @@
"inlineSources": true, "inlineSources": true,
"types": [] "types": []
}, },
"exclude": [ "exclude": ["src/test.ts", "**/*.spec.ts"]
"src/test.ts",
"**/*.spec.ts"
]
} }

View File

@@ -3,15 +3,8 @@
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./out-tsc/spec", "outDir": "./out-tsc/spec",
"types": [ "types": ["jasmine"]
"jasmine"
]
}, },
"files": [ "files": ["src/test.ts"],
"src/test.ts" "include": ["**/*.spec.ts", "**/*.d.ts"]
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
} }

View File

@@ -1,91 +1,95 @@
import iconNodeToSvg from "../helpers/iconNodeToSvg" import iconNodeToSvg from '../helpers/iconNodeToSvg';
export type IconNode = any[] export type IconNode = any[];
export type IconName = string export type IconName = string;
export type Tag = string[] export type Tag = string[];
export interface Tags { export interface Tags {
[key:string]: Tag [key: string]: Tag;
} }
export interface LucideIcons { export interface LucideIcons {
version: string version: string;
iconNodes: { [key: IconName]: IconNode } iconNodes: { [key: IconName]: IconNode };
tags: Tags, tags: Tags;
svgs: { [key: IconName]: string } svgs: { [key: IconName]: string };
} }
export const fetchIcons = async (cachedIcons? : LucideIcons): Promise<LucideIcons> => { export const fetchIcons = async (cachedIcons?: LucideIcons): Promise<LucideIcons> => {
const response = await fetch('https://unpkg.com/lucide-static@latest/package.json') const response = await fetch('https://unpkg.com/lucide-static@latest/package.json');
const packageJson = await response.json(); const packageJson = await response.json();
if(cachedIcons && cachedIcons?.version === packageJson.version) { if (cachedIcons && cachedIcons?.version === packageJson.version) {
return cachedIcons return cachedIcons;
} }
const [iconNodesResponse, tagsResponse] = await Promise.all([ const [iconNodesResponse, tagsResponse] = await Promise.all([
fetch('https://lucide.dev/api/icon-nodes'), fetch('https://lucide.dev/api/icon-nodes'),
fetch('https://lucide.dev/api/tags') fetch('https://lucide.dev/api/tags'),
]) ]);
const iconNodes = await iconNodesResponse.json(); const iconNodes = await iconNodesResponse.json();
const tags = await tagsResponse.json(); const tags = await tagsResponse.json();
const svgs = Object.keys(iconNodes).reduce((acc : { [key:string]: string}, iconName) => { const svgs = Object.keys(iconNodes).reduce((acc: { [key: string]: string }, iconName) => {
acc[iconName] = iconNodeToSvg(iconName, iconNodes[iconName]) acc[iconName] = iconNodeToSvg(iconName, iconNodes[iconName]);
return acc return acc;
}, {}) }, {});
const lucideIcons: LucideIcons = { const lucideIcons: LucideIcons = {
version: packageJson.version, version: packageJson.version,
tags, tags,
iconNodes, iconNodes,
svgs svgs,
} };
parent.postMessage({ parent.postMessage(
pluginMessage: { {
type: "setCachedIcons", pluginMessage: {
lucideIcons type: 'setCachedIcons',
} lucideIcons,
}, "*") },
},
'*',
);
return lucideIcons return lucideIcons;
} };
export const getIcons = () => new Promise<LucideIcons>(async (resolve, reject)=> { export const getIcons = () =>
new Promise<LucideIcons>(async (resolve, reject) => {
parent.postMessage(
{
pluginMessage: {
type: 'getCachedIcons',
},
},
'*',
);
parent.postMessage({ window.onmessage = async (event) => {
pluginMessage: { if (event.type === 'message' && event?.data?.pluginMessage.type === 'cachedIcons') {
type: "getCachedIcons", const lucideIcons = await fetchIcons(event?.data?.pluginMessage?.cachedIcons);
} resolve(lucideIcons);
}, "*") }
};
});
window.onmessage = async (event) => { type EventCallback = (lucideIcons: LucideIcons) => void;
if (event.type === 'message' && event?.data?.pluginMessage.type === 'cachedIcons') {
const lucideIcons = await fetchIcons(event?.data?.pluginMessage?.cachedIcons)
resolve(lucideIcons)
}
}
});
type EventCallback = (lucideIcons: LucideIcons) => void
export const iconFetchListener = (callback: EventCallback) => { export const iconFetchListener = (callback: EventCallback) => {
fetchIcons() fetchIcons();
const handleEvent = (event: MessageEvent) => { const handleEvent = (event: MessageEvent) => {
if (event.type === 'message' && event?.data?.pluginMessage.type === 'cachedIcons') { if (event.type === 'message' && event?.data?.pluginMessage.type === 'cachedIcons') {
const lucideIcons = event?.data?.pluginMessage?.cachedIcons;
const lucideIcons = event?.data?.pluginMessage?.cachedIcons callback(lucideIcons);
callback(lucideIcons)
} }
} };
window.addEventListener('message', handleEvent) window.addEventListener('message', handleEvent);
const removeListener = () => { const removeListener = () => {
window.removeEventListener('message', handleEvent) window.removeEventListener('message', handleEvent);
} };
return removeListener return removeListener;
} };

View File

@@ -1,5 +1,3 @@
const EditBar = () => { const EditBar = () => {};
} export default EditBar;
export default EditBar

View File

@@ -1,32 +1,37 @@
import { renderToString } from 'react-dom/server' import { renderToString } from 'react-dom/server';
import { FC } from 'react'; import { FC } from 'react';
import './IconButton.scss' import './IconButton.scss';
interface IconButtonProps { interface IconButtonProps {
name: string, name: string;
component: FC, component: FC;
} }
function IconButton({ name, component: IconComponent }: IconButtonProps) { function IconButton({ name, component: IconComponent }: IconButtonProps) {
const onIconClick = () => { const onIconClick = () => {
const svg = renderToString(<IconComponent/>); const svg = renderToString(<IconComponent />);
parent.postMessage({ pluginMessage: { parent.postMessage(
type: 'drawIcon', {
icon: { name, svg } pluginMessage: {
}}, '*') type: 'drawIcon',
} icon: { name, svg },
},
},
'*',
);
};
return ( return (
<button <button
key={name} key={name}
aria-label={name} aria-label={name}
onClick={onIconClick} onClick={onIconClick}
className='icon-button' className="icon-button"
> >
<IconComponent /> <IconComponent />
</button> </button>
) );
} }
export default IconButton export default IconButton;

View File

@@ -1 +1 @@
export { default } from './IconButton' export { default } from './IconButton';

View File

@@ -1,23 +1,26 @@
import { useState } from 'react' import { useState } from 'react';
import './Menu.scss' import './Menu.scss';
interface MenuProps { interface MenuProps {
page: string page: string;
setPage: (page:string) => void setPage: (page: string) => void;
} }
const menuItems = ['icons', 'info'] const menuItems = ['icons', 'info'];
const Menu = ({page, setPage = (page) => {}}: MenuProps) => { const Menu = ({ page, setPage = (page) => {} }: MenuProps) => {
return ( return (
<nav className="menu"> <nav className="menu">
{ menuItems.map((menuItem) => ( {menuItems.map((menuItem) => (
<div className={`menu-item ${page === menuItem ? 'active' : null }`} onClick={() => setPage(menuItem)}> <div
className={`menu-item ${page === menuItem ? 'active' : null}`}
onClick={() => setPage(menuItem)}
>
{menuItem} {menuItem}
</div> </div>
)) } ))}
</nav> </nav>
) );
} };
export default Menu export default Menu;

View File

@@ -1 +1 @@
export { default } from './Menu' export { default } from './Menu';

View File

@@ -1,12 +1,12 @@
import "./SearchInput.scss" import './SearchInput.scss';
import { ChangeEvent } from "react" import { ChangeEvent } from 'react';
import SearchIcon from "../icons/SearchIcon" import SearchIcon from '../icons/SearchIcon';
interface SearchInputProps extends React.HTMLProps<HTMLDivElement> { interface SearchInputProps extends React.HTMLProps<HTMLDivElement> {
value: string, value: string;
iconCount: number, iconCount: number;
onChange: (event: ChangeEvent<HTMLInputElement>) => void onChange: (event: ChangeEvent<HTMLInputElement>) => void;
placeholder: string placeholder: string;
} }
function SearchInput({ value, onChange, placeholder, className, ...props }: SearchInputProps) { function SearchInput({ value, onChange, placeholder, className, ...props }: SearchInputProps) {
@@ -15,7 +15,7 @@ function SearchInput({ value, onChange, placeholder, className, ...props }: Sear
className="search-input" className="search-input"
{...props} {...props}
> >
<SearchIcon className='icon'/> <SearchIcon className="icon" />
<input <input
autoFocus autoFocus
type="search" type="search"
@@ -25,7 +25,7 @@ function SearchInput({ value, onChange, placeholder, className, ...props }: Sear
className="input__field" className="input__field"
/> />
</div> </div>
) );
} }
export default SearchInput export default SearchInput;

View File

@@ -1 +1 @@
export { default } from './SearchInput' export { default } from './SearchInput';

View File

@@ -8,12 +8,8 @@
--block: rgba(0, 0, 0, 0.06); --block: rgba(0, 0, 0, 0.06);
--loader: hsl(0 0% 89%); --loader: hsl(0 0% 89%);
background: linear-gradient( background:
-75deg, linear-gradient(-75deg, transparent 40%, var(--loader), transparent 60%) 0 0 / 200% 100%,
transparent 40%,
var(--loader),
transparent 60%
) 0 0 / 200% 100%,
var(--block); var(--block);
border-radius: calc(var(--padding) * 0.5); border-radius: calc(var(--padding) * 0.5);

View File

@@ -1,13 +1,13 @@
import './Skeleton.scss' import './Skeleton.scss';
const Skeleton = () => { const Skeleton = () => {
return ( return (
<> <>
{Array.from({length: 48 }, () => ( {Array.from({ length: 48 }, () => (
<div className="skeleton"/> <div className="skeleton" />
))} ))}
</> </>
) );
} };
export default Skeleton export default Skeleton;

View File

@@ -1,5 +1,4 @@
import { createElement, forwardRef } from 'react' import { createElement, forwardRef } from 'react';
const SearchIcon = (props: any) => ( const SearchIcon = (props: any) => (
<svg <svg
@@ -17,6 +16,6 @@ const SearchIcon = (props: any) => (
stroke="none" stroke="none"
/> />
</svg> </svg>
) );
export default SearchIcon export default SearchIcon;

View File

@@ -13,9 +13,8 @@ const defaultAttributes = {
strokeLinejoin: 'round', strokeLinejoin: 'round',
}; };
export interface LucideProps extends Partial<SVGProps<SVGSVGElement>> { export interface LucideProps extends Partial<SVGProps<SVGSVGElement>> {
size?: string | number size?: string | number;
} }
/** /**
@@ -26,7 +25,8 @@ export interface LucideProps extends Partial<SVGProps<SVGSVGElement>> {
* @param {string} string * @param {string} string
* @returns {string} A kebabized string * @returns {string} A kebabized string
*/ */
export const toKebabCase = (string: string) => string.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase(); export const toKebabCase = (string: string) =>
string.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
const createIconComponent = (iconName: string, iconNode: IconNode) => { const createIconComponent = (iconName: string, iconNode: IconNode) => {
const Component = forwardRef<SVGSVGElement, LucideProps>( const Component = forwardRef<SVGSVGElement, LucideProps>(
@@ -43,7 +43,12 @@ const createIconComponent = (iconName: string, iconNode: IconNode) => {
className: `lucide lucide-${toKebabCase(iconName)}`, className: `lucide lucide-${toKebabCase(iconName)}`,
...rest, ...rest,
}, },
[...iconNode.map(([tag, attrs]: [tag:string, attrs: SVGProps<SVGSVGElement>]) => createElement(tag, attrs)), ...([children] || [])], [
...iconNode.map(([tag, attrs]: [tag: string, attrs: SVGProps<SVGSVGElement>]) =>
createElement(tag, attrs),
),
...([children] || []),
],
), ),
); );
@@ -52,4 +57,4 @@ const createIconComponent = (iconName: string, iconNode: IconNode) => {
return Component; return Component;
}; };
export default createIconComponent export default createIconComponent;

View File

@@ -1,13 +1,9 @@
import { Tags } from "../api/fetchIcons"; import { Tags } from '../api/fetchIcons';
import { Icon } from "../hooks/useSearch"; import { Icon } from '../hooks/useSearch';
export default (icons: Icon[], tags: Tags ,query:string) => export default (icons: Icon[], tags: Tags, query: string) =>
icons.filter(([name]: Icon) => { icons.filter(([name]: Icon) => {
const iconTags = tags && tags[name] ? tags[name] : [] const iconTags = tags && tags[name] ? tags[name] : [];
return [name, ...iconTags].some( return [name, ...iconTags].some((item: string) => item.toLowerCase().includes(query));
(item:string) => item });
.toLowerCase()
.includes(query)
)
})

View File

@@ -1,11 +1,11 @@
import { createElement } from "react"; import { createElement } from 'react';
import { renderToString } from "react-dom/server"; import { renderToString } from 'react-dom/server';
import { IconNode } from "../api/fetchIcons"; import { IconNode } from '../api/fetchIcons';
import createIconComponent from "./createIconComponent"; import createIconComponent from './createIconComponent';
const iconNodeToSvg = (iconName: string, iconNode : IconNode) => { const iconNodeToSvg = (iconName: string, iconNode: IconNode) => {
const IconComponent = createIconComponent(iconName, iconNode) const IconComponent = createIconComponent(iconName, iconNode);
return renderToString(createElement(IconComponent)); return renderToString(createElement(IconComponent));
} };
export default iconNodeToSvg export default iconNodeToSvg;

View File

@@ -1,4 +1,3 @@
/** /**
* Converts string to camelcase * Converts string to camelcase
* *

View File

@@ -1,15 +1,12 @@
import { IconName, IconNode, Tags } from "../api/fetchIcons"; import { IconName, IconNode, Tags } from '../api/fetchIcons';
import filterIcons from "../helpers/filterIcons"; import filterIcons from '../helpers/filterIcons';
export type Icon = [ export type Icon = [name: IconName, iconNode: IconNode];
name: IconName,
iconNode: IconNode
]
function useSearch(icons: Icon[], tags: Tags ,query: string) { function useSearch(icons: Icon[], tags: Tags, query: string) {
if(!query) return icons; if (!query) return icons;
const searchString = query.toLowerCase() const searchString = query.toLowerCase();
return filterIcons(icons, tags, searchString); return filterIcons(icons, tags, searchString);
} }

View File

@@ -1,3 +1,6 @@
<script type="module" src="./interface.tsx"></script> <script
type="module"
src="./interface.tsx"
></script>
<div id="root"></div> <div id="root"></div>

View File

@@ -3,10 +3,9 @@
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
font-display: swap; font-display: swap;
src: url('https://rsms.me/inter/font-files/Inter-Regular.woff2?v=3.9') src:
format('woff2'), url('https://rsms.me/inter/font-files/Inter-Regular.woff2?v=3.9') format('woff2'),
url('https://rsms.me/inter/font-files/Inter-Regular.woff?v=3.9') url('https://rsms.me/inter/font-files/Inter-Regular.woff?v=3.9') format('woff');
format('woff');
} }
@font-face { @font-face {
@@ -14,10 +13,9 @@
font-style: normal; font-style: normal;
font-weight: 500; font-weight: 500;
font-display: swap; font-display: swap;
src: url('https://rsms.me/inter/font-files/Inter-Medium.woff2?v=3.9') src:
format('woff2'), url('https://rsms.me/inter/font-files/Inter-Medium.woff2?v=3.9') format('woff2'),
url('https://rsms.me/inter/font-files/Inter-Medium.woff?v=3.9') url('https://rsms.me/inter/font-files/Inter-Medium.woff?v=3.9') format('woff');
format('woff');
} }
:root { :root {

View File

@@ -1,54 +1,57 @@
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react';
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom';
import * as views from '../views' import * as views from '../views';
type Views = typeof views type Views = typeof views;
import useSearch, { Icon } from '../hooks/useSearch' import useSearch, { Icon } from '../hooks/useSearch';
import { getIcons, iconFetchListener, LucideIcons } from '../api/fetchIcons' import { getIcons, iconFetchListener, LucideIcons } from '../api/fetchIcons';
import './interface.scss' import './interface.scss';
import Menu from '../components/Menu' import Menu from '../components/Menu';
function App() { function App() {
const [page, setPage] = useState('icons') const [page, setPage] = useState('icons');
const [query, setQuery] = useState('') const [query, setQuery] = useState('');
const [icons, setIcons] = useState<Icon[]>([]) const [icons, setIcons] = useState<Icon[]>([]);
const [tags, setTags] = useState({}) const [tags, setTags] = useState({});
const [version, setVersion ] = useState('') const [version, setVersion] = useState('');
const searchResults = useMemo(() => useSearch(icons, tags, query), [icons, query]) const searchResults = useMemo(() => useSearch(icons, tags, query), [icons, query]);
const handleFetchResponse = async (lucideIcons: LucideIcons) => { const handleFetchResponse = async (lucideIcons: LucideIcons) => {
const icons = Object.entries(lucideIcons.iconNodes) const icons = Object.entries(lucideIcons.iconNodes);
setIcons(icons) setIcons(icons);
setTags(lucideIcons.tags) setTags(lucideIcons.tags);
setVersion(lucideIcons.version) setVersion(lucideIcons.version);
} };
useEffect(() => { useEffect(() => {
const removeListener = iconFetchListener(handleFetchResponse) const removeListener = iconFetchListener(handleFetchResponse);
return removeListener return removeListener;
}, []) }, []);
const View = views?.[page as keyof Views] ?? views.icons const View = views?.[page as keyof Views] ?? views.icons;
return ( return (
<div> <div>
<Menu page={page} setPage={setPage}/> <Menu
page={page}
setPage={setPage}
/>
<View <View
{...{ {...{
query, query,
setQuery, setQuery,
searchResults, searchResults,
icons, icons,
version version,
}} }}
/> />
</div> </div>
) );
} }
ReactDOM.render(<App />, document.getElementById('root')) ReactDOM.render(<App />, document.getElementById('root'));

View File

@@ -1,26 +1,34 @@
import type { LucideIcons } from "./api/fetchIcons"; import type { LucideIcons } from './api/fetchIcons';
import filterIcons from "./helpers/filterIcons"; import filterIcons from './helpers/filterIcons';
figma.showUI(__uiFiles__.worker, { visible: false }) figma.showUI(__uiFiles__.worker, { visible: false });
let cachedIcons: LucideIcons let cachedIcons: LucideIcons;
type InsertableNodes = FrameNode | GroupNode type InsertableNodes = FrameNode | GroupNode;
function isInsertableNode (node: SceneNode): node is InsertableNodes { function isInsertableNode(node: SceneNode): node is InsertableNodes {
return ['FRAME', 'GROUP'].includes(node.type) return ['FRAME', 'GROUP'].includes(node.type);
} }
const setResults = ({result, query, lucideIcons} : { result: SuggestionResults, query: string, lucideIcons: LucideIcons }) => { const setResults = ({
result,
query,
lucideIcons,
}: {
result: SuggestionResults;
query: string;
lucideIcons: LucideIcons;
}) => {
const icons = Object.entries(lucideIcons.iconNodes); const icons = Object.entries(lucideIcons.iconNodes);
const suggestions = filterIcons(icons, lucideIcons.tags, query.toLowerCase()).map(([name]) => ({ const suggestions = filterIcons(icons, lucideIcons.tags, query.toLowerCase()).map(([name]) => ({
name, name,
icon: lucideIcons.svgs[name] icon: lucideIcons.svgs[name],
})) }));
result.setSuggestions(suggestions) result.setSuggestions(suggestions);
} };
// const styles = figma.getLocalPaintStyles(); // const styles = figma.getLocalPaintStyles();
// const styleNames = styles.map((style) => style.name); // const styleNames = styles.map((style) => style.name);
@@ -28,112 +36,114 @@ const setResults = ({result, query, lucideIcons} : { result: SuggestionResults,
figma.parameters.on('input', async ({ parameters, key, query, result }) => { figma.parameters.on('input', async ({ parameters, key, query, result }) => {
if (key === 'icon-name') { if (key === 'icon-name') {
cachedIcons = await figma.clientStorage.getAsync(`lucide-icons`) cachedIcons = await figma.clientStorage.getAsync(`lucide-icons`);
if(cachedIcons && cachedIcons.iconNodes && cachedIcons.tags) { if (cachedIcons && cachedIcons.iconNodes && cachedIcons.tags) {
setResults({result, query, lucideIcons: cachedIcons}) setResults({ result, query, lucideIcons: cachedIcons });
} }
} }
if(key === 'size') { if (key === 'size') {
const iconSizes = [24,36,48,72] const iconSizes = [24, 36, 48, 72];
result.setSuggestions(iconSizes.map((size)=>({ result.setSuggestions(
name: size.toString(), iconSizes.map((size) => ({
data: size name: size.toString(),
}))) data: size,
})),
);
} }
}) });
const drawIcon = ({icon: {name, svg, size }}: any) => { const drawIcon = ({ icon: { name, svg, size } }: any) => {
const min = 0 const min = 0;
const max = 100 const max = 100;
const randomPosition = () => Math.floor(Math.random() * (max - min + 1) + min) const randomPosition = () => Math.floor(Math.random() * (max - min + 1) + min);
const icon = figma.createNodeFromSvg(svg) const icon = figma.createNodeFromSvg(svg);
icon.setPluginData('isLucideIcon', 'true') icon.setPluginData('isLucideIcon', 'true');
icon.setPluginData('iconName', name) icon.setPluginData('iconName', name);
const pluginData = icon.getPluginData('isLucideIcon') const pluginData = icon.getPluginData('isLucideIcon');
icon.name = name icon.name = name;
icon.x = Math.round(figma.viewport.center.x + randomPosition()) icon.x = Math.round(figma.viewport.center.x + randomPosition());
icon.y = Math.round(figma.viewport.center.y + randomPosition()) icon.y = Math.round(figma.viewport.center.y + randomPosition());
if(figma.currentPage.selection.length) { if (figma.currentPage.selection.length) {
let currentSelection = figma.currentPage.selection[0] let currentSelection = figma.currentPage.selection[0];
const isLucideIcon = currentSelection.getPluginData('isLucideIcon') const isLucideIcon = currentSelection.getPluginData('isLucideIcon');
// if(isLucideIcon && currentSelection?.parent) { // if(isLucideIcon && currentSelection?.parent) {
// return // return
// // currentSelection = currentSelection.parent as SceneNode // // currentSelection = currentSelection.parent as SceneNode
// } // }
if(!isLucideIcon && isInsertableNode(currentSelection)) { if (!isLucideIcon && isInsertableNode(currentSelection)) {
icon.x = currentSelection.type === 'GROUP' ? currentSelection.x : 0 icon.x = currentSelection.type === 'GROUP' ? currentSelection.x : 0;
icon.y = currentSelection.type === 'GROUP' ? currentSelection.y : 0 icon.y = currentSelection.type === 'GROUP' ? currentSelection.y : 0;
currentSelection.appendChild(icon) currentSelection.appendChild(icon);
} }
} }
figma.currentPage.selection = [icon] figma.currentPage.selection = [icon];
// lock children // lock children
// icon.children.forEach((vectorNode, key) => { // icon.children.forEach((vectorNode, key) => {
// icon.children[key].locked = true // icon.children[key].locked = true
// }); // });
} };
const setCachedIcons = async (pluginMessage: any) => { const setCachedIcons = async (pluginMessage: any) => {
if(pluginMessage.lucideIcons) { if (pluginMessage.lucideIcons) {
await figma.clientStorage.setAsync(`lucide-icons`, pluginMessage.lucideIcons) await figma.clientStorage.setAsync(`lucide-icons`, pluginMessage.lucideIcons);
} }
} };
const getCachedIcons = async () => { const getCachedIcons = async () => {
cachedIcons = await figma.clientStorage.getAsync(`lucide-icons`) cachedIcons = await figma.clientStorage.getAsync(`lucide-icons`);
const response = { type: 'cachedIcons' } const response = { type: 'cachedIcons' };
if(cachedIcons) { if (cachedIcons) {
Object.assign(response, { cachedIcons }) Object.assign(response, { cachedIcons });
} }
figma.ui.postMessage(response) figma.ui.postMessage(response);
} };
getCachedIcons() getCachedIcons();
figma.ui.onmessage = (event) => { figma.ui.onmessage = (event) => {
switch (event.type) { switch (event.type) {
case "drawIcon": case 'drawIcon':
drawIcon(event) drawIcon(event);
break; break;
case "getCachedIcons": case 'getCachedIcons':
getCachedIcons() getCachedIcons();
break; break;
case "setCachedIcons": case 'setCachedIcons':
setCachedIcons(event) setCachedIcons(event);
break; break;
case "close": case 'close':
figma.closePlugin() figma.closePlugin();
break; break;
default: default:
break; break;
} }
} };
figma.on('run', event => { figma.on('run', (event) => {
if(event.parameters) { if (event.parameters) {
figma.ui.postMessage({ figma.ui.postMessage({
type: 'getSvg', type: 'getSvg',
iconName: event.parameters['icon-name'], iconName: event.parameters['icon-name'],
size: event.parameters['size'], size: event.parameters['size'],
cachedIcons cachedIcons,
}) });
} else { } else {
figma.showUI(__uiFiles__.interface, { width: 300, height: 400 }) figma.showUI(__uiFiles__.interface, { width: 300, height: 400 });
} }
}) });

View File

@@ -5,4 +5,4 @@ export default {
blue: '#18a0fb', blue: '#18a0fb',
}, },
radii: [0, 2], radii: [0, 2],
} };

View File

@@ -1,59 +1,52 @@
import IconButton from '../components/IconButton' import IconButton from '../components/IconButton';
import SearchInput from '../components/SearchInput' import SearchInput from '../components/SearchInput';
import createIconComponent from '../helpers/createIconComponent' import createIconComponent from '../helpers/createIconComponent';
import { Icon } from '../hooks/useSearch' import { Icon } from '../hooks/useSearch';
import Skeleton from '../components/Skeleton/Skeleton' import Skeleton from '../components/Skeleton/Skeleton';
interface PageProps { interface PageProps {
query: string query: string;
setQuery: (query:string) => void setQuery: (query: string) => void;
searchResults: Icon[] searchResults: Icon[];
icons: Icon[] icons: Icon[];
version: string version: string;
} }
const Icons = ({ const Icons = ({ query, setQuery, searchResults, icons, version }: PageProps) => {
query,
setQuery,
searchResults,
icons,
version
}: PageProps) => {
return ( return (
<> <>
<SearchInput <SearchInput
value={query} value={query}
iconCount={icons.length} iconCount={icons.length}
onChange={(event) => setQuery(event.target.value)} onChange={(event) => setQuery(event.target.value)}
placeholder={icons.length ? `Search ${icons.length} icons`: 'Loading icons ..'} placeholder={icons.length ? `Search ${icons.length} icons` : 'Loading icons ..'}
/> />
<main> <main>
<div className='icon-grid'> <div className="icon-grid">
{icons.length ? ( {icons.length ? (
searchResults.map(([name, iconNode]: any) => ( searchResults.map(([name, iconNode]: any) => (
<IconButton <IconButton
name={name} name={name}
key={name} key={name}
component={createIconComponent(name, iconNode)} component={createIconComponent(name, iconNode)}
/> />
)) ))
) : ( ) : (
<Skeleton /> <Skeleton />
)} )}
</div>
</div> <footer>
<footer> <a
<a href="https://lucide.dev"
href="https://lucide.dev" target="_blank"
target="_blank" className="footer-link"
className='footer-link' >
> Lucide v{version}
Lucide v{version} </a>
</a> </footer>
</footer> </main>
</main>
</> </>
) );
} };
export default Icons export default Icons;

View File

@@ -1,64 +1,70 @@
import { SyntheticEvent } from "react" import { SyntheticEvent } from 'react';
interface PageProps { interface PageProps {
version: string version: string;
} }
const Info = ({ version }: PageProps) => { const Info = ({ version }: PageProps) => {
const menuItems = [ const menuItems = [
{ {
name: 'Report a bug', name: 'Report a bug',
url: 'https://github.com/lucide-icons/lucide/issues' url: 'https://github.com/lucide-icons/lucide/issues',
}, },
{ {
name: 'Contribute an icon', name: 'Contribute an icon',
url: 'https://github.com/lucide-icons/lucide/blob/main/CONTRIBUTING.md' url: 'https://github.com/lucide-icons/lucide/blob/main/CONTRIBUTING.md',
}, },
{ {
name: 'Website', name: 'Website',
url: 'https://lucide.dev' url: 'https://lucide.dev',
}, },
{ {
name: 'Repository', name: 'Repository',
url: 'https://github.com/lucide-icons/lucide' url: 'https://github.com/lucide-icons/lucide',
}, },
{ {
name: 'License', name: 'License',
url: 'https://lucide.dev/license' url: 'https://lucide.dev/license',
}, },
{ {
name: 'Community Page', name: 'Community Page',
url: 'https://www.figma.com/community/plugin/939567362549682242/Lucide-Icons' url: 'https://www.figma.com/community/plugin/939567362549682242/Lucide-Icons',
}, },
{ {
name: 'Supported Frameworks', name: 'Supported Frameworks',
url: 'https://lucide.dev/packages' url: 'https://lucide.dev/packages',
} },
] ];
const onClick = (url: string) => (event: SyntheticEvent) => { const onClick = (url: string) => (event: SyntheticEvent) => {
event.preventDefault() event.preventDefault();
window.open(url,'_blank') window.open(url, '_blank');
} };
return ( return (
<div className="info-page"> <div className="info-page">
<img src="https://lucide.dev/logo-text.svg" alt="Lucide Logo" className="lucide-logo"/> <img
<p className='version'> src="https://lucide.dev/logo-text.svg"
v{version} alt="Lucide Logo"
</p> className="lucide-logo"
/>
<p className="version">v{version}</p>
<section className="link-list"> <section className="link-list">
{ {menuItems.map(({ name, url }) => (
menuItems.map(({ name, url }) => ( <a
<a href={url} key={name} aria-label={name} className="info-link" onClick={onClick(url)}> href={url}
{name} key={name}
</a> aria-label={name}
)) className="info-link"
} onClick={onClick(url)}
>
{name}
</a>
))}
</section> </section>
</div> </div>
) );
} };
export default Info export default Info;

View File

@@ -1,2 +1,2 @@
export { default as icons } from './Icons' export { default as icons } from './Icons';
export { default as info } from './Info' export { default as info } from './Info';

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