Compare commits
102 Commits
lucide-vue
...
0.363.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e565116a4a | ||
|
|
cf50c9f849 | ||
|
|
9fa47227a7 | ||
|
|
547111ed5b | ||
|
|
0d6aa9feaa | ||
|
|
3a0f2b3fa8 | ||
|
|
712e6bf6e4 | ||
|
|
58319dd447 | ||
|
|
cf89b8eeac | ||
|
|
a8c6add82b | ||
|
|
d000e4904d | ||
|
|
c99d6cbf4a | ||
|
|
0b1fb8ff1f | ||
|
|
dcde43e97e | ||
|
|
96da23cc88 | ||
|
|
78182c3573 | ||
|
|
02f8f5a1c1 | ||
|
|
16ee591f49 | ||
|
|
5378156833 | ||
|
|
7b76078792 | ||
|
|
e2af1af4f9 | ||
|
|
c9513d0bf4 | ||
|
|
6fbd5ee06a | ||
|
|
d8773827fb | ||
|
|
bc1843f767 | ||
|
|
07aefe3b88 | ||
|
|
76d449a974 | ||
|
|
dd39fa328c | ||
|
|
0cf568c38b | ||
|
|
a8b8dfc58e | ||
|
|
41dde6b574 | ||
|
|
ae35ca256c | ||
|
|
38142f24a5 | ||
|
|
b16f70993a | ||
|
|
ad1accb2e3 | ||
|
|
d255c6ac4e | ||
|
|
d67ef7b0ca | ||
|
|
198ccb8430 | ||
|
|
ce1b5bdefa | ||
|
|
45aa928369 | ||
|
|
93dc356fa1 | ||
|
|
a0aa132682 | ||
|
|
1a4dd64862 | ||
|
|
3481f70ad7 | ||
|
|
024f21e896 | ||
|
|
c30ccd472e | ||
|
|
28ec03ebc8 | ||
|
|
ab3a31367a | ||
|
|
148bae88d8 | ||
|
|
ca4a38ba85 | ||
|
|
f297765a13 | ||
|
|
4657ccbfff | ||
|
|
db222d4591 | ||
|
|
bafad1c625 | ||
|
|
7f1e95c6e7 | ||
|
|
ca7a87112c | ||
|
|
d542da0a1c | ||
|
|
6fa51d2a22 | ||
|
|
ca53b06af2 | ||
|
|
b5dc96d2c2 | ||
|
|
5e5fe0085f | ||
|
|
0c2a8d774f | ||
|
|
804906dcd8 | ||
|
|
d575743d3a | ||
|
|
42c1faed75 | ||
|
|
42b494f853 | ||
|
|
9bf8a653a3 | ||
|
|
01cff578e5 | ||
|
|
a0b1305258 | ||
|
|
0cfdfa2181 | ||
|
|
a2e8ea32d2 | ||
|
|
8a7e6ba343 | ||
|
|
7a9233f4a7 | ||
|
|
71aef25812 | ||
|
|
33189a81ac | ||
|
|
e3923f87c2 | ||
|
|
08c040a57d | ||
|
|
981c3309ce | ||
|
|
2e0af66d8a | ||
|
|
a789c91213 | ||
|
|
91c95600f3 | ||
|
|
99acf4102c | ||
|
|
86f2dc12f4 | ||
|
|
b9fdde2d09 | ||
|
|
5c494962e1 | ||
|
|
772c5be034 | ||
|
|
ee3483eb1b | ||
|
|
9182c51962 | ||
|
|
eb035fe370 | ||
|
|
b96e6acd2e | ||
|
|
7b62649c39 | ||
|
|
10aff6cf7b | ||
|
|
cfa8924025 | ||
|
|
713e9b8a09 | ||
|
|
8ab6f80e4f | ||
|
|
a5221c236a | ||
|
|
cdd32b5294 | ||
|
|
54c8d4078d | ||
|
|
3302870983 | ||
|
|
0f25ee86a0 | ||
|
|
28686b5bd5 | ||
|
|
8cc143915c |
15
.cspell/custom-words.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
# Custom words
|
||||
fullscreen
|
||||
gamepad
|
||||
gantt
|
||||
kanban
|
||||
pilcrow
|
||||
squircle
|
||||
strikethrough
|
||||
touchpad
|
||||
ungroup
|
||||
|
||||
# Brands
|
||||
codepen
|
||||
codesandbox
|
||||
dribbble
|
||||
@@ -6,6 +6,5 @@ tests
|
||||
node_modules
|
||||
.eslintrc.js
|
||||
docs/images
|
||||
docs/guide/basics/examples
|
||||
docs/guide/advanced/examples
|
||||
docs/**/examples/
|
||||
packages/lucide-react/dynamicIconImports.js
|
||||
|
||||
13
.eslintrc.js
@@ -42,12 +42,15 @@ module.exports = {
|
||||
'@html-eslint/no-duplicate-attrs': 'error',
|
||||
'@html-eslint/no-inline-styles': 'error',
|
||||
'@html-eslint/require-attrs': [
|
||||
'error',
|
||||
...Object.entries(DEFAULT_ATTRS)
|
||||
.map(([attr, value]) => ({ tag: 'svg', attr, value: String(value) }))
|
||||
'error',
|
||||
...Object.entries(DEFAULT_ATTRS).map(([attr, value]) => ({
|
||||
tag: 'svg',
|
||||
attr,
|
||||
value: String(value),
|
||||
})),
|
||||
],
|
||||
'@html-eslint/indent': ['error', 2],
|
||||
"@html-eslint/no-multiple-empty-lines": ["error", { "max": 0 }],
|
||||
'@html-eslint/no-multiple-empty-lines': ['error', { max: 0 }],
|
||||
'@html-eslint/no-extra-spacing-attrs': [
|
||||
'error',
|
||||
{
|
||||
@@ -64,7 +67,7 @@ module.exports = {
|
||||
'@html-eslint/element-newline': 'error',
|
||||
'@html-eslint/no-trailing-spaces': 'error',
|
||||
'@html-eslint/quotes': 'error',
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
10
.github/ISSUE_TEMPLATE/02_bug_report.yml
vendored
@@ -35,6 +35,16 @@ body:
|
||||
placeholder: e.g. 0.289.1
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: can-reproduce-in-latest-version
|
||||
attributes:
|
||||
label: Can you reproduce this in the latest version?
|
||||
description: i.e. after running `npm install lucide-react@latest`
|
||||
options:
|
||||
- label: 'Yes'
|
||||
- label: 'No'
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
id: browsers
|
||||
attributes:
|
||||
|
||||
6
.github/actions/build-and-test.yml
vendored
@@ -1,5 +1,5 @@
|
||||
name: "Build and Test"
|
||||
description: "Builds and test a package"
|
||||
name: 'Build and Test'
|
||||
description: 'Builds and test a package'
|
||||
|
||||
inputs:
|
||||
name:
|
||||
@@ -7,7 +7,7 @@ inputs:
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
|
||||
6
.github/actions/check-icons.yml
vendored
@@ -1,5 +1,5 @@
|
||||
name: "Check icons"
|
||||
description: "Cross-checks icon and category references in JSON descriptors"
|
||||
name: 'Check icons'
|
||||
description: 'Cross-checks icon and category references in JSON descriptors'
|
||||
|
||||
inputs:
|
||||
name:
|
||||
@@ -7,7 +7,7 @@ inputs:
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
|
||||
96
.github/labeler.yml
vendored
@@ -1,92 +1,92 @@
|
||||
# For changed dependencies
|
||||
📦 dependencies:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- pnpm-lock.yaml
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- pnpm-lock.yaml
|
||||
|
||||
# For changes in documentation
|
||||
📖 documentation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- docs/*.md
|
||||
- docs/**/*.md
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- docs/*.md
|
||||
- docs/**/*.md
|
||||
|
||||
# For changes in the site, but not markdown files
|
||||
🌍 site:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'docs/**'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'docs/**'
|
||||
|
||||
# For changes in the metadata
|
||||
🫧 metadata:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'icons/*.json'
|
||||
- categories/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'icons/*.json'
|
||||
- categories/*
|
||||
|
||||
# For changes or added icons
|
||||
🎨 icon:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'icons/*.svg'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'icons/*.svg'
|
||||
|
||||
# For changes in the lucide package
|
||||
🧳 lucide package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide/*'
|
||||
|
||||
# For changes in the lucide React package
|
||||
⚛️ react package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-react/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-react/*'
|
||||
|
||||
# For changes in the lucide React Native package
|
||||
⚛️ react native package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-react-native/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-react-native/*'
|
||||
|
||||
# For changes in the lucide vue packages
|
||||
💎 vue package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-vue/*'
|
||||
- 'packages/lucide-vue-next/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-vue/*'
|
||||
- 'packages/lucide-vue-next/*'
|
||||
|
||||
# For changes in the lucide angular package
|
||||
🅰️ angular package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-angular/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-angular/*'
|
||||
|
||||
# For changes in the lucide preact package
|
||||
⚛️ preact package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-preact/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-preact/*'
|
||||
|
||||
# For changes in the lucide svelte package
|
||||
🧣 svelte package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-svelte/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-svelte/*'
|
||||
|
||||
# For changes in the lucide solid package
|
||||
🪝 solid package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-solid/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-solid/*'
|
||||
|
||||
# For changes in the lucide static package
|
||||
🪨 static package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-static/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-static/*'
|
||||
|
||||
# For changes in the lucide flutter package
|
||||
🏹 flutter package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-flutter/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-flutter/*'
|
||||
|
||||
2
.github/pull_request_template.md
vendored
@@ -47,7 +47,7 @@
|
||||
- [ ] I've made sure that the icons look sharp on low DPI displays.
|
||||
- [ ] I've made sure that the icons look consistent with the icon set in size, optical volume and density.
|
||||
- [ ] I've made sure that the icons are visually centered.
|
||||
- [ ] I've correctly optimized all icons to two points of precision.
|
||||
- [ ] I've correctly optimized all icons to three points of precision.
|
||||
|
||||
## Before Submitting <!-- For every PR! -->
|
||||
<!-- All of these requirements must be fulfilled. -->
|
||||
|
||||
5
.github/workflows/close-stale-prs.yml
vendored
@@ -1,11 +1,13 @@
|
||||
name: Close stale issues and PR
|
||||
on:
|
||||
schedule:
|
||||
- cron: "45 1 * * *"
|
||||
- cron: '45 1 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
@@ -14,4 +16,5 @@ jobs:
|
||||
close-pr-message: This PR was closed because it has been stalled for 5 days with no activity.
|
||||
close-pr-label: 🧶 stale
|
||||
days-before-stale: 30
|
||||
days-before-issue-stale: -1
|
||||
days-before-close: -1
|
||||
|
||||
6
.github/workflows/labeler.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: "Pull Request Labeler"
|
||||
name: 'Pull Request Labeler'
|
||||
on:
|
||||
- pull_request_target
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
@@ -9,4 +9,4 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
- uses: actions/labeler@v5
|
||||
|
||||
2
.github/workflows/lucide-font.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
- name: Create font in ./lucide-font
|
||||
run: pnpm build:font
|
||||
|
||||
- name: "Upload to Artifacts"
|
||||
- name: 'Upload to Artifacts'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lucide-font
|
||||
|
||||
30
.github/workflows/release.yml
vendored
@@ -41,17 +41,18 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package: [
|
||||
'lucide',
|
||||
'lucide-react',
|
||||
'lucide-react-native',
|
||||
'lucide-vue',
|
||||
'lucide-vue-next',
|
||||
'lucide-angular',
|
||||
'lucide-preact',
|
||||
'lucide-solid',
|
||||
'lucide-svelte',
|
||||
]
|
||||
package:
|
||||
[
|
||||
'lucide',
|
||||
'lucide-react',
|
||||
'lucide-react-native',
|
||||
'lucide-vue',
|
||||
'lucide-vue-next',
|
||||
'lucide-angular',
|
||||
'lucide-preact',
|
||||
'lucide-solid',
|
||||
'lucide-svelte',
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
@@ -136,7 +137,7 @@ jobs:
|
||||
- name: Create font in ./lucide-font
|
||||
run: pnpm build:font
|
||||
|
||||
- name: "Upload to Artifacts"
|
||||
- name: 'Upload to Artifacts'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lucide-font
|
||||
@@ -145,10 +146,7 @@ jobs:
|
||||
post-release:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [
|
||||
pre-release,
|
||||
lucide-font,
|
||||
]
|
||||
needs: [pre-release, lucide-font]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
pnpm-lock.yaml
|
||||
|
||||
# docs examples
|
||||
docs/**/examples/
|
||||
|
||||
# lucide-angular
|
||||
packages/lucide-angular/.angular/cache
|
||||
|
||||
|
||||
11
.vscode/settings.json
vendored
@@ -1,13 +1,6 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"devs",
|
||||
"preact",
|
||||
"Preact"
|
||||
],
|
||||
"cSpell.words": ["devs", "preact", "Preact"],
|
||||
"eslint.enable": true,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"svg"
|
||||
],
|
||||
"eslint.validate": ["javascript", "svg"],
|
||||
"svg.preview.background": "transparent"
|
||||
}
|
||||
|
||||
2
.vscode/svg.code-snippets
vendored
@@ -49,7 +49,7 @@
|
||||
"circle",
|
||||
"<circle"
|
||||
],
|
||||
"body": "<circle cx=\"${2:12}\" cy=\"${3:$2}\" r=\"${1|10,2,.5|}\" />"
|
||||
"body": "<circle cx=\"${2:12}\" cy=\"${3:$2}\" r=\"${1|10,2,.5\" fill=\"currentColor|}\" />"
|
||||
},
|
||||
"Ellipse": {
|
||||
"scope": "xml",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Coding & development",
|
||||
"icon": "code-2"
|
||||
"icon": "code-xml"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Multimedia",
|
||||
"icon": "play-circle"
|
||||
"icon": "circle-play"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Notifications",
|
||||
"icon": "alert-triangle"
|
||||
"icon": "triangle-alert"
|
||||
}
|
||||
10
cspell.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"dictionaries": ["en-us", "custom-words"],
|
||||
"dictionaryDefinitions": [
|
||||
{
|
||||
"name": "custom-words",
|
||||
"path": "./.cspell/custom-words.txt",
|
||||
"addWords": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { eventHandler, setResponseHeader } from 'h3'
|
||||
import iconMetaData from '../../data/iconMetaData'
|
||||
import { eventHandler, setResponseHeader } from 'h3';
|
||||
import iconMetaData from '../../data/iconMetaData';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(iconMetaData).map(([name, { categories }]) => [ name, categories ])
|
||||
)
|
||||
})
|
||||
Object.entries(iconMetaData).map(([name, { categories }]) => [name, categories]),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -37,13 +37,13 @@ export default eventHandler((event) => {
|
||||
backdropString,
|
||||
src,
|
||||
color: name in iconNodes ? 'red' : '#777',
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const svg = Buffer.from(
|
||||
// We can't use jsx here, is not supported here by nitro.
|
||||
renderToString(createElement(SvgPreview, { src, showGrid: true }, children))
|
||||
renderToString(createElement(SvgPreview, { src, showGrid: true }, children)),
|
||||
).toString('utf8');
|
||||
|
||||
defaultContentType(event, 'image/svg+xml');
|
||||
|
||||
@@ -28,7 +28,7 @@ export default eventHandler(async (event) => {
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
`
|
||||
`,
|
||||
);
|
||||
|
||||
const resvg = new Resvg(svg, { background: '#000' });
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { eventHandler, setResponseHeader, defaultContentType } from 'h3'
|
||||
import { renderToString } from 'react-dom/server'
|
||||
import { createElement } from 'react'
|
||||
import { eventHandler, setResponseHeader, defaultContentType } from 'h3';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
import { createElement } from 'react';
|
||||
import SvgPreview from '../../../lib/SvgPreview/index.tsx';
|
||||
import createLucideIcon, { IconNode } from 'lucide-react/src/createLucideIcon'
|
||||
import createLucideIcon, { IconNode } from 'lucide-react/src/createLucideIcon';
|
||||
import { parseSync } from 'svgson';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const { params } = event.context
|
||||
const { params } = event.context;
|
||||
|
||||
const [strokeWidth, svgData] = params.data.split('/');
|
||||
const data = svgData.slice(0, -4);
|
||||
@@ -16,8 +16,8 @@ export default eventHandler((event) => {
|
||||
const Icon = createLucideIcon(
|
||||
'icon',
|
||||
parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`).children.map(
|
||||
({ name, attributes }) => [name, attributes]
|
||||
) as IconNode
|
||||
({ name, attributes }) => [name, attributes],
|
||||
) as IconNode,
|
||||
);
|
||||
|
||||
const svg = Buffer.from(
|
||||
@@ -33,12 +33,12 @@ export default eventHandler((event) => {
|
||||
@media screen and (prefers-color-scheme: dark) {
|
||||
svg { stroke: #fff; fill: transparent !important; }
|
||||
}
|
||||
</style>`
|
||||
)
|
||||
</style>`,
|
||||
),
|
||||
).toString('utf8');
|
||||
|
||||
defaultContentType(event, 'image/svg+xml')
|
||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000')
|
||||
defaultContentType(event, 'image/svg+xml');
|
||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
|
||||
|
||||
return svg
|
||||
})
|
||||
return svg;
|
||||
});
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import { eventHandler, getQuery, setResponseHeader } from 'h3'
|
||||
import iconNodes from '../../data/iconNodes'
|
||||
import { IconNodeWithKeys } from '../../theme/types'
|
||||
import { eventHandler, getQuery, setResponseHeader } from 'h3';
|
||||
import iconNodes from '../../data/iconNodes';
|
||||
import { IconNodeWithKeys } from '../../theme/types';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const query = getQuery(event)
|
||||
const query = getQuery(event);
|
||||
|
||||
const withUniqueKeys = query.withUniqueKeys === 'true'
|
||||
const withUniqueKeys = query.withUniqueKeys === 'true';
|
||||
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
if (withUniqueKeys) {
|
||||
return iconNodes
|
||||
return iconNodes;
|
||||
}
|
||||
|
||||
return Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
|
||||
if (withUniqueKeys) {
|
||||
return [name, iconNode]
|
||||
return [name, iconNode];
|
||||
}
|
||||
|
||||
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs}]) => {
|
||||
return [name, attrs]
|
||||
})
|
||||
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
|
||||
return [name, attrs];
|
||||
});
|
||||
|
||||
acc[name] = newIconNode
|
||||
acc[name] = newIconNode;
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
})
|
||||
return acc;
|
||||
}, {});
|
||||
});
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import { eventHandler, getQuery, setResponseHeader, createError } from 'h3'
|
||||
import iconNodes from '../../data/iconNodes'
|
||||
import createLucideIcon from 'lucide-react/src/createLucideIcon'
|
||||
import { renderToString } from 'react-dom/server'
|
||||
import { createElement } from 'react'
|
||||
import { eventHandler, getQuery, setResponseHeader, createError } from 'h3';
|
||||
import iconNodes from '../../data/iconNodes';
|
||||
import createLucideIcon from 'lucide-react/src/createLucideIcon';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
import { createElement } from 'react';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const { params } = event.context
|
||||
const { params } = event.context;
|
||||
|
||||
const iconNode = iconNodes[params.iconName]
|
||||
const iconNode = iconNodes[params.iconName];
|
||||
|
||||
if (iconNode == null) {
|
||||
const error = createError({
|
||||
statusCode: 404,
|
||||
message: `Icon "${params.iconName}" not found`,
|
||||
})
|
||||
});
|
||||
|
||||
return sendError(event, error)
|
||||
return sendError(event, error);
|
||||
}
|
||||
|
||||
const width = getQuery(event).width || undefined
|
||||
const height = getQuery(event).height || undefined
|
||||
const color = getQuery(event).color || undefined
|
||||
const strokeWidth = getQuery(event).strokeWidth || undefined
|
||||
const width = getQuery(event).width || undefined;
|
||||
const height = getQuery(event).height || undefined;
|
||||
const color = getQuery(event).color || undefined;
|
||||
const strokeWidth = getQuery(event).strokeWidth || undefined;
|
||||
|
||||
const LucideIcon = createLucideIcon(params.iconName, iconNode)
|
||||
const LucideIcon = createLucideIcon(params.iconName, iconNode);
|
||||
|
||||
const svg = Buffer.from(
|
||||
renderToString(
|
||||
@@ -32,14 +32,13 @@ export default eventHandler((event) => {
|
||||
height,
|
||||
color: color ? `#${color}` : undefined,
|
||||
strokeWidth,
|
||||
}
|
||||
))
|
||||
}),
|
||||
),
|
||||
).toString('utf8');
|
||||
|
||||
defaultContentType(event, 'image/svg+xml')
|
||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000')
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||
defaultContentType(event, 'image/svg+xml');
|
||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
return svg
|
||||
|
||||
})
|
||||
return svg;
|
||||
});
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { eventHandler, setResponseHeader } from 'h3'
|
||||
import iconMetaData from '../../data/iconMetaData'
|
||||
import { eventHandler, setResponseHeader } from 'h3';
|
||||
import iconMetaData from '../../data/iconMetaData';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(iconMetaData).map(([name, { tags }]) => [ name, tags ])
|
||||
)
|
||||
})
|
||||
return Object.fromEntries(Object.entries(iconMetaData).map(([name, { tags }]) => [name, tags]));
|
||||
});
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export default eventHandler(() => {
|
||||
return { nitro: 'Is Awesome! asda' }
|
||||
})
|
||||
return { nitro: 'Is Awesome! asda' };
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import { defineConfig } from 'vitepress'
|
||||
import { fileURLToPath, URL } from 'node:url';
|
||||
import { defineConfig } from 'vitepress';
|
||||
import sidebar from './sidebar';
|
||||
|
||||
const title = "Lucide";
|
||||
const socialTitle = "Lucide Icons";
|
||||
const description = "Beautiful & consistent icon toolkit made by the community."
|
||||
const title = 'Lucide';
|
||||
const socialTitle = 'Lucide Icons';
|
||||
const description = 'Beautiful & consistent icon toolkit made by the community.';
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
@@ -19,86 +19,131 @@ export default defineConfig({
|
||||
{
|
||||
find: /^.*\/VPIconAlignLeft\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/overrides/VPIconAlignLeft.vue', import.meta.url)
|
||||
)
|
||||
new URL('./theme/components/overrides/VPIconAlignLeft.vue', import.meta.url),
|
||||
),
|
||||
},
|
||||
{
|
||||
find: /^.*\/VPFooter\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/overrides/VPFooter.vue', import.meta.url)
|
||||
)
|
||||
}
|
||||
]
|
||||
new URL('./theme/components/overrides/VPFooter.vue', import.meta.url),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
head: [
|
||||
[ 'script', {
|
||||
src: 'https://analytics.lucide.dev/js/script.js',
|
||||
'data-domain': 'lucide.dev',
|
||||
defer: ''
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:locale",
|
||||
content:"en_US"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:type",
|
||||
content:"website"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:site_name",
|
||||
content: title,
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:title",
|
||||
content: socialTitle,
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:description",
|
||||
content: description
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:url",
|
||||
content:"https://lucide.dev"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:image",
|
||||
content: "https://lucide.dev/og.png"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:image:width",
|
||||
content:"1200"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:image:height",
|
||||
content:"630"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"og:image:type",
|
||||
content:"image/png"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"twitter:card",
|
||||
content:"summary_large_image"
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"twitter:title",
|
||||
content: socialTitle,
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"twitter:description",
|
||||
content: description
|
||||
}],
|
||||
[ 'meta', {
|
||||
property:"twitter:image",
|
||||
content:"https://lucide.dev/og.png"
|
||||
}],
|
||||
[
|
||||
'script',
|
||||
{
|
||||
src: 'https://analytics.lucide.dev/js/script.js',
|
||||
'data-domain': 'lucide.dev',
|
||||
defer: '',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:locale',
|
||||
content: 'en_US',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:type',
|
||||
content: 'website',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:site_name',
|
||||
content: title,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:title',
|
||||
content: socialTitle,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:description',
|
||||
content: description,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:url',
|
||||
content: 'https://lucide.dev',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image',
|
||||
content: 'https://lucide.dev/og.png',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image:width',
|
||||
content: '1200',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image:height',
|
||||
content: '630',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image:type',
|
||||
content: 'image/png',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:card',
|
||||
content: 'summary_large_image',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:title',
|
||||
content: socialTitle,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:description',
|
||||
content: description,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:image',
|
||||
content: 'https://lucide.dev/og.png',
|
||||
},
|
||||
],
|
||||
],
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
logo: {
|
||||
light: '/logo.light.svg',
|
||||
dark: '/logo.dark.svg'
|
||||
dark: '/logo.dark.svg',
|
||||
},
|
||||
nav: [
|
||||
{ text: 'Icons', link: '/icons/' },
|
||||
@@ -110,21 +155,21 @@ export default defineConfig({
|
||||
sidebar,
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://github.com/lucide-icons/lucide' },
|
||||
{ icon: 'discord', link: 'https://discord.gg/EH6nSts' }
|
||||
{ icon: 'discord', link: 'https://discord.gg/EH6nSts' },
|
||||
],
|
||||
footer: {
|
||||
message: 'Released under the ISC License.',
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`,
|
||||
},
|
||||
editLink: {
|
||||
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path'
|
||||
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path',
|
||||
},
|
||||
carbonAds: {
|
||||
code: 'CWYIC53U',
|
||||
placement: 'lucidedev'
|
||||
}
|
||||
placement: 'lucidedev',
|
||||
},
|
||||
},
|
||||
sitemap: {
|
||||
hostname: 'https://lucide.dev/'
|
||||
}
|
||||
})
|
||||
hostname: 'https://lucide.dev/',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,87 +3,171 @@
|
||||
"order": 0,
|
||||
"icon": "js",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide", "href": "https://www.npmjs.com/package/lucide" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide", "href": "https://www.npmjs.com/package/lucide" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide",
|
||||
"href": "https://www.npmjs.com/package/lucide"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide",
|
||||
"href": "https://www.npmjs.com/package/lucide"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-react": {
|
||||
"order": 1,
|
||||
"icon": "react",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-react", "href": "https://www.npmjs.com/package/lucide-react" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-react", "href": "https://www.npmjs.com/package/lucide-react" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-react",
|
||||
"href": "https://www.npmjs.com/package/lucide-react"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-react",
|
||||
"href": "https://www.npmjs.com/package/lucide-react"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-vue": {
|
||||
"order": 2,
|
||||
"icon": "vue",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-vue", "href": "https://www.npmjs.com/package/lucide-vue" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-vue", "href": "https://www.npmjs.com/package/lucide-vue" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-vue",
|
||||
"href": "https://www.npmjs.com/package/lucide-vue"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-vue",
|
||||
"href": "https://www.npmjs.com/package/lucide-vue"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-vue-next": {
|
||||
"order": 3,
|
||||
"icon": "vue-next",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-vue-next", "href": "https://www.npmjs.com/package/lucide-vue-next" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-vue-next", "href": "https://www.npmjs.com/package/lucide-vue-next" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-vue-next",
|
||||
"href": "https://www.npmjs.com/package/lucide-vue-next"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-vue-next",
|
||||
"href": "https://www.npmjs.com/package/lucide-vue-next"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-svelte": {
|
||||
"order": 4,
|
||||
"icon": "svelte",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-svelte", "href": "https://www.npmjs.com/package/lucide-svelte" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-svelte", "href": "https://www.npmjs.com/package/lucide-svelte" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-svelte",
|
||||
"href": "https://www.npmjs.com/package/lucide-svelte"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-svelte",
|
||||
"href": "https://www.npmjs.com/package/lucide-svelte"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-solid": {
|
||||
"order": 4,
|
||||
"icon": "solid",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-solid", "href": "https://www.npmjs.com/package/lucide-solid" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-solid", "href": "https://www.npmjs.com/package/lucide-solid" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-solid",
|
||||
"href": "https://www.npmjs.com/package/lucide-solid"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-solid",
|
||||
"href": "https://www.npmjs.com/package/lucide-solid"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-preact": {
|
||||
"order": 5,
|
||||
"icon": "preact",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-preact", "href": "https://www.npmjs.com/package/lucide-preact" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-preact", "href": "https://www.npmjs.com/package/lucide-preact" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-preact",
|
||||
"href": "https://www.npmjs.com/package/lucide-preact"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-preact",
|
||||
"href": "https://www.npmjs.com/package/lucide-preact"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-react-native": {
|
||||
"order": 6,
|
||||
"icon": "react-native",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-react-native", "href": "https://www.npmjs.com/package/lucide-react-native" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-react-native", "href": "https://www.npmjs.com/package/lucide-react-native" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-react-native",
|
||||
"href": "https://www.npmjs.com/package/lucide-react-native"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-react-native",
|
||||
"href": "https://www.npmjs.com/package/lucide-react-native"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-angular": {
|
||||
"order": 7,
|
||||
"icon": "angular",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-angular", "href": "https://www.npmjs.com/package/lucide-angular" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-angular", "href": "https://www.npmjs.com/package/lucide-angular" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-angular",
|
||||
"href": "https://www.npmjs.com/package/lucide-angular"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-angular",
|
||||
"href": "https://www.npmjs.com/package/lucide-angular"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-static": {
|
||||
"order": 8,
|
||||
"icon": "svg",
|
||||
"shields": [
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/v/lucide-static", "href": "https://www.npmjs.com/package/lucide-static" },
|
||||
{ "alt": "npm", "src": "https://img.shields.io/npm/dw/lucide-static", "href": "https://www.npmjs.com/package/lucide-static" }
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-static",
|
||||
"href": "https://www.npmjs.com/package/lucide-static"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-static",
|
||||
"href": "https://www.npmjs.com/package/lucide-static"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-flutter": {
|
||||
"order": 9,
|
||||
"icon": "flutter",
|
||||
"shields": [
|
||||
{ "alt": "flutter", "src": "https://img.shields.io/pub/v/lucide_icons", "href": "https://img.shields.io/pub/v/lucide_icons" }
|
||||
{
|
||||
"alt": "flutter",
|
||||
"src": "https://img.shields.io/pub/v/lucide_icons",
|
||||
"href": "https://img.shields.io/pub/v/lucide_icons"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,21 +17,62 @@ const Backdrop = ({ src, color = 'red', backdropString }: BackdropProps): JSX.El
|
||||
patternUnits="userSpaceOnUse"
|
||||
patternTransform="rotate(45 50 50)"
|
||||
>
|
||||
<line stroke={color} strokeWidth={0.1} y2={1} />
|
||||
<line stroke={color} strokeWidth={0.1} y2={1} />
|
||||
<line
|
||||
stroke={color}
|
||||
strokeWidth={0.1}
|
||||
y2={1}
|
||||
/>
|
||||
<line
|
||||
stroke={color}
|
||||
strokeWidth={0.1}
|
||||
y2={1}
|
||||
/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<mask id="svg-preview-backdrop-mask-outline" maskUnits="userSpaceOnUse">
|
||||
<g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} />
|
||||
<g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} />
|
||||
<mask
|
||||
id="svg-preview-backdrop-mask-outline"
|
||||
maskUnits="userSpaceOnUse"
|
||||
>
|
||||
<g
|
||||
stroke="#fff"
|
||||
dangerouslySetInnerHTML={{ __html: backdropString }}
|
||||
/>
|
||||
<g
|
||||
dangerouslySetInnerHTML={{ __html: src }}
|
||||
strokeWidth={2.05}
|
||||
/>
|
||||
</mask>
|
||||
<mask id="svg-preview-backdrop-mask-fill" maskUnits="userSpaceOnUse">
|
||||
<g stroke="#fff" dangerouslySetInnerHTML={{ __html: backdropString }} />
|
||||
<g dangerouslySetInnerHTML={{ __html: src }} strokeWidth={2.05} />
|
||||
<g strokeWidth={1.75} dangerouslySetInnerHTML={{ __html: backdropString }} />
|
||||
<mask
|
||||
id="svg-preview-backdrop-mask-fill"
|
||||
maskUnits="userSpaceOnUse"
|
||||
>
|
||||
<g
|
||||
stroke="#fff"
|
||||
dangerouslySetInnerHTML={{ __html: backdropString }}
|
||||
/>
|
||||
<g
|
||||
dangerouslySetInnerHTML={{ __html: src }}
|
||||
strokeWidth={2.05}
|
||||
/>
|
||||
<g
|
||||
strokeWidth={1.75}
|
||||
dangerouslySetInnerHTML={{ __html: backdropString }}
|
||||
/>
|
||||
</mask>
|
||||
<g strokeWidth={2.25} stroke="url(#pattern)" mask={'url(#svg-preview-backdrop-mask-outline)'}>
|
||||
<rect x="0" y="0" width="24" height="24" fill="url(#pattern)" opacity={0.5} stroke="none" />
|
||||
<g
|
||||
strokeWidth={2.25}
|
||||
stroke="url(#pattern)"
|
||||
mask={'url(#svg-preview-backdrop-mask-outline)'}
|
||||
>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="url(#pattern)"
|
||||
opacity={0.5}
|
||||
stroke="none"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
x="0"
|
||||
|
||||
@@ -10,7 +10,11 @@ const Grid = ({
|
||||
strokeWidth: number;
|
||||
radius: number;
|
||||
} & PathProps<'stroke', 'strokeWidth'>) => (
|
||||
<g className="svg-preview-grid-group" strokeLinecap="butt" {...props}>
|
||||
<g
|
||||
className="svg-preview-grid-group"
|
||||
strokeLinecap="butt"
|
||||
{...props}
|
||||
>
|
||||
<rect
|
||||
className="svg-preview-grid-rect"
|
||||
width={24 - props.strokeWidth}
|
||||
@@ -21,11 +25,29 @@ const Grid = ({
|
||||
fill={fill}
|
||||
/>
|
||||
<path
|
||||
strokeDasharray={'0 0.1 ' + '0.1 0.15 '.repeat(11) + '0 0.15'}
|
||||
strokeWidth={0.1}
|
||||
d={
|
||||
props.d ||
|
||||
new Array(Math.floor(24 - 1))
|
||||
.fill(null)
|
||||
.flatMap((_, i) => [
|
||||
.map((_, i) => i)
|
||||
.filter((i) => i % 3 !== 2)
|
||||
.flatMap((i) => [
|
||||
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
|
||||
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
|
||||
])
|
||||
.join('')
|
||||
}
|
||||
/>
|
||||
<path
|
||||
d={
|
||||
props.d ||
|
||||
new Array(Math.floor(24 - 1))
|
||||
.fill(null)
|
||||
.map((_, i) => i)
|
||||
.filter((i) => i % 3 === 2)
|
||||
.flatMap((i) => [
|
||||
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
|
||||
`M${i + 1} ${props.strokeWidth}v${24 - props.strokeWidth * 2}`,
|
||||
])
|
||||
@@ -44,15 +66,21 @@ const Shadow = ({
|
||||
paths: Path[];
|
||||
} & PathProps<'stroke' | 'strokeWidth' | 'strokeOpacity', 'd'>) => {
|
||||
const groupedPaths = Object.entries(
|
||||
paths.reduce((groups, val) => {
|
||||
const key = val.c.id;
|
||||
groups[key] = [...(groups[key] || []), val];
|
||||
return groups;
|
||||
}, {} as Record<number, Path[]>)
|
||||
paths.reduce(
|
||||
(groups, val) => {
|
||||
const key = val.c.id;
|
||||
groups[key] = [...(groups[key] || []), val];
|
||||
return groups;
|
||||
},
|
||||
{} as Record<number, Path[]>,
|
||||
),
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<g className="svg-preview-shadow-mask-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-shadow-mask-group"
|
||||
{...props}
|
||||
>
|
||||
{groupedPaths.map(([id, paths]) => (
|
||||
<mask
|
||||
id={`svg-preview-shadow-mask-${id}`}
|
||||
@@ -61,7 +89,15 @@ const Shadow = ({
|
||||
strokeWidth={props.strokeWidth}
|
||||
stroke="#000"
|
||||
>
|
||||
<rect x={0} y={0} width={24} height={24} fill="#fff" stroke="none" rx={radius} />
|
||||
<rect
|
||||
x={0}
|
||||
y={0}
|
||||
width={24}
|
||||
height={24}
|
||||
fill="#fff"
|
||||
stroke="none"
|
||||
rx={radius}
|
||||
/>
|
||||
<path
|
||||
d={paths
|
||||
.flatMap(({ prev, next }) => [
|
||||
@@ -74,9 +110,16 @@ const Shadow = ({
|
||||
</mask>
|
||||
))}
|
||||
</g>
|
||||
<g className="svg-preview-shadow-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-shadow-group"
|
||||
{...props}
|
||||
>
|
||||
{paths.map(({ d, c: { id } }, i) => (
|
||||
<path key={i} mask={`url(#svg-preview-shadow-mask-${id})`} d={d} />
|
||||
<path
|
||||
key={i}
|
||||
mask={`url(#svg-preview-shadow-mask-${id})`}
|
||||
d={d}
|
||||
/>
|
||||
))}
|
||||
<path
|
||||
d={paths
|
||||
@@ -94,9 +137,16 @@ const ColoredPath = ({
|
||||
paths,
|
||||
...props
|
||||
}: { paths: Path[]; colors: string[] } & PathProps<never, 'd' | 'stroke'>) => (
|
||||
<g className="svg-preview-colored-path-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-colored-path-group"
|
||||
{...props}
|
||||
>
|
||||
{paths.map(({ d, c }, i) => (
|
||||
<path key={i} d={d} stroke={colors[(c.name === 'path' ? i : c.id) % colors.length]} />
|
||||
<path
|
||||
key={i}
|
||||
d={d}
|
||||
stroke={colors[(c.name === 'path' ? i : c.id) % colors.length]}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
);
|
||||
@@ -138,7 +188,15 @@ const ControlPath = ({
|
||||
key={i}
|
||||
maskUnits="userSpaceOnUse"
|
||||
>
|
||||
<rect x="0" y="0" width="24" height="24" fill="#fff" stroke="none" rx={radius} />
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="#fff"
|
||||
stroke="none"
|
||||
rx={radius}
|
||||
/>
|
||||
<path d={`M${prev.x} ${prev.y}h.01`} />
|
||||
<path d={`M${next.x} ${next.y}h.01`} />
|
||||
</mask>
|
||||
@@ -146,7 +204,10 @@ const ControlPath = ({
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
<g className="svg-preview-control-path-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-control-path-group"
|
||||
{...props}
|
||||
>
|
||||
{controlPaths.map(({ d, showMarker }, i) => (
|
||||
<path
|
||||
key={i}
|
||||
@@ -155,18 +216,33 @@ const ControlPath = ({
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
<g className="svg-preview-control-path-marker-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-control-path-marker-group"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d={controlPaths
|
||||
.flatMap(({ prev, next, showMarker }) =>
|
||||
showMarker ? [`M${prev.x} ${prev.y}h.01`, `M${next.x} ${next.y}h.01`] : []
|
||||
showMarker ? [`M${prev.x} ${prev.y}h.01`, `M${next.x} ${next.y}h.01`] : [],
|
||||
)
|
||||
.join('')}
|
||||
/>
|
||||
{controlPaths.map(({ d, prev, next, startMarker, endMarker }, i) => (
|
||||
<React.Fragment key={i}>
|
||||
{startMarker && <circle cx={prev.x} cy={prev.y} r={pointSize / 2} />}
|
||||
{endMarker && <circle cx={next.x} cy={next.y} r={pointSize / 2} />}
|
||||
{startMarker && (
|
||||
<circle
|
||||
cx={prev.x}
|
||||
cy={prev.y}
|
||||
r={pointSize / 2}
|
||||
/>
|
||||
)}
|
||||
{endMarker && (
|
||||
<circle
|
||||
cx={next.x}
|
||||
cy={next.y}
|
||||
r={pointSize / 2}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</g>
|
||||
@@ -182,15 +258,16 @@ const Radii = ({
|
||||
any
|
||||
>) => {
|
||||
return (
|
||||
<g className="svg-preview-radii-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-radii-group"
|
||||
{...props}
|
||||
>
|
||||
{paths.map(
|
||||
({ c, prev, next, circle }, i) =>
|
||||
circle && (
|
||||
<React.Fragment key={i}>
|
||||
{c.name !== "circle" && (
|
||||
<path
|
||||
d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`}
|
||||
/>
|
||||
{c.name !== 'circle' && (
|
||||
<path d={`M${prev.x} ${prev.y} ${circle.x} ${circle.y} ${next.x} ${next.y}`} />
|
||||
)}
|
||||
<circle
|
||||
cy={circle.y}
|
||||
@@ -200,7 +277,7 @@ const Radii = ({
|
||||
stroke={
|
||||
(Math.round(circle.x * 100) / 100) % 1 !== 0 ||
|
||||
(Math.round(circle.y * 100) / 100) % 1 !== 0
|
||||
? "red"
|
||||
? 'red'
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
@@ -208,11 +285,7 @@ const Radii = ({
|
||||
cy={circle.y}
|
||||
cx={circle.x}
|
||||
r={circle.r}
|
||||
stroke={
|
||||
(Math.round(circle.r * 1000) / 1000) % 1 !== 0
|
||||
? "red"
|
||||
: undefined
|
||||
}
|
||||
stroke={(Math.round(circle.r * 1000) / 1000) % 1 !== 0 ? 'red' : undefined}
|
||||
/>
|
||||
</React.Fragment>
|
||||
),
|
||||
@@ -230,13 +303,28 @@ const Handles = ({
|
||||
>) => {
|
||||
console.log(paths);
|
||||
return (
|
||||
<g className="svg-preview-handles-group" {...props}>
|
||||
<g
|
||||
className="svg-preview-handles-group"
|
||||
{...props}
|
||||
>
|
||||
{paths.map(({ c, prev, next, cp1, cp2 }) => (
|
||||
<>
|
||||
{cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />}
|
||||
{cp1 && <circle cy={cp1.y} cx={cp1.x} r={0.25} />}
|
||||
{cp1 && (
|
||||
<circle
|
||||
cy={cp1.y}
|
||||
cx={cp1.x}
|
||||
r={0.25}
|
||||
/>
|
||||
)}
|
||||
{cp2 && <path d={`M${next.x} ${next.y} ${cp2.x} ${cp2.y}`} />}
|
||||
{cp2 && <circle cy={cp2.y} cx={cp2.x} r={0.25} />}
|
||||
{cp2 && (
|
||||
<circle
|
||||
cy={cp2.y}
|
||||
cx={cp2.x}
|
||||
r={0.25}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</g>
|
||||
@@ -280,9 +368,27 @@ const SvgPreview = React.forwardRef<
|
||||
{...props}
|
||||
>
|
||||
<style>{darkModeCss}</style>
|
||||
{showGrid && <Grid strokeWidth={0.1} stroke="#777" strokeOpacity={0.3} radius={1} />}
|
||||
<Shadow paths={paths} strokeWidth={4} stroke="#777" radius={1} strokeOpacity={0.15} />
|
||||
<Handles paths={paths} strokeWidth={0.12} stroke="#777" strokeOpacity={0.6} />
|
||||
{showGrid && (
|
||||
<Grid
|
||||
strokeWidth={0.1}
|
||||
stroke="#777"
|
||||
strokeOpacity={0.3}
|
||||
radius={1}
|
||||
/>
|
||||
)}
|
||||
<Shadow
|
||||
paths={paths}
|
||||
strokeWidth={4}
|
||||
stroke="#777"
|
||||
radius={1}
|
||||
strokeOpacity={0.15}
|
||||
/>
|
||||
<Handles
|
||||
paths={paths}
|
||||
strokeWidth={0.12}
|
||||
stroke="#777"
|
||||
strokeOpacity={0.6}
|
||||
/>
|
||||
<ColoredPath
|
||||
paths={paths}
|
||||
colors={[
|
||||
@@ -307,8 +413,19 @@ const SvgPreview = React.forwardRef<
|
||||
stroke="#777"
|
||||
strokeOpacity={0.3}
|
||||
/>
|
||||
<ControlPath radius={1} paths={paths} pointSize={1} stroke="#fff" strokeWidth={0.125} />
|
||||
<Handles paths={paths} strokeWidth={0.12} stroke="#FFF" strokeOpacity={0.3} />
|
||||
<ControlPath
|
||||
radius={1}
|
||||
paths={paths}
|
||||
pointSize={1}
|
||||
stroke="#fff"
|
||||
strokeWidth={0.125}
|
||||
/>
|
||||
<Handles
|
||||
paths={paths}
|
||||
strokeWidth={0.12}
|
||||
stroke="#FFF"
|
||||
strokeOpacity={0.3}
|
||||
/>
|
||||
{children}
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,7 @@ export type Path = {
|
||||
|
||||
export type PathProps<
|
||||
RequiredProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
|
||||
NeverProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>
|
||||
NeverProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
|
||||
> = Required<Pick<React.SVGProps<SVGElement & SVGRectElement & SVGCircleElement>, RequiredProps>> &
|
||||
Omit<
|
||||
React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>,
|
||||
|
||||
@@ -51,7 +51,7 @@ export const getCommands = (src: string) =>
|
||||
getNodes(src)
|
||||
.map(convertToPathNode)
|
||||
.flatMap(({ d, name }, idx) =>
|
||||
new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name }))
|
||||
new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })),
|
||||
);
|
||||
|
||||
export const getPaths = (src: string) => {
|
||||
@@ -60,10 +60,10 @@ export const getPaths = (src: string) => {
|
||||
let prev: Point | undefined = undefined;
|
||||
let start: Point | undefined = undefined;
|
||||
const addPath = (
|
||||
c: typeof commands[number],
|
||||
c: (typeof commands)[number],
|
||||
next: Point,
|
||||
d?: string,
|
||||
extras?: { circle?: Path['circle']; cp1?: Path['cp1']; cp2?: Path['cp2'] }
|
||||
extras?: { circle?: Path['circle']; cp1?: Path['cp1']; cp2?: Path['cp2'] },
|
||||
) => {
|
||||
assert(prev);
|
||||
paths.push({
|
||||
@@ -153,7 +153,7 @@ export const getPaths = (src: string) => {
|
||||
{
|
||||
cp1: { x: prev.x - reflectedCp1.x, y: prev.y - reflectedCp1.y },
|
||||
cp2: { x: c.x2, y: c.y2 },
|
||||
}
|
||||
},
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -169,7 +169,7 @@ export const getPaths = (src: string) => {
|
||||
assert(prev);
|
||||
const backTrackCP = (
|
||||
index: number,
|
||||
currentPoint: { x: number; y: number }
|
||||
currentPoint: { x: number; y: number },
|
||||
): { x: number; y: number } => {
|
||||
const previousCommand = commands[index - 1];
|
||||
if (!previousCommand) {
|
||||
@@ -211,7 +211,7 @@ export const getPaths = (src: string) => {
|
||||
{
|
||||
cp1: { x: prevCP.x, y: prevCP.y },
|
||||
cp2: { x: prevCP.x, y: prevCP.y },
|
||||
}
|
||||
},
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -226,13 +226,13 @@ export const getPaths = (src: string) => {
|
||||
c.lArcFlag,
|
||||
c.sweepFlag,
|
||||
c.x,
|
||||
c.y
|
||||
c.y,
|
||||
);
|
||||
addPath(
|
||||
c,
|
||||
c,
|
||||
`M ${prev.x} ${prev.y} A${c.rX} ${c.rY} ${c.xRot} ${c.lArcFlag} ${c.sweepFlag} ${c.x} ${c.y}`,
|
||||
{ circle: c.rX === c.rY ? { ...center, r: c.rX } : undefined }
|
||||
{ circle: c.rX === c.rY ? { ...center, r: c.rX } : undefined },
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -253,7 +253,7 @@ export const arcEllipseCenter = (
|
||||
fa: number,
|
||||
fs: number,
|
||||
x2: number,
|
||||
y2: number
|
||||
y2: number,
|
||||
) => {
|
||||
const phi = (a * Math.PI) / 180;
|
||||
|
||||
@@ -280,7 +280,7 @@ export const arcEllipseCenter = (
|
||||
sign *
|
||||
Math.sqrt(
|
||||
Math.max(rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p, 0) /
|
||||
(rx * rx * y1p * y1p + ry * ry * x1p * x1p)
|
||||
(rx * rx * y1p * y1p + ry * ry * x1p * x1p),
|
||||
);
|
||||
|
||||
const V2 = [(rx * y1p) / ry, (-ry * x1p) / rx];
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import {Category, IconEntity} from "../theme/types";
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { Category, IconEntity } from '../theme/types';
|
||||
|
||||
const directory = path.join(process.cwd(), "../categories");
|
||||
const directory = path.join(process.cwd(), '../categories');
|
||||
|
||||
export function getAllCategoryFiles(): Category[] {
|
||||
const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json');
|
||||
|
||||
return fileNames.map((fileName) => {
|
||||
const name = path.basename(fileName, '.json')
|
||||
const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8')
|
||||
const name = path.basename(fileName, '.json');
|
||||
const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8');
|
||||
|
||||
const parsedFileContent = JSON.parse(fileContent)
|
||||
const parsedFileContent = JSON.parse(fileContent);
|
||||
|
||||
return {
|
||||
name,
|
||||
title: parsedFileContent.title,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function mapCategoryIconCount(categories: Category[], icons: { categories: IconEntity['categories'] }[]) {
|
||||
export function mapCategoryIconCount(
|
||||
categories: Category[],
|
||||
icons: { categories: IconEntity['categories'] }[],
|
||||
) {
|
||||
return categories.map((category) => ({
|
||||
...category,
|
||||
iconCount: icons.reduce((acc, curr) => (curr.categories.includes(category.name) ? ++acc : acc), 0)
|
||||
}))
|
||||
iconCount: icons.reduce(
|
||||
(acc, curr) => (curr.categories.includes(category.name) ? ++acc : acc),
|
||||
0,
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
import {
|
||||
bundledLanguages,
|
||||
type ThemeRegistration
|
||||
} from 'shikiji'
|
||||
import {
|
||||
getHighlighter,
|
||||
} from 'shikiji'
|
||||
|
||||
import { bundledLanguages, type ThemeRegistration } from 'shikiji';
|
||||
import { getHighlighter } from 'shikiji';
|
||||
|
||||
type CodeExampleType = {
|
||||
title: string,
|
||||
language: string,
|
||||
code: string,
|
||||
}[]
|
||||
title: string;
|
||||
language: string;
|
||||
code: string;
|
||||
}[];
|
||||
|
||||
const getIconCodes = (): CodeExampleType => {
|
||||
return [
|
||||
{
|
||||
language: 'html',
|
||||
title: 'HTML',
|
||||
code: `<i data-lucide="Name"></i>`
|
||||
code: `<i data-lucide="Name"></i>`,
|
||||
},
|
||||
{
|
||||
language: 'tsx',
|
||||
@@ -109,36 +103,37 @@ import { LucideAngularModule, PascalCase } from 'lucide-angular';
|
||||
|
||||
<div class="icon-Name"></div>
|
||||
`,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export type ThemeOptions =
|
||||
| ThemeRegistration
|
||||
| { light: ThemeRegistration; dark: ThemeRegistration }
|
||||
| { light: ThemeRegistration; dark: ThemeRegistration };
|
||||
|
||||
const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
||||
const highlighter = await getHighlighter({
|
||||
themes: ['github-light', 'github-dark'],
|
||||
langs: Object.keys(bundledLanguages)
|
||||
})
|
||||
langs: Object.keys(bundledLanguages),
|
||||
});
|
||||
|
||||
const highlightedCode = highlighter.codeToHtml(code, {
|
||||
lang,
|
||||
themes: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark'
|
||||
},
|
||||
defaultColor: false
|
||||
}).replace('shiki-themes', 'shiki-themes vp-code')
|
||||
const highlightedCode = highlighter
|
||||
.codeToHtml(code, {
|
||||
lang,
|
||||
themes: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark',
|
||||
},
|
||||
defaultColor: false,
|
||||
})
|
||||
.replace('shiki-themes', 'shiki-themes vp-code');
|
||||
|
||||
return `<div class="language-${lang} ${active ? 'active' : ''}">
|
||||
<button title="Copy Code" class="copy"></button>
|
||||
<span class="lang">${lang}</span>
|
||||
${highlightedCode}
|
||||
</div>`
|
||||
}
|
||||
|
||||
</div>`;
|
||||
};
|
||||
|
||||
export default async function createCodeExamples() {
|
||||
const codes = getIconCodes();
|
||||
@@ -153,7 +148,7 @@ export default async function createCodeExamples() {
|
||||
language: language,
|
||||
code: codeString,
|
||||
};
|
||||
})
|
||||
});
|
||||
|
||||
return Promise.all(codeExamplePromises);
|
||||
}
|
||||
|
||||
@@ -1,38 +1,42 @@
|
||||
import { promises as fs, constants } from 'fs';
|
||||
import path from 'path';
|
||||
import yaml from 'js-yaml'
|
||||
import yaml from 'js-yaml';
|
||||
import { PackageItem } from '../theme/types';
|
||||
|
||||
const fileExist = (filePath) => fs.access(filePath, constants.F_OK).then(() => true).catch(() => false)
|
||||
const fileExist = (filePath) =>
|
||||
fs
|
||||
.access(filePath, constants.F_OK)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
const fetchPackages = async (): Promise<PackageItem[]> => {
|
||||
const docsDir = path.resolve(process.cwd(), '../packages');
|
||||
const fileNames = await (await fs.readdir(docsDir)).map(filename => ({filename, directory: docsDir}))
|
||||
const fileNames = await (
|
||||
await fs.readdir(docsDir)
|
||||
).map((filename) => ({ filename, directory: docsDir }));
|
||||
|
||||
const packageJsons = await Promise.all(fileNames.map( async ({filename, directory}) => {
|
||||
const filePath = path.resolve(directory, filename)
|
||||
const fileStat = await fs.lstat(filePath);
|
||||
const packageJsons = await Promise.all(
|
||||
fileNames.map(async ({ filename, directory }) => {
|
||||
const filePath = path.resolve(directory, filename);
|
||||
const fileStat = await fs.lstat(filePath);
|
||||
|
||||
if(!fileStat.isDirectory()) return null;
|
||||
if (!fileStat.isDirectory()) return null;
|
||||
|
||||
const jsonFilePath = path.resolve(filePath, 'package.json')
|
||||
if (await fileExist(jsonFilePath)) {
|
||||
return JSON.parse(
|
||||
await fs.readFile(jsonFilePath, 'utf-8')
|
||||
)
|
||||
}
|
||||
const jsonFilePath = path.resolve(filePath, 'package.json');
|
||||
if (await fileExist(jsonFilePath)) {
|
||||
return JSON.parse(await fs.readFile(jsonFilePath, 'utf-8'));
|
||||
}
|
||||
|
||||
const ymlFilePath = path.resolve(filePath, 'pubspec.yaml')
|
||||
if(await fileExist(ymlFilePath)) {
|
||||
return yaml.load(
|
||||
await fs.readFile(ymlFilePath, 'utf-8')
|
||||
);
|
||||
}
|
||||
const ymlFilePath = path.resolve(filePath, 'pubspec.yaml');
|
||||
if (await fileExist(ymlFilePath)) {
|
||||
return yaml.load(await fs.readFile(ymlFilePath, 'utf-8'));
|
||||
}
|
||||
|
||||
return null
|
||||
}))
|
||||
return null;
|
||||
}),
|
||||
);
|
||||
|
||||
return packageJsons
|
||||
}
|
||||
return packageJsons;
|
||||
};
|
||||
|
||||
export default fetchPackages;
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
export type IconContent = [icon: string, src:string];
|
||||
export type IconContent = [icon: string, src: string];
|
||||
|
||||
async function generateZip(icons: IconContent[]) {
|
||||
const JSZip = (await import('jszip')).default
|
||||
const JSZip = (await import('jszip')).default;
|
||||
|
||||
const zip = new JSZip();
|
||||
|
||||
const addingZipPromises = icons.map(([name, src]) =>
|
||||
zip.file(`${name}.svg`, src),
|
||||
);
|
||||
const addingZipPromises = icons.map(([name, src]) => zip.file(`${name}.svg`, src));
|
||||
|
||||
await Promise.all(addingZipPromises)
|
||||
await Promise.all(addingZipPromises);
|
||||
|
||||
return zip.generateAsync({ type: 'blob' });
|
||||
}
|
||||
|
||||
export default generateZip
|
||||
export default generateZip;
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { createLucideIcon } from "lucide-react/src/lucide-react"
|
||||
import { type LucideProps, type IconNode } from "lucide-react/src/createLucideIcon"
|
||||
import { IconEntity } from "../theme/types"
|
||||
import { createLucideIcon } from 'lucide-react/src/lucide-react';
|
||||
import { type LucideProps, type IconNode } from 'lucide-react/src/createLucideIcon';
|
||||
import { IconEntity } from '../theme/types';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import { IconContent } from "./generateZip";
|
||||
import { IconContent } from './generateZip';
|
||||
|
||||
const getFallbackZip = (icons: IconEntity[], params: LucideProps) => {
|
||||
return icons
|
||||
.map<IconContent>((icon) => {
|
||||
const Icon = createLucideIcon(icon.name, icon.iconNode as IconNode)
|
||||
const src = renderToStaticMarkup(<Icon {...params} />)
|
||||
return [icon.name, src]
|
||||
})
|
||||
}
|
||||
return icons.map<IconContent>((icon) => {
|
||||
const Icon = createLucideIcon(icon.name, icon.iconNode as IconNode);
|
||||
const src = renderToStaticMarkup(<Icon {...params} />);
|
||||
return [icon.name, src];
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export default getFallbackZip
|
||||
export default getFallbackZip;
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { IconNodeWithKeys } from "../theme/types";
|
||||
import iconNodes from '../data/iconNodes'
|
||||
import releaseMeta from "../data/releaseMetaData.json";
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { IconNodeWithKeys } from '../theme/types';
|
||||
import iconNodes from '../data/iconNodes';
|
||||
import releaseMeta from '../data/releaseMetaData.json';
|
||||
|
||||
const DATE_OF_FORK = '2020-06-08T16:39:52+0100';
|
||||
|
||||
const directory = path.join(process.cwd(), "../icons");
|
||||
const directory = path.join(process.cwd(), '../icons');
|
||||
|
||||
export interface GetDataOptions {
|
||||
withChildKeys?: boolean
|
||||
withChildKeys?: boolean;
|
||||
}
|
||||
|
||||
export async function getData(name: string) {
|
||||
const jsonPath = path.join(directory, `${name}.json`);
|
||||
const jsonContent = fs.readFileSync(jsonPath, "utf8");
|
||||
const jsonContent = fs.readFileSync(jsonPath, 'utf8');
|
||||
const { tags, categories, contributors } = JSON.parse(jsonContent);
|
||||
|
||||
const iconNode = iconNodes[name]
|
||||
const iconNode = iconNodes[name];
|
||||
|
||||
const releaseData = releaseMeta?.[name] ?? {
|
||||
"createdRelease": {
|
||||
"version": "0.0.0",
|
||||
"date": DATE_OF_FORK
|
||||
createdRelease: {
|
||||
version: '0.0.0',
|
||||
date: DATE_OF_FORK,
|
||||
},
|
||||
"changedRelease": {
|
||||
"version": "0.0.0",
|
||||
"date": DATE_OF_FORK
|
||||
}
|
||||
}
|
||||
changedRelease: {
|
||||
version: '0.0.0',
|
||||
date: DATE_OF_FORK,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
name,
|
||||
@@ -36,11 +36,11 @@ export async function getData(name: string) {
|
||||
categories,
|
||||
iconNode,
|
||||
contributors,
|
||||
...releaseData
|
||||
...releaseData,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getAllData(): Promise<{ name: string, iconNode: IconNodeWithKeys}[]> {
|
||||
export async function getAllData(): Promise<{ name: string; iconNode: IconNodeWithKeys }[]> {
|
||||
const names = Object.keys(iconNodes);
|
||||
|
||||
return Promise.all(names.map((name) => getData(name)));
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import { DefaultTheme, UserConfig } from "vitepress"
|
||||
import { DefaultTheme, UserConfig } from 'vitepress';
|
||||
|
||||
const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
|
||||
'guide':[
|
||||
guide: [
|
||||
{
|
||||
text: 'Introduction',
|
||||
items: [
|
||||
{ text: 'What is lucide?', link: '/guide/' },
|
||||
{ text: 'Installation', link: '/guide/installation' },
|
||||
{ text: 'Comparison', link: '/guide/comparison' }
|
||||
]
|
||||
{ text: 'Comparison', link: '/guide/comparison' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Basics',
|
||||
items: [
|
||||
{
|
||||
text: 'Color',
|
||||
link: '/guide/basics/color'
|
||||
link: '/guide/basics/color',
|
||||
},
|
||||
{
|
||||
text: 'Sizing',
|
||||
link: '/guide/basics/sizing'
|
||||
link: '/guide/basics/sizing',
|
||||
},
|
||||
{
|
||||
text: 'Stroke width',
|
||||
link: '/guide/basics/stroke-width'
|
||||
link: '/guide/basics/stroke-width',
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
// TODO: Add this section
|
||||
{
|
||||
@@ -37,14 +37,14 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
|
||||
// },
|
||||
{
|
||||
text: 'Global styling',
|
||||
link: '/guide/advanced/global-styling'
|
||||
link: '/guide/advanced/global-styling',
|
||||
},
|
||||
// {
|
||||
// text: 'Animations',
|
||||
// },
|
||||
{
|
||||
text: 'Filled icons',
|
||||
link: '/guide/advanced/filled-icons'
|
||||
link: '/guide/advanced/filled-icons',
|
||||
},
|
||||
// {
|
||||
// text: 'Combining icons',
|
||||
@@ -55,75 +55,73 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
|
||||
// {
|
||||
// text: 'Auto importing'
|
||||
// },
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Packages',
|
||||
items: [
|
||||
{
|
||||
text: 'Lucide',
|
||||
link: '/guide/packages/lucide'
|
||||
link: '/guide/packages/lucide',
|
||||
},
|
||||
{
|
||||
text: 'Lucide React',
|
||||
link: '/guide/packages/lucide-react'
|
||||
link: '/guide/packages/lucide-react',
|
||||
},
|
||||
{
|
||||
text: 'Lucide React Native',
|
||||
link: '/guide/packages/lucide-react-native'
|
||||
link: '/guide/packages/lucide-react-native',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Vue',
|
||||
link: '/guide/packages/lucide-vue-next'
|
||||
link: '/guide/packages/lucide-vue-next',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Svelte',
|
||||
link: '/guide/packages/lucide-svelte'
|
||||
link: '/guide/packages/lucide-svelte',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Solid',
|
||||
link: '/guide/packages/lucide-solid'
|
||||
link: '/guide/packages/lucide-solid',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Preact',
|
||||
link: '/guide/packages/lucide-preact'
|
||||
link: '/guide/packages/lucide-preact',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Angular',
|
||||
link: '/guide/packages/lucide-angular'
|
||||
link: '/guide/packages/lucide-angular',
|
||||
},
|
||||
{
|
||||
text: 'Lucide Static',
|
||||
link: '/guide/packages/lucide-static'
|
||||
link: '/guide/packages/lucide-static',
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Contributing',
|
||||
items: [
|
||||
{
|
||||
text: 'Icon Design Principles',
|
||||
link: '/guide/design/icon-design-guide'
|
||||
link: '/guide/design/icon-design-guide',
|
||||
},
|
||||
{
|
||||
text: 'Designing in Illustrator',
|
||||
link: '/guide/design/illustrator-guide'
|
||||
link: '/guide/design/illustrator-guide',
|
||||
},
|
||||
{
|
||||
text: 'Designing in InkScape',
|
||||
link: '/guide/design/inkscape-guide'
|
||||
link: '/guide/design/inkscape-guide',
|
||||
},
|
||||
{
|
||||
text: 'Designing in Figma',
|
||||
link: '/guide/design/figma-guide'
|
||||
link: '/guide/design/figma-guide',
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
],
|
||||
// This should be here to keep the sidebar shown on the icons page
|
||||
'icons': [
|
||||
{ text: '', link: '/' },
|
||||
],
|
||||
}
|
||||
icons: [{ text: '', link: '/' }],
|
||||
};
|
||||
|
||||
export default sidebar
|
||||
export default sidebar;
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
export default {
|
||||
async load() {
|
||||
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest').then(res => {
|
||||
if (res.ok) {
|
||||
const releaseData = res.json() as Promise<{ tag_name: string }>
|
||||
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest')
|
||||
.then((res) => {
|
||||
if (res.ok) {
|
||||
const releaseData = res.json() as Promise<{ tag_name: string }>;
|
||||
|
||||
return releaseData
|
||||
}
|
||||
return null
|
||||
}).then(res => res.tag_name)
|
||||
return releaseData;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.then((res) => res.tag_name);
|
||||
|
||||
return {
|
||||
version
|
||||
}
|
||||
}
|
||||
}
|
||||
version,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,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 {
|
||||
async load() {
|
||||
const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode }))
|
||||
const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode }));
|
||||
|
||||
const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons))
|
||||
const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons));
|
||||
|
||||
return {
|
||||
icons: randomIcons,
|
||||
iconsCount: icons.length,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -47,7 +47,7 @@ export default {
|
||||
logo: '/framework-logos/flutter.svg',
|
||||
label: 'Lucide documentation for Flutter',
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { getAllData } from '../../../lib/icons';
|
||||
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
|
||||
import iconsMetaData from '../../../data/iconMetaData'
|
||||
|
||||
import iconsMetaData from '../../../data/iconMetaData';
|
||||
|
||||
export default {
|
||||
async load() {
|
||||
let categories = getAllCategoryFiles()
|
||||
let categories = getAllCategoryFiles();
|
||||
|
||||
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData))
|
||||
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData));
|
||||
|
||||
return {
|
||||
categories,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useData } from 'vitepress'
|
||||
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
|
||||
import { isActive } from 'vitepress/dist/client/shared'
|
||||
import { useActiveAnchor } from '../../composables/useActiveAnchor'
|
||||
import { data } from './CategoryList.data'
|
||||
import CategoryListItem from './CategoryListItem.vue'
|
||||
import { useCategoryView } from '../../composables/useCategoryView'
|
||||
|
||||
const { page } = useData()
|
||||
const { categoryCounts } = useCategoryView();
|
||||
|
||||
const categoriesIsActive = computed(() => {
|
||||
return isActive(page.value.relativePath, '/icons/categories');
|
||||
@@ -25,14 +27,21 @@ const headers = computed(() => {
|
||||
level: 2,
|
||||
link: `${linkPrefix}#${name}`,
|
||||
title,
|
||||
iconCount
|
||||
iconCount: categoryCounts.value[name] ?? iconCount,
|
||||
name
|
||||
}))
|
||||
})
|
||||
|
||||
const container = ref()
|
||||
const marker = ref()
|
||||
|
||||
useActiveAnchor(container, marker)
|
||||
const { setActiveLinkDebounced } = useActiveAnchor(container, marker)
|
||||
|
||||
watch(headers, () => {
|
||||
setTimeout(() => {
|
||||
setActiveLinkDebounced()
|
||||
}, 200)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -7,6 +7,7 @@ interface Header {
|
||||
slug: string;
|
||||
iconCount: number;
|
||||
link: string;
|
||||
name: string;
|
||||
children: Header[];
|
||||
}
|
||||
|
||||
@@ -14,36 +15,35 @@ type MenuItem = Omit<Header, 'slug' | 'children'> & {
|
||||
children?: MenuItem[];
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
headers: MenuItem[];
|
||||
root?: boolean;
|
||||
}>();
|
||||
|
||||
const { selectedCategory } = useCategoryView();
|
||||
|
||||
function onClick(event: Event) {
|
||||
const target =
|
||||
(event.target as HTMLElement).nodeName === 'span'
|
||||
? (event.target as HTMLElement).parentNode
|
||||
: (event.target as HTMLElement);
|
||||
const href = (target as HTMLAnchorElement)?.href;
|
||||
function onClick(categoryName: string) {
|
||||
selectedCategory.value = categoryName;
|
||||
|
||||
if (href) {
|
||||
const id = '#' + href.split('#')[1];
|
||||
const decodedId = decodeURIComponent(id);
|
||||
const heading = document.querySelector<HTMLAnchorElement>(categoryName);
|
||||
heading?.focus();
|
||||
|
||||
selectedCategory.value = decodedId.replace('#', '');
|
||||
|
||||
const heading = document.querySelector<HTMLAnchorElement>(decodedId);
|
||||
heading?.focus();
|
||||
}
|
||||
window.history.pushState({}, '', `/icons/categories#${categoryName}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul :class="root ? 'root' : 'nested'">
|
||||
<li v-for="{ children, link, title, iconCount } in headers">
|
||||
<a class="outline-link" :href="link" @click="onClick" :title="title">
|
||||
<li v-for="{ children, link, title, iconCount, name } in headers">
|
||||
<a
|
||||
class="outline-link"
|
||||
:href="link"
|
||||
@click="onClick(name)"
|
||||
:title="title"
|
||||
:class="{
|
||||
inactive: iconCount === 0,
|
||||
}"
|
||||
>
|
||||
<span>
|
||||
{{ title }}
|
||||
</span>
|
||||
@@ -83,6 +83,10 @@ function onClick(event: Event) {
|
||||
transition: color 0.25s;
|
||||
}
|
||||
|
||||
.outline-link.inactive {
|
||||
color: var(--vp-c-text-4);
|
||||
}
|
||||
|
||||
.outline-link.nested {
|
||||
padding-left: 13px;
|
||||
}
|
||||
@@ -93,4 +97,8 @@ function onClick(event: Event) {
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.outline-link.inactive .icon-count {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,7 @@ import IconItem from './IconItem.vue'
|
||||
|
||||
const emit = defineEmits(['setActiveIcon'])
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
icons: IconEntity[]
|
||||
activeIcon?: string
|
||||
overlayMode?: boolean
|
||||
@@ -40,7 +40,6 @@ function setActiveIcon(name: string) {
|
||||
.icons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
|
||||
/* padding: 32px 32px 96px; */
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,56 +1,53 @@
|
||||
<script lang="ts">
|
||||
import { IconEntity } from '../../types';
|
||||
|
||||
type CategoryNameRow = {
|
||||
type: 'category';
|
||||
title: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type CategoryIconsRow = {
|
||||
type: 'icons';
|
||||
icons: IconEntity[];
|
||||
};
|
||||
|
||||
export type CategoryRow = CategoryNameRow | CategoryIconsRow;
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Category } from '../../types';
|
||||
import IconGrid from './IconGrid.vue'
|
||||
import { vIntersectionObserver } from '@vueuse/components'
|
||||
|
||||
defineProps<{
|
||||
activeIconName: string
|
||||
category: Category
|
||||
categoryRow: CategoryRow
|
||||
}>()
|
||||
|
||||
|
||||
const emit = defineEmits(['setActiveIcon'])
|
||||
|
||||
const showIcons = ref(false)
|
||||
|
||||
// Added intersection observer to improve performance
|
||||
const onIntersectionObserver: IntersectionObserverCallback = ([{ isIntersecting }]) => {
|
||||
showIcons.value = isIntersecting
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="category"
|
||||
:key="category.name"
|
||||
:id="category.name"
|
||||
v-intersection-observer="onIntersectionObserver"
|
||||
<h2
|
||||
v-if="categoryRow.type === 'category'"
|
||||
class="title"
|
||||
>
|
||||
<h2 class="title" >
|
||||
<a class="header-anchor" :href="`#${category.name}`" :aria-label="`Permalink to "${category.title}"`">​</a>
|
||||
{{ category.title }}
|
||||
</h2>
|
||||
<IconGrid
|
||||
:activeIcon="activeIconName"
|
||||
:icons="category.icons"
|
||||
@setActiveIcon="$event => $emit('setActiveIcon', $event)"
|
||||
overlayMode
|
||||
:hideIcons="!showIcons"
|
||||
/>
|
||||
</section>
|
||||
<a class="header-anchor" :href="`#${categoryRow.name}`" :aria-label="`Permalink to "${categoryRow.title}"`">​</a>
|
||||
{{ categoryRow.title }}
|
||||
</h2>
|
||||
<IconGrid
|
||||
v-else-if="categoryRow.type === 'icons'"
|
||||
:activeIcon="activeIconName"
|
||||
:icons="categoryRow.icons"
|
||||
@setActiveIcon="$event => $emit('setActiveIcon', $event)"
|
||||
overlayMode
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.title {
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 19px;
|
||||
font-weight: 500;
|
||||
padding-top: 86px;
|
||||
/* scroll-padding-top: 240px; */
|
||||
}
|
||||
|
||||
.category {
|
||||
margin-bottom: calc(-86px + 32px);
|
||||
padding: 24px 0 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, defineAsyncComponent } from 'vue';
|
||||
import { ref, computed, defineAsyncComponent, onMounted } from 'vue';
|
||||
import type { IconEntity, Category } from '../../types';
|
||||
import useSearch from '../../composables/useSearch';
|
||||
import InputSearch from '../base/InputSearch.vue';
|
||||
@@ -8,6 +8,13 @@ import StickyBar from './StickyBar.vue';
|
||||
import IconsCategory from './IconsCategory.vue';
|
||||
import useFetchTags from '../../composables/useFetchTags';
|
||||
import useFetchCategories from '../../composables/useFetchCategories';
|
||||
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
||||
import chunkArray from '../../utils/chunkArray';
|
||||
import { CategoryRow } from './IconsCategory.vue';
|
||||
import useScrollToCategory from '../../composables/useScrollToCategory';
|
||||
|
||||
const ICON_SIZE = 56;
|
||||
const ICON_GRID_GAP = 8;
|
||||
|
||||
const props = defineProps<{
|
||||
icons: IconEntity[];
|
||||
@@ -17,7 +24,6 @@ const props = defineProps<{
|
||||
|
||||
const activeIconName = ref(null);
|
||||
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
||||
|
||||
const isSearching = computed(() => !!searchQuery.value);
|
||||
|
||||
function setActiveIconName(name: string) {
|
||||
@@ -27,6 +33,13 @@ function setActiveIconName(name: string) {
|
||||
const { execute: fetchTags, data: tags } = useFetchTags();
|
||||
const { execute: fetchCategories, data: categoriesMap } = useFetchCategories();
|
||||
|
||||
const overviewEl = ref<HTMLElement | null>(null);
|
||||
const { width: containerWidth } = useElementSize(overviewEl)
|
||||
|
||||
const columnSize = computed(() => {
|
||||
return Math.floor((containerWidth.value) / ((ICON_SIZE + ICON_GRID_GAP)));
|
||||
});
|
||||
|
||||
const mappedIcons = computed(() => {
|
||||
if (tags.value == null) {
|
||||
return props.icons;
|
||||
@@ -43,9 +56,10 @@ const mappedIcons = computed(() => {
|
||||
});
|
||||
});
|
||||
|
||||
const searchResults = useSearch(searchQuery, mappedIcons, [
|
||||
{ name: 'name', weight: 2 },
|
||||
{ name: 'tags', weight: 1 },
|
||||
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
|
||||
{ name: 'name', weight: 3 },
|
||||
{ name: 'aliases', weight: 3 },
|
||||
{ name: 'tags', weight: 2 },
|
||||
]);
|
||||
|
||||
const categories = computed(() => {
|
||||
@@ -71,9 +85,43 @@ const categories = computed(() => {
|
||||
icons: searchedCategoryIcons,
|
||||
};
|
||||
})
|
||||
.filter(({ icons }) => icons.length);
|
||||
});
|
||||
|
||||
const categoriesList = computed(() => {
|
||||
return categories.value
|
||||
.filter(({ icons }) => icons.length)
|
||||
.reduce<CategoryRow[]>((acc, category) => {
|
||||
acc.push({ type: 'category', title: category.title, name: category.name });
|
||||
|
||||
const categoryIcons = chunkArray(category.icons, columnSize.value);
|
||||
categoryIcons.forEach((icons) => {
|
||||
acc.push({ type: 'icons', icons });
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
});
|
||||
|
||||
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
|
||||
categoriesList,
|
||||
{
|
||||
itemHeight: ICON_SIZE + ICON_GRID_GAP,
|
||||
overscan: 10
|
||||
},
|
||||
)
|
||||
|
||||
useScrollToCategory({
|
||||
categories,
|
||||
categoriesList,
|
||||
scrollTo,
|
||||
searchQueryDebounced,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
containerProps.ref.value = document.documentElement;
|
||||
useEventListener(window, 'scroll', containerProps.onScroll)
|
||||
})
|
||||
|
||||
function onFocusSearchInput() {
|
||||
if (tags.value == null) {
|
||||
fetchTags();
|
||||
@@ -84,28 +132,35 @@ function onFocusSearchInput() {
|
||||
}
|
||||
|
||||
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
||||
|
||||
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StickyBar class="search-bar category-search">
|
||||
<InputSearch
|
||||
:placeholder="`Search ${icons.length} icons ...`"
|
||||
v-model="searchQuery"
|
||||
class="input-wrapper"
|
||||
ref="searchInput"
|
||||
@focus="onFocusSearchInput"
|
||||
<div ref="overviewEl" class="overview-container">
|
||||
<StickyBar class="category-search">
|
||||
<InputSearch
|
||||
:placeholder="`Search ${icons.length} icons ...`"
|
||||
v-model="searchQuery"
|
||||
class="input-wrapper"
|
||||
ref="searchInput"
|
||||
@focus="onFocusSearchInput"
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="categories.length === 0"
|
||||
:searchQuery="searchQuery"
|
||||
@clear="searchQuery = ''"
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults v-if="categories.length === 0" :searchQuery="searchQuery" @clear="searchQuery = ''" />
|
||||
<IconsCategory
|
||||
v-for="category in categories"
|
||||
:key="category.name"
|
||||
:category="category"
|
||||
:activeIconName="activeIconName"
|
||||
@setActiveIcon="setActiveIconName"
|
||||
/>
|
||||
<div v-bind="wrapperProps">
|
||||
<IconsCategory
|
||||
v-for="{ index, data } in list"
|
||||
:categoryRow="data"
|
||||
:activeIconName="activeIconName"
|
||||
@setActiveIcon="setActiveIconName"
|
||||
:key="index"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<IconDetailOverlay
|
||||
v-if="activeIconName != null"
|
||||
:iconName="activeIconName"
|
||||
@@ -119,6 +174,21 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
|
||||
}
|
||||
|
||||
.search-bar.category-search {
|
||||
margin-bottom: -54px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 8px;
|
||||
font-size: 19px;
|
||||
font-weight: 500;
|
||||
padding: 24px 0 8px;
|
||||
}
|
||||
|
||||
.icons {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.overview-container {
|
||||
padding-bottom: 288px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, defineAsyncComponent } from 'vue';
|
||||
import { ref, computed, defineAsyncComponent, onMounted, watch } from 'vue';
|
||||
import type { IconEntity } from '../../types';
|
||||
import { useMediaQuery, useOffsetPagination } from '@vueuse/core';
|
||||
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
||||
import IconGrid from './IconGrid.vue';
|
||||
import InputSearch from '../base/InputSearch.vue';
|
||||
import useSearch from '../../composables/useSearch';
|
||||
import EndOfPage from '../base/EndOfPage.vue';
|
||||
import useSearchInput from '../../composables/useSearchInput';
|
||||
import StickyBar from './StickyBar.vue';
|
||||
import useFetchTags from '../../composables/useFetchTags';
|
||||
import useFetchCategories from '../../composables/useFetchCategories';
|
||||
import chunkArray from '../../utils/chunkArray';
|
||||
|
||||
const ICON_SIZE = 56;
|
||||
const ICON_GRID_GAP = 8;
|
||||
|
||||
const props = defineProps<{
|
||||
icons: IconEntity[];
|
||||
@@ -17,32 +20,16 @@ const props = defineProps<{
|
||||
|
||||
const activeIconName = ref(null);
|
||||
|
||||
const isExtraLargeScreen = useMediaQuery('(min-width: 1440px)');
|
||||
const isLargeScreen = useMediaQuery('(min-width: 1280px)');
|
||||
const isMediumScreen = useMediaQuery('(min-width: 960px)');
|
||||
const isSmallScreen = useMediaQuery('(min-width: 640px)');
|
||||
|
||||
const pageSize = computed(() => {
|
||||
if (isExtraLargeScreen.value) {
|
||||
return 16 * 20;
|
||||
}
|
||||
if (isLargeScreen.value) {
|
||||
return 16 * 12;
|
||||
}
|
||||
if (isMediumScreen.value) {
|
||||
return 13 * 12;
|
||||
}
|
||||
|
||||
if (isSmallScreen.value) {
|
||||
return 10 * 10;
|
||||
}
|
||||
|
||||
return 10 * 5;
|
||||
});
|
||||
|
||||
const { execute: fetchTags, data: tags } = useFetchTags();
|
||||
const { execute: fetchCategories, data: categories } = useFetchCategories();
|
||||
|
||||
const overviewEl = ref<HTMLElement | null>(null);
|
||||
const { width: containerWidth } = useElementSize(overviewEl)
|
||||
|
||||
const columnSize = computed(() => {
|
||||
return Math.floor((containerWidth.value) / ((ICON_SIZE + ICON_GRID_GAP)));
|
||||
});
|
||||
|
||||
const mappedIcons = computed(() => {
|
||||
if (tags.value == null) {
|
||||
return props.icons;
|
||||
@@ -63,26 +50,33 @@ const mappedIcons = computed(() => {
|
||||
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
||||
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
|
||||
{ name: 'name', weight: 3 },
|
||||
{ name: 'aliases', weight: 3 },
|
||||
{ name: 'tags', weight: 2 },
|
||||
{ name: 'categories', weight: 1 },
|
||||
]);
|
||||
|
||||
const { next, currentPage } = useOffsetPagination({ pageSize });
|
||||
|
||||
const paginatedIcons = computed(() => {
|
||||
const end = pageSize.value * currentPage.value;
|
||||
|
||||
return searchResults.value.slice(0, end);
|
||||
const chunkedIcons = computed(() => {
|
||||
return chunkArray(searchResults.value, columnSize.value);
|
||||
});
|
||||
|
||||
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
|
||||
chunkedIcons,
|
||||
{
|
||||
itemHeight: ICON_SIZE + ICON_GRID_GAP,
|
||||
overscan: 10
|
||||
},
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
containerProps.ref.value = document.documentElement;
|
||||
useEventListener(window, 'scroll', containerProps.onScroll)
|
||||
})
|
||||
|
||||
|
||||
function setActiveIconName(name: string) {
|
||||
activeIconName.value = name;
|
||||
}
|
||||
|
||||
watch(searchQueryDebounced, (searchString) => {
|
||||
currentPage.value = 1;
|
||||
});
|
||||
|
||||
function onFocusSearchInput() {
|
||||
if (tags.value == null) {
|
||||
fetchTags();
|
||||
@@ -95,30 +89,40 @@ function onFocusSearchInput() {
|
||||
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
||||
|
||||
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
|
||||
|
||||
watch(searchQueryDebounced, () => {
|
||||
scrollTo(0)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StickyBar>
|
||||
<InputSearch
|
||||
:placeholder="`Search ${icons.length} icons ...`"
|
||||
v-model="searchQuery"
|
||||
ref="searchInput"
|
||||
class="input-wrapper"
|
||||
@focus="onFocusSearchInput"
|
||||
<div ref="overviewEl" class="overview-container">
|
||||
<StickyBar>
|
||||
<InputSearch
|
||||
:placeholder="`Search ${icons.length} icons ...`"
|
||||
v-model="searchQuery"
|
||||
ref="searchInput"
|
||||
class="input-wrapper"
|
||||
@focus="onFocusSearchInput"
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="list.length === 0"
|
||||
:searchQuery="searchQuery"
|
||||
@clear="searchQuery = ''"
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="paginatedIcons.length === 0"
|
||||
:searchQuery="searchQuery"
|
||||
@clear="searchQuery = ''"
|
||||
/>
|
||||
<IconGrid
|
||||
overlayMode
|
||||
:activeIcon="activeIconName"
|
||||
:icons="paginatedIcons"
|
||||
@setActiveIcon="setActiveIconName"
|
||||
/>
|
||||
<EndOfPage @end-of-page="next" class="bottom-page" />
|
||||
<div v-bind="wrapperProps" class="icon">
|
||||
<IconGrid
|
||||
v-for="{ index, data: icons } in list"
|
||||
:key="index"
|
||||
overlayMode
|
||||
:icons="icons"
|
||||
:activeIcon="activeIconName"
|
||||
@setActiveIcon="setActiveIconName"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IconDetailOverlay
|
||||
v-if="activeIconName != null"
|
||||
:iconName="activeIconName"
|
||||
@@ -128,10 +132,7 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
|
||||
|
||||
<style>
|
||||
.icons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@@ -142,7 +143,7 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bottom-page {
|
||||
height: 288px;
|
||||
.overview-container {
|
||||
padding-bottom: 288px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import {ref} from 'vue'
|
||||
import {bird} from '../../../data/iconNodes'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { bird, squirrel, rabbit } from '../../../data/iconNodes'
|
||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
||||
import {useEventListener} from '@vueuse/core'
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
|
||||
import { IconNode } from '../../types'
|
||||
|
||||
defineProps<{
|
||||
searchQuery: string
|
||||
@@ -11,32 +12,48 @@ defineProps<{
|
||||
|
||||
defineEmits(['clear'])
|
||||
|
||||
const birdIcon = ref<HTMLElement>()
|
||||
const Bird = createLucideIcon('bird', bird)
|
||||
const animalIcon = ref<HTMLElement>()
|
||||
const randomAnimal = computed<IconNode>(() => {
|
||||
return Math.random() > 0.5 ? squirrel : Math.random() > 0.5 ? rabbit : bird
|
||||
})
|
||||
const animalComponent = computed(() => createLucideIcon('animal', randomAnimal.value))
|
||||
const flip = ref(false)
|
||||
|
||||
useEventListener(document, 'mousemove', (mouseEvent) => {
|
||||
const {width, height, x, y} = birdIcon.value.getBoundingClientRect()
|
||||
onMounted(() => {
|
||||
useEventListener(document, 'mousemove', (mouseEvent) => {
|
||||
const {width, height, x, y} = animalIcon.value.getBoundingClientRect()
|
||||
|
||||
const centerX = (width / 2) + x
|
||||
const centerX = (width / 2) + x
|
||||
|
||||
flip.value = mouseEvent.x < centerX
|
||||
flip.value = mouseEvent.x < centerX
|
||||
})
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="no-results">
|
||||
<Bird class="bird-icon" ref="birdIcon" :class="{ flip }" :strokeWidth="1"/>
|
||||
<component
|
||||
:is="animalComponent"
|
||||
class="animal-icon"
|
||||
ref="animalIcon"
|
||||
:class="{ flip }"
|
||||
:strokeWidth="1"
|
||||
/>
|
||||
<h2 class="no-results-text">
|
||||
No icons found for '{{ searchQuery }}'
|
||||
</h2>
|
||||
<VPButton text="Clear your search and try again" theme="alt" @click="$emit('clear')"/>
|
||||
<VPButton
|
||||
text="Clear your search and try again"
|
||||
theme="alt"
|
||||
@click="$emit('clear')"
|
||||
/>
|
||||
<span class="text-divider">or</span>
|
||||
<VPButton text="Search on Github issues"
|
||||
theme="alt"
|
||||
:href="`https://github.com/lucide-icons/lucide/issues?q=is%3Aopen+${searchQuery}`"
|
||||
target="_blank"
|
||||
<VPButton
|
||||
text="Search on Github issues"
|
||||
theme="alt"
|
||||
:href="`https://github.com/lucide-icons/lucide/issues?q=is%3Aopen+${searchQuery}`"
|
||||
target="_blank"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -48,7 +65,7 @@ useEventListener(document, 'mousemove', (mouseEvent) => {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bird-icon {
|
||||
.animal-icon {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
color: var(--vp-c-neutral);
|
||||
@@ -56,12 +73,12 @@ useEventListener(document, 'mousemove', (mouseEvent) => {
|
||||
margin-top: 72px;
|
||||
}
|
||||
|
||||
.bird-icon.flip {
|
||||
.animal-icon.flip {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.bird-icon {
|
||||
.animal-icon {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
.confetti-button:before,
|
||||
.confetti-button:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
content: '';
|
||||
display: block;
|
||||
width: 140%;
|
||||
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%);
|
||||
background-size: 10% 10%, 20% 20%, 15% 15%, 20% 20%, 18% 18%, 10% 10%, 15% 15%,
|
||||
10% 10%, 18% 18%;
|
||||
background-size:
|
||||
10% 10%,
|
||||
20% 20%,
|
||||
15% 15%,
|
||||
20% 20%,
|
||||
18% 18%,
|
||||
10% 10%,
|
||||
15% 15%,
|
||||
10% 10%,
|
||||
18% 18%;
|
||||
}
|
||||
|
||||
.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%);
|
||||
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 {
|
||||
@@ -70,35 +85,89 @@
|
||||
@keyframes topBubbles {
|
||||
0% {
|
||||
color: rgb(var(--text-color) / 0);
|
||||
background-position: 5% 90%, 10% 90%, 10% 90%, 15% 90%, 25% 90%, 25% 90%,
|
||||
40% 90%, 55% 90%, 70% 90%;
|
||||
background-position:
|
||||
5% 90%,
|
||||
10% 90%,
|
||||
10% 90%,
|
||||
15% 90%,
|
||||
25% 90%,
|
||||
25% 90%,
|
||||
40% 90%,
|
||||
55% 90%,
|
||||
70% 90%;
|
||||
}
|
||||
30% {
|
||||
color: rgb(var(--text-color) / 1);
|
||||
}
|
||||
50% {
|
||||
background-position: 0% 80%, 0% 20%, 10% 40%, 20% 0%, 30% 30%, 22% 50%,
|
||||
50% 50%, 65% 20%, 90% 30%;
|
||||
background-position:
|
||||
0% 80%,
|
||||
0% 20%,
|
||||
10% 40%,
|
||||
20% 0%,
|
||||
30% 30%,
|
||||
22% 50%,
|
||||
50% 50%,
|
||||
65% 20%,
|
||||
90% 30%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 70%, 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%;
|
||||
background-position:
|
||||
0% 70%,
|
||||
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);
|
||||
}
|
||||
}
|
||||
@keyframes bottomBubbles {
|
||||
0% {
|
||||
background-position: 10% -10%, 30% 10%, 55% -10%, 70% -10%, 85% -10%,
|
||||
70% -10%, 70% 0%;
|
||||
background-position:
|
||||
10% -10%,
|
||||
30% 10%,
|
||||
55% -10%,
|
||||
70% -10%,
|
||||
85% -10%,
|
||||
70% -10%,
|
||||
70% 0%;
|
||||
}
|
||||
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%;
|
||||
}
|
||||
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%;
|
||||
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%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
import packageData from '../../../data/packageData.json';
|
||||
import thirdPartyPackages from '../../../data/packageData.thirdParty.json';
|
||||
import fetchPackages from "../../../lib/fetchPackages";
|
||||
import fetchPackages from '../../../lib/fetchPackages';
|
||||
|
||||
export default {
|
||||
async load() {
|
||||
const packages = await fetchPackages();
|
||||
return {
|
||||
packages: packages
|
||||
.filter(p => p.name in packageData)
|
||||
.filter((p) => p.name in packageData)
|
||||
.map((pData) => ({
|
||||
...pData,
|
||||
...packageData[pData.name],
|
||||
documentation: `/guide/packages/${pData.name}`,
|
||||
source: `https://github.com/lucide-icons/lucide/tree/main/packages/${pData.name}`,
|
||||
icon: `/framework-logos/${packageData[pData.name].icon}.svg`,
|
||||
})).sort((a, b) => a.order - b.order),
|
||||
...pData,
|
||||
...packageData[pData.name],
|
||||
documentation: `/guide/packages/${pData.name}`,
|
||||
source: `https://github.com/lucide-icons/lucide/tree/main/packages/${pData.name}`,
|
||||
icon: `/framework-logos/${packageData[pData.name].icon}.svg`,
|
||||
}))
|
||||
.sort((a, b) => a.order - b.order),
|
||||
thirdPartyPackages,
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,73 +1,76 @@
|
||||
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) {
|
||||
const onScroll = throttleAndDebounce(setActiveLink, 100);
|
||||
const setActiveLinkDebounced = throttleAndDebounce(setActiveLink, 100);
|
||||
let prevActiveLink = null;
|
||||
onMounted(() => {
|
||||
requestAnimationFrame(setActiveLink);
|
||||
window.addEventListener('scroll', onScroll);
|
||||
requestAnimationFrame(setActiveLink);
|
||||
window.addEventListener('scroll', setActiveLinkDebounced);
|
||||
});
|
||||
onUpdated(() => {
|
||||
// sidebar update means a route change
|
||||
activateLink(location.hash);
|
||||
// sidebar update means a route change
|
||||
activateLink(location.hash);
|
||||
});
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('scroll', onScroll);
|
||||
window.removeEventListener('scroll', setActiveLinkDebounced);
|
||||
});
|
||||
function setActiveLink() {
|
||||
const links = [].slice.call(container.value.querySelectorAll('.outline-link'));
|
||||
const anchors = [].slice
|
||||
.call(document.querySelectorAll('.content .header-anchor'))
|
||||
.filter((anchor) => {
|
||||
return links.some((link) => {
|
||||
return link.hash === anchor.hash && anchor.offsetParent !== null;
|
||||
});
|
||||
const links = [].slice.call(container.value.querySelectorAll('.outline-link'));
|
||||
const anchors = [].slice
|
||||
.call(document.querySelectorAll('.content .header-anchor'))
|
||||
.filter((anchor) => {
|
||||
return links.some((link) => {
|
||||
return link.hash === anchor.hash && anchor.offsetParent !== null;
|
||||
});
|
||||
});
|
||||
const scrollY = window.scrollY;
|
||||
const innerHeight = window.innerHeight;
|
||||
const offsetHeight = document.body.offsetHeight;
|
||||
const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1;
|
||||
// page bottom - highlight last one
|
||||
if (anchors.length && isBottom) {
|
||||
activateLink(anchors[anchors.length - 1].hash);
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < anchors.length; i++) {
|
||||
const anchor = anchors[i];
|
||||
const nextAnchor = anchors[i + 1];
|
||||
const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor);
|
||||
if (isActive) {
|
||||
activateLink(hash);
|
||||
return;
|
||||
}
|
||||
const scrollY = window.scrollY;
|
||||
const innerHeight = window.innerHeight;
|
||||
const offsetHeight = document.body.offsetHeight;
|
||||
const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1;
|
||||
// page bottom - highlight last one
|
||||
if (anchors.length && isBottom) {
|
||||
activateLink(anchors[anchors.length - 1].hash);
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < anchors.length; i++) {
|
||||
const anchor = anchors[i];
|
||||
const nextAnchor = anchors[i + 1];
|
||||
const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor);
|
||||
if (isActive) {
|
||||
activateLink(hash);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
function activateLink(hash) {
|
||||
if (prevActiveLink) {
|
||||
prevActiveLink.classList.remove('active');
|
||||
}
|
||||
if (hash !== null) {
|
||||
prevActiveLink = container.value.querySelector(`a[href="${decodeURIComponent(hash)}"]`);
|
||||
}
|
||||
const activeLink = prevActiveLink;
|
||||
if (activeLink) {
|
||||
activeLink.classList.add('active');
|
||||
marker.value.style.top = activeLink.offsetTop + 5 + 'px';
|
||||
marker.value.style.opacity = '1';
|
||||
}
|
||||
else {
|
||||
marker.value.style.top = '33px';
|
||||
marker.value.style.opacity = '0';
|
||||
}
|
||||
if (prevActiveLink) {
|
||||
prevActiveLink.classList.remove('active');
|
||||
}
|
||||
if (hash !== null) {
|
||||
prevActiveLink = container.value.querySelector(`a[href="${decodeURIComponent(hash)}"]`);
|
||||
}
|
||||
const activeLink = prevActiveLink;
|
||||
if (activeLink) {
|
||||
activeLink.classList.add('active');
|
||||
marker.value.style.top = activeLink.offsetTop + 5 + 'px';
|
||||
marker.value.style.opacity = '1';
|
||||
} else {
|
||||
marker.value.style.top = '33px';
|
||||
marker.value.style.opacity = '0';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
setActiveLinkDebounced,
|
||||
};
|
||||
}
|
||||
|
||||
const PAGE_OFFSET = 64;
|
||||
const PAGE_OFFSET = 128;
|
||||
|
||||
function getAnchorTop(anchor) {
|
||||
return anchor.parentElement.offsetTop - PAGE_OFFSET;
|
||||
@@ -75,13 +78,13 @@ function getAnchorTop(anchor) {
|
||||
function isAnchorActive(index, anchor, nextAnchor) {
|
||||
const scrollTop = window.scrollY;
|
||||
if (index === 0 && scrollTop === 0) {
|
||||
return [true, null];
|
||||
return [true, anchor.hash];
|
||||
}
|
||||
if (scrollTop < getAnchorTop(anchor)) {
|
||||
return [false, null];
|
||||
return [false, null];
|
||||
}
|
||||
if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor)) {
|
||||
return [true, anchor.hash];
|
||||
return [true, anchor.hash];
|
||||
}
|
||||
return [false, null];
|
||||
}
|
||||
|
||||
@@ -1,25 +1,38 @@
|
||||
import {
|
||||
ref, inject, Ref
|
||||
} from 'vue';
|
||||
import { useRoute } from 'vitepress';
|
||||
import { ref, inject, Ref, onMounted, watch } from 'vue';
|
||||
|
||||
export const CATEGORY_VIEW_CONTEXT = Symbol('categoryView');
|
||||
|
||||
interface CategoryViewContext {
|
||||
selectedCategory: Ref<string>
|
||||
categoryCounts: Ref<Record<string, number>>
|
||||
selectedCategory: Ref<string>;
|
||||
categoryCounts: Ref<Record<string, number>>;
|
||||
}
|
||||
|
||||
export const categoryViewContext = {
|
||||
selectedCategory: ref(''),
|
||||
selectedCategory: ref(),
|
||||
categoryCounts: ref({}),
|
||||
};
|
||||
|
||||
export function useCategoryView(): CategoryViewContext {
|
||||
const context = inject<CategoryViewContext>(CATEGORY_VIEW_CONTEXT);
|
||||
const route = useRoute();
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useCategoryView must be used with categoryView context');
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (window.location.hash) {
|
||||
context.selectedCategory.value = decodeURIComponent(window.location.hash.slice(1));
|
||||
}
|
||||
});
|
||||
|
||||
watch(route, (currentRoute) => {
|
||||
if (currentRoute.path !== '/icons/categories') {
|
||||
context.selectedCategory.value = '';
|
||||
context.categoryCounts.value = {};
|
||||
}
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ref } from "vue";
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default function useConfetti() {
|
||||
const animate = ref(false)
|
||||
const confettiText = ref('confetti!')
|
||||
const animate = ref(false);
|
||||
const confettiText = ref('confetti!');
|
||||
|
||||
function confetti() {
|
||||
animate.value = true;
|
||||
@@ -15,6 +15,6 @@ export default function useConfetti() {
|
||||
return {
|
||||
animate,
|
||||
confetti,
|
||||
confettiText
|
||||
}
|
||||
confettiText,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useFetch } from "@vueuse/core"
|
||||
import { useFetch } from '@vueuse/core';
|
||||
|
||||
const useFetchCategories = () => useFetch<Record<string, string[]>>(
|
||||
`${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/categories`,
|
||||
{
|
||||
immediate:
|
||||
typeof window !== 'undefined'
|
||||
&& new URLSearchParams(window.location.search).has('search'),
|
||||
}
|
||||
).json()
|
||||
const useFetchCategories = () =>
|
||||
useFetch<Record<string, string[]>>(
|
||||
`${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/categories`,
|
||||
{
|
||||
immediate:
|
||||
typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('search'),
|
||||
},
|
||||
).json();
|
||||
|
||||
export default useFetchCategories
|
||||
export default useFetchCategories;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useFetch } from "@vueuse/core"
|
||||
import { useFetch } from '@vueuse/core';
|
||||
|
||||
const useFetchTags = () => useFetch<Record<string, string[]>>(
|
||||
`${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/tags`,
|
||||
{
|
||||
immediate:
|
||||
typeof window !== 'undefined'
|
||||
&& new URLSearchParams(window.location.search).has('search'),
|
||||
}
|
||||
).json()
|
||||
const useFetchTags = () =>
|
||||
useFetch<Record<string, string[]>>(
|
||||
`${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/tags`,
|
||||
{
|
||||
immediate:
|
||||
typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('search'),
|
||||
},
|
||||
).json();
|
||||
|
||||
export default useFetchTags
|
||||
export default useFetchTags;
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import {
|
||||
ref, inject, Ref
|
||||
} from 'vue';
|
||||
import { ref, inject, Ref } from 'vue';
|
||||
|
||||
export const ICON_STYLE_CONTEXT = Symbol('size');
|
||||
|
||||
interface IconSizeContext {
|
||||
size: Ref<number>
|
||||
strokeWidth: Ref<number>
|
||||
color: Ref<string>
|
||||
absoluteStrokeWidth: Ref<boolean>
|
||||
size: Ref<number>;
|
||||
strokeWidth: Ref<number>;
|
||||
color: Ref<string>;
|
||||
absoluteStrokeWidth: Ref<boolean>;
|
||||
}
|
||||
|
||||
export const STYLE_DEFAULTS = {
|
||||
@@ -27,7 +25,7 @@ export const iconStyleContext = {
|
||||
absoluteStrokeWidth: ref(false),
|
||||
};
|
||||
|
||||
export function useIconStyleContext(): IconSizeContext{
|
||||
export function useIconStyleContext(): IconSizeContext {
|
||||
const context = inject<IconSizeContext>(ICON_STYLE_CONTEXT);
|
||||
|
||||
if (!context) {
|
||||
|
||||
52
docs/.vitepress/theme/composables/useScrollToCategory.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Ref, onMounted, watch } from 'vue';
|
||||
import { CategoryRow } from '../components/icons/IconsCategory.vue';
|
||||
import { Category } from '../types';
|
||||
import { useCategoryView } from './useCategoryView';
|
||||
|
||||
interface UseScrollToCategory {
|
||||
categories: Ref<Pick<Category, 'name' | 'icons'>[]>;
|
||||
categoriesList: Ref<CategoryRow[]>;
|
||||
scrollTo: (index: number) => void;
|
||||
searchQueryDebounced: Ref<string>;
|
||||
}
|
||||
|
||||
export default function useScrollToCategory({
|
||||
categories,
|
||||
categoriesList,
|
||||
scrollTo,
|
||||
searchQueryDebounced,
|
||||
}: UseScrollToCategory) {
|
||||
const { selectedCategory, categoryCounts } = useCategoryView();
|
||||
|
||||
function scrollToSelectedCategory(selectedCategory: string) {
|
||||
const category = categories.value.find((category) => category.name === selectedCategory);
|
||||
|
||||
if (category != null) {
|
||||
const categoryRowIndex = categoriesList.value.findIndex(
|
||||
(row) => row.type === 'category' && row.name === selectedCategory,
|
||||
);
|
||||
|
||||
if (categoryRowIndex !== -1) {
|
||||
setTimeout(() => {
|
||||
scrollTo(categoryRowIndex);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(selectedCategory, scrollToSelectedCategory);
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
scrollToSelectedCategory(selectedCategory.value);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
watch(searchQueryDebounced, () => {
|
||||
scrollTo(0);
|
||||
});
|
||||
|
||||
watch(categories, (items) => {
|
||||
categoryCounts.value = Object.fromEntries(items.map(({ name, icons }) => [name, icons.length]));
|
||||
});
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
import Fuse from 'fuse.js';
|
||||
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.FuseOptionKeyObject<T>[] = [],
|
||||
) => {
|
||||
const index = shallowRef(
|
||||
new Fuse(collection.value, {
|
||||
threshold: 0.2,
|
||||
keys,
|
||||
})
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
const results = computed(() => {
|
||||
index.value.setCollection(collection.value);
|
||||
@@ -16,6 +20,16 @@ const useSearch = <T>(query: Ref<string>, collection: Ref<T[]>, keys: Fuse.FuseO
|
||||
return index.value.search(query.value).map((result) => result.item);
|
||||
}
|
||||
|
||||
if (keys.length !== 0) {
|
||||
const mainKey = keys[0].name;
|
||||
|
||||
return collection.value.sort((a, b) => {
|
||||
const aString = a[mainKey as keyof T] as string;
|
||||
const bString = b[mainKey as keyof T] as string;
|
||||
return aString.localeCompare(bString);
|
||||
});
|
||||
}
|
||||
|
||||
return collection.value;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { h } from 'vue'
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import './style.css'
|
||||
import { Theme } from 'vitepress'
|
||||
import IconsSidebarNavAfter from './layouts/IconsSidebarNavAfter.vue'
|
||||
import HomeHeroIconsCard from './components/home/HomeHeroIconsCard.vue'
|
||||
import HomeHeroBefore from "./components/home/HomeHeroBefore.vue";
|
||||
import { ICON_STYLE_CONTEXT, iconStyleContext } from './composables/useIconStyle'
|
||||
import { CATEGORY_VIEW_CONTEXT, categoryViewContext } from './composables/useCategoryView'
|
||||
import { h } from 'vue';
|
||||
import DefaultTheme from 'vitepress/theme';
|
||||
import './style.css';
|
||||
import { Theme } from 'vitepress';
|
||||
import IconsSidebarNavAfter from './layouts/IconsSidebarNavAfter.vue';
|
||||
import HomeHeroIconsCard from './components/home/HomeHeroIconsCard.vue';
|
||||
import HomeHeroBefore from './components/home/HomeHeroBefore.vue';
|
||||
import { ICON_STYLE_CONTEXT, iconStyleContext } from './composables/useIconStyle';
|
||||
import { CATEGORY_VIEW_CONTEXT, categoryViewContext } from './composables/useCategoryView';
|
||||
|
||||
const theme: Partial<Theme> = {
|
||||
extends: DefaultTheme,
|
||||
@@ -15,12 +15,12 @@ const theme: Partial<Theme> = {
|
||||
'home-hero-before': () => h(HomeHeroBefore),
|
||||
'sidebar-nav-after': () => h(IconsSidebarNavAfter),
|
||||
'home-hero-image': () => h(HomeHeroIconsCard),
|
||||
})
|
||||
});
|
||||
},
|
||||
enhanceApp({ app }) {
|
||||
app.provide(ICON_STYLE_CONTEXT, iconStyleContext)
|
||||
app.provide(CATEGORY_VIEW_CONTEXT, categoryViewContext)
|
||||
}
|
||||
}
|
||||
app.provide(ICON_STYLE_CONTEXT, iconStyleContext);
|
||||
app.provide(CATEGORY_VIEW_CONTEXT, categoryViewContext);
|
||||
},
|
||||
};
|
||||
|
||||
export default theme
|
||||
export default theme;
|
||||
|
||||
@@ -1,43 +1,47 @@
|
||||
:root {
|
||||
--vp-c-brand: #F56565;
|
||||
--vp-c-brand-light: #F67373;
|
||||
--vp-c-brand-lighter: #F89191;
|
||||
--vp-c-brand-dark: #DC5A5A;
|
||||
--vp-c-brand-darker: #C45050;
|
||||
--vp-c-brand: #f56565;
|
||||
--vp-c-brand-light: #f67373;
|
||||
--vp-c-brand-lighter: #f89191;
|
||||
--vp-c-brand-dark: #dc5a5a;
|
||||
--vp-c-brand-darker: #c45050;
|
||||
|
||||
--vp-c-brand-1: #F67373;
|
||||
--vp-c-brand-2: #FF7070;
|
||||
--vp-c-brand-3: #F56565;
|
||||
--vp-c-brand-4: #DC5A5A;
|
||||
--vp-c-brand-5: #C45050;
|
||||
--vp-c-brand-1: #f67373;
|
||||
--vp-c-brand-2: #ff7070;
|
||||
--vp-c-brand-3: #f56565;
|
||||
--vp-c-brand-4: #dc5a5a;
|
||||
--vp-c-brand-5: #c45050;
|
||||
|
||||
--vp-c-bg-alt-up: #fff;
|
||||
--vp-c-bg-alt-down: #fff;
|
||||
|
||||
--vp-code-editor-plain: #24292E;
|
||||
--vp-code-editor-comment: #6A737D;
|
||||
--vp-code-editor-keyword: #D73A49;
|
||||
--vp-code-editor-tag: #22863A;
|
||||
--vp-code-editor-punctuation: #24292E;
|
||||
--vp-code-editor-definition: #6F42C1;
|
||||
--vp-code-editor-property: #005CC5;
|
||||
--vp-code-editor-static: #F78C6C;
|
||||
--vp-code-editor-string: #032F62;
|
||||
--vp-code-editor-plain: #24292e;
|
||||
--vp-code-editor-comment: #6a737d;
|
||||
--vp-code-editor-keyword: #d73a49;
|
||||
--vp-code-editor-tag: #22863a;
|
||||
--vp-code-editor-punctuation: #24292e;
|
||||
--vp-code-editor-definition: #6f42c1;
|
||||
--vp-code-editor-property: #005cc5;
|
||||
--vp-code-editor-static: #f78c6c;
|
||||
--vp-code-editor-string: #032f62;
|
||||
|
||||
--vp-c-text-4: rgba(60, 60, 67, 0.32);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-c-bg-alt-up: #1B1B1D;
|
||||
--vp-c-bg-alt-down: #0F0F10;
|
||||
--vp-c-bg-alt-up: #1b1b1d;
|
||||
--vp-c-bg-alt-down: #0f0f10;
|
||||
|
||||
--vp-code-editor-plain: #E1E4E8;
|
||||
--vp-code-editor-comment: #6A737D;
|
||||
--vp-code-editor-keyword: #F97583;
|
||||
--vp-code-editor-tag: #85E89D;
|
||||
--vp-code-editor-punctuation: #9ECBFF;
|
||||
--vp-code-editor-definition: #B392F0;
|
||||
--vp-code-editor-property: #79B8FF;
|
||||
--vp-code-editor-static: #F78C6C;
|
||||
--vp-code-editor-string: #9ECBFF;
|
||||
--vp-code-editor-plain: #e1e4e8;
|
||||
--vp-code-editor-comment: #6a737d;
|
||||
--vp-code-editor-keyword: #f97583;
|
||||
--vp-code-editor-tag: #85e89d;
|
||||
--vp-code-editor-punctuation: #9ecbff;
|
||||
--vp-code-editor-definition: #b392f0;
|
||||
--vp-code-editor-property: #79b8ff;
|
||||
--vp-code-editor-static: #f78c6c;
|
||||
--vp-code-editor-string: #9ecbff;
|
||||
|
||||
--vp-c-text-4: rgba(235, 235, 245, 0.16);
|
||||
}
|
||||
|
||||
.VPNavBarTitle .logo {
|
||||
@@ -69,7 +73,6 @@
|
||||
|
||||
.VPHomeHero .container .main h1.name {
|
||||
color: var(--vp-c-text);
|
||||
|
||||
}
|
||||
.VPHomeHero .container .main h1.name .clip {
|
||||
color: inherit;
|
||||
@@ -82,7 +85,6 @@
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
|
||||
/* */
|
||||
.VPHomeHero .container .image {
|
||||
margin: 0;
|
||||
@@ -101,14 +103,14 @@
|
||||
}
|
||||
|
||||
.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%;
|
||||
}
|
||||
|
||||
.VPDoc:has(.vp-doc[class*=" _icons_"]) > .container > .content{
|
||||
.VPDoc:has(.vp-doc[class*=' _icons_']) > .container > .content {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
@@ -120,7 +122,6 @@
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
|
||||
.VPHomeHero .container .image {
|
||||
order: 1;
|
||||
margin-bottom: auto;
|
||||
@@ -141,7 +142,6 @@
|
||||
}
|
||||
|
||||
.VPHomeHero .container .main h1.name {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,10 +198,10 @@ html:has(* .outline-link:target) {
|
||||
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);
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export type IconNode = [elementName: string, attrs: Record<string, string>][]
|
||||
export type IconNodeWithKeys = [elementName: string, attrs: Record<string, string>, key: string][]
|
||||
export type IconNode = [elementName: string, attrs: Record<string, string>][];
|
||||
export type IconNodeWithKeys = [elementName: string, attrs: Record<string, string>, key: string][];
|
||||
|
||||
export interface IconEntity {
|
||||
name: string;
|
||||
@@ -13,45 +13,44 @@ export interface IconEntity {
|
||||
}
|
||||
|
||||
export interface Category {
|
||||
name: string
|
||||
title: string
|
||||
icon?: string
|
||||
iconCount: number
|
||||
icons?: IconEntity[]
|
||||
name: string;
|
||||
title: string;
|
||||
icon?: string;
|
||||
iconCount: number;
|
||||
icons?: IconEntity[];
|
||||
}
|
||||
|
||||
interface Shield {
|
||||
alt: string
|
||||
src: string
|
||||
href: string
|
||||
alt: string;
|
||||
src: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
export interface PackageItem {
|
||||
name: string
|
||||
description: string
|
||||
icon: string
|
||||
iconDark: string
|
||||
shields: Shield[]
|
||||
source: string
|
||||
documentation: string
|
||||
order?: number
|
||||
private?: boolean
|
||||
flutter?: object
|
||||
name: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
iconDark: string;
|
||||
shields: Shield[];
|
||||
source: string;
|
||||
documentation: string;
|
||||
order?: number;
|
||||
private?: boolean;
|
||||
flutter?: object;
|
||||
}
|
||||
|
||||
|
||||
export interface Release {
|
||||
version: string
|
||||
date: string
|
||||
version: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
interface ShowcaseItemImage {
|
||||
light: string
|
||||
dark: string
|
||||
light: string;
|
||||
dark: string;
|
||||
}
|
||||
|
||||
export interface ShowcaseItem {
|
||||
name: string
|
||||
url: string
|
||||
image: ShowcaseItemImage
|
||||
name: string;
|
||||
url: string;
|
||||
image: ShowcaseItemImage;
|
||||
}
|
||||
|
||||
9
docs/.vitepress/theme/utils/chunkArray.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
const chunkArray = <ArrayType>(stream: ArrayType, size: number) => {
|
||||
return stream.reduce<ArrayType[][]>(
|
||||
(chunks, item, idx, arr) =>
|
||||
idx % size == 0 ? [...chunks, arr.slice(idx, idx + size)] : chunks,
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
export default chunkArray;
|
||||
@@ -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');
|
||||
link.download = filename;
|
||||
link.href = data
|
||||
link.href = data;
|
||||
link.click();
|
||||
}
|
||||
|
||||
@@ -9,26 +9,26 @@ const allowedAttrs = [
|
||||
'stroke-linecap',
|
||||
'stroke-linejoin',
|
||||
'class',
|
||||
]
|
||||
];
|
||||
|
||||
export default function getSVGIcon(element?: HTMLElement, attrs?: Record<string, string>) {
|
||||
const svg = element ?? document.querySelector('#previewer svg')
|
||||
if (!svg) return
|
||||
const svg = element ?? document.querySelector('#previewer svg');
|
||||
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
|
||||
for (const attr of Array.from(clonedSvg.attributes)) {
|
||||
if (!allowedAttrs.includes(attr.name)) {
|
||||
clonedSvg.removeAttribute(attr.name)
|
||||
clonedSvg.removeAttribute(attr.name);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -106,6 +106,15 @@ Here are rules that should be followed to keep quality and consistency when maki
|
||||
6. Names containing numerals are not allowed, unless the number itself is represented in the icon.\
|
||||
For example: `arrow-down-0-to-1` contains both numerals.
|
||||
|
||||
7. Icons depicting multiple elements (e.g. a person and a circle) of different sizes must list these elements in decreasing order of size.\
|
||||
For example: if the circle is bigger, it should be `circle-person`, if the person is bigger, it should be `person-circle`.
|
||||
|
||||
8. Icons depicting multiple elements of roughly equal sizes (e.g. a `ruler` and a `pencil`) must list these elements in English reading order.\
|
||||
For example: if the `pencil` is either above or left of `ruler`, it should be `pencil-ruler`, otherwise, it should be `ruler-pencil`.
|
||||
|
||||
9. Icons depicting some sort of variation of an element must use the `[element]-[modifier]` naming scheme, with modifiers being applied to each element respectively.\
|
||||
For example: a dashed circle must be named `circle-dashed`, not `dashed-circle`, and in coordination with the previous guidelines, a dashed circle containing a broken heart would be named `circle-dashed-heart-broken`, due to the heart being smaller than the circle.
|
||||
|
||||
## Code Conventions
|
||||
|
||||
Before an icon is added to the library, we like to have readable and optimized SVG code.
|
||||
|
||||
@@ -4,7 +4,7 @@ Implementation of the lucide icon library for React Native applications
|
||||
|
||||
## Installation
|
||||
|
||||
First, ensure that you have `react-native-svg` (version between 12 and 14) installed. Then, install the package:
|
||||
First, ensure that you have `react-native-svg` (version between 12 and 15) installed. Then, install the package:
|
||||
|
||||
::: code-group
|
||||
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
import relatedIcons from '../.vitepress/data/relatedIcons.json'
|
||||
import iconNodes from '../.vitepress/data/iconNodes'
|
||||
import * as iconDetails from '../.vitepress/data/iconDetails'
|
||||
import { IconEntity } from "../.vitepress/theme/types";
|
||||
import relatedIcons from '../.vitepress/data/relatedIcons.json';
|
||||
import iconNodes from '../.vitepress/data/iconNodes';
|
||||
import * as iconDetails from '../.vitepress/data/iconDetails';
|
||||
import { IconEntity } from '../.vitepress/theme/types';
|
||||
|
||||
export default {
|
||||
paths: async () => {
|
||||
return (Object.values(iconDetails) as unknown as IconEntity[]).map((iconEntity) => {
|
||||
|
||||
const params = {
|
||||
...iconEntity,
|
||||
relatedIcons: relatedIcons[iconEntity.name].map((name: string) => ({
|
||||
name,
|
||||
iconNode: iconNodes[name],
|
||||
}))
|
||||
}
|
||||
})),
|
||||
};
|
||||
|
||||
return {
|
||||
params,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { getAllCategoryFiles } from '../.vitepress/lib/categories'
|
||||
import iconMetaData from '../.vitepress/data/iconMetaData'
|
||||
import { getAllCategoryFiles } from '../.vitepress/lib/categories';
|
||||
import iconMetaData from '../.vitepress/data/iconMetaData';
|
||||
|
||||
export default {
|
||||
async load() {
|
||||
return {
|
||||
categories: getAllCategoryFiles(),
|
||||
iconCategories: Object.fromEntries(
|
||||
Object.entries(iconMetaData).map(([name, { categories }]) => [name, categories])
|
||||
Object.entries(iconMetaData).map(([name, { categories }]) => [name, categories]),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import createCodeExamples from '../.vitepress/lib/createCodeExamples'
|
||||
import createCodeExamples from '../.vitepress/lib/createCodeExamples';
|
||||
|
||||
export default {
|
||||
async load() {
|
||||
const codeExamples = await createCodeExamples()
|
||||
const codeExamples = await createCodeExamples();
|
||||
|
||||
// const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons))
|
||||
|
||||
return {
|
||||
codeExamples,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import iconNodes from '../.vitepress/data/iconNodes'
|
||||
import iconNodes from '../.vitepress/data/iconNodes';
|
||||
|
||||
export default {
|
||||
async load() {
|
||||
return {
|
||||
icons: Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode })),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"lucide-vue-next": "workspace:*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"sandpack-vue3": "3.1.8",
|
||||
"sandpack-vue3": "3.1.11",
|
||||
"semver": "^7.5.2",
|
||||
"shikiji": "^0.7.4",
|
||||
"simple-git": "^3.18.0",
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
"jsx": "react",
|
||||
"allowImportingTsExtensions": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"noEmit": true
|
||||
}
|
||||
"noEmit": true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
"cast",
|
||||
"mirroring",
|
||||
"screen",
|
||||
"monitor"
|
||||
"monitor",
|
||||
"macos",
|
||||
"osx"
|
||||
],
|
||||
"categories": [
|
||||
"multimedia",
|
||||
|
||||
@@ -10,5 +10,5 @@
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M5 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-1" />
|
||||
<polygon points="12 15 17 21 7 21 12 15" />
|
||||
<path d="m12 15 5 6H7Z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 327 B |
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"danielbayley"
|
||||
"danielbayley",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"tags": [
|
||||
"fire",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
"ericfennis",
|
||||
"jguddas"
|
||||
],
|
||||
"tags": [
|
||||
"items",
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect width="14" height="6" x="5" y="14" rx="2" />
|
||||
<rect width="10" height="6" x="7" y="4" rx="2" />
|
||||
<path d="M22 7h-5" />
|
||||
<path d="M7 7H1" />
|
||||
<path d="M22 17h-3" />
|
||||
<path d="M22 7h-5" />
|
||||
<path d="M5 17H2" />
|
||||
<path d="M7 7H2" />
|
||||
<rect x="5" y="14" width="14" height="6" rx="2" />
|
||||
<rect x="7" y="4" width="10" height="6" rx="2" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 407 B After Width: | Height: | Size: 407 B |
30
icons/ambulance.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"jordan808",
|
||||
"jguddas",
|
||||
"colebemis",
|
||||
"ahtohbi4",
|
||||
"ericfennis",
|
||||
"Andreto",
|
||||
"csandman",
|
||||
"karsa-mistmere",
|
||||
"danielbayley"
|
||||
],
|
||||
"tags": [
|
||||
"ambulance",
|
||||
"emergency",
|
||||
"medical",
|
||||
"vehicle",
|
||||
"siren",
|
||||
"healthcare",
|
||||
"transportation",
|
||||
"rescue",
|
||||
"urgent",
|
||||
"first aid"
|
||||
],
|
||||
"categories": [
|
||||
"medical",
|
||||
"transportation"
|
||||
]
|
||||
}
|
||||
20
icons/ambulance.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M10 10H6" />
|
||||
<path d="M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2" />
|
||||
<path
|
||||
d="M19 18h2a1 1 0 0 0 1-1v-3.28a1 1 0 0 0-.684-.948l-1.923-.641a1 1 0 0 1-.578-.502l-1.539-3.076A1 1 0 0 0 16.382 8H14" />
|
||||
<path d="M8 8v4" />
|
||||
<path d="M9 18h6" />
|
||||
<circle cx="17" cy="18" r="2" />
|
||||
<circle cx="7" cy="18" r="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 553 B |
@@ -9,7 +9,7 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="5" r="3" />
|
||||
<line x1="12" x2="12" y1="22" y2="8" />
|
||||
<path d="M12 22V8" />
|
||||
<path d="M5 12H2a10 10 0 0 0 20 0h-3" />
|
||||
<circle cx="12" cy="5" r="3" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 327 B After Width: | Height: | Size: 309 B |
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"karsa-mistmere",
|
||||
"danielbayley"
|
||||
],
|
||||
"tags": [
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M7 10c-2.8 0-5-2.2-5-5h5" />
|
||||
<path d="M7 4v8h7a8 8 0 0 0 8-8Z" />
|
||||
<path d="M7 10H6a4 4 0 0 1-4-4 1 1 0 0 1 1-1h4" />
|
||||
<path d="M7 5a1 1 0 0 1 1-1h13a1 1 0 0 1 1 1 7 7 0 0 1-7 7H8a1 1 0 0 1-1-1z" />
|
||||
<path d="M9 12v5" />
|
||||
<path d="M15 12v5" />
|
||||
<path d="M5 20a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v1H5Z" />
|
||||
<path d="M5 20a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3 1 1 0 0 1-1 1H6a1 1 0 0 1-1-1" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 390 B After Width: | Height: | Size: 471 B |
21
icons/app-window-mac.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"danielbayley"
|
||||
],
|
||||
"tags": [
|
||||
"application",
|
||||
"menu bar",
|
||||
"pane",
|
||||
"preferences",
|
||||
"macos",
|
||||
"osx",
|
||||
"executable"
|
||||
],
|
||||
"categories": [
|
||||
"layout",
|
||||
"design",
|
||||
"development",
|
||||
"files"
|
||||
]
|
||||
}
|
||||
16
icons/app-window-mac.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect width="20" height="16" x="2" y="4" rx="2" />
|
||||
<path d="M6 8h.01" />
|
||||
<path d="M10 8h.01" />
|
||||
<path d="M14 8h.01" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 335 B |
@@ -6,11 +6,14 @@
|
||||
],
|
||||
"tags": [
|
||||
"application",
|
||||
"menu bar",
|
||||
"pane",
|
||||
"executable"
|
||||
],
|
||||
"categories": [
|
||||
"layout",
|
||||
"design",
|
||||
"files",
|
||||
"layout"
|
||||
"development",
|
||||
"files"
|
||||
]
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
],
|
||||
"tags": [
|
||||
"statistics",
|
||||
"analytics",
|
||||
"diagram",
|
||||
"graph",
|
||||
"area"
|
||||
|
||||
@@ -10,5 +10,5 @@
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M15 5H9" />
|
||||
<path d="M15 9v3h4l-7 7-7-7h4V9h6z" />
|
||||
<path d="M15 9v3h4l-7 7-7-7h4V9z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 272 B After Width: | Height: | Size: 270 B |
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"karsa-mistmere"
|
||||
"karsa-mistmere",
|
||||
"danielbayley"
|
||||
],
|
||||
"tags": [
|
||||
"filter",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"karsa-mistmere"
|
||||
"karsa-mistmere",
|
||||
"danielbayley"
|
||||
],
|
||||
"tags": [
|
||||
"filter",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"karsa-mistmere"
|
||||
"karsa-mistmere",
|
||||
"danielbayley"
|
||||
],
|
||||
"tags": [
|
||||
"filter",
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"danielbayley",
|
||||
"karsa-mistmere"
|
||||
"danielbayley"
|
||||
],
|
||||
"tags": [
|
||||
"direction",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"karsa-mistmere"
|
||||
"karsa-mistmere",
|
||||
"danielbayley"
|
||||
],
|
||||
"tags": [
|
||||
"filter",
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"wojtekmaj",
|
||||
"ericfennis",
|
||||
"karsa-mistmere"
|
||||
"ericfennis"
|
||||
],
|
||||
"tags": [
|
||||
"bidirectional",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"karsa-mistmere"
|
||||
"karsa-mistmere",
|
||||
"danielbayley"
|
||||
],
|
||||
"tags": [
|
||||
"filter",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"karsa-mistmere"
|
||||
"karsa-mistmere",
|
||||
"danielbayley"
|
||||
],
|
||||
"tags": [
|
||||
"filter",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"karsa-mistmere"
|
||||
"karsa-mistmere",
|
||||
"danielbayley"
|
||||
],
|
||||
"tags": [
|
||||
"filter",
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"danielbayley",
|
||||
"karsa-mistmere"
|
||||
"danielbayley"
|
||||
],
|
||||
"tags": [
|
||||
"direction",
|
||||
|
||||